summaryrefslogtreecommitdiffstats
path: root/test/units
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:35:18 +0000
commitb750101eb236130cf056c675997decbac904cc49 (patch)
treea5df1a06754bdd014cb975c051c83b01c9a97532 /test/units
parentInitial commit. (diff)
downloadsystemd-b750101eb236130cf056c675997decbac904cc49.tar.xz
systemd-b750101eb236130cf056c675997decbac904cc49.zip
Adding upstream version 252.22.upstream/252.22
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/units')
-rw-r--r--test/units/a-conj.service9
-rw-r--r--test/units/a.service8
-rwxr-xr-xtest/units/assert.sh58
-rw-r--r--test/units/autorelabel.service19
-rw-r--r--test/units/b.service7
-rw-r--r--test/units/basic.target22
-rw-r--r--test/units/c.service7
-rw-r--r--test/units/d.service9
-rw-r--r--test/units/daughter.service9
-rw-r--r--test/units/dml-discard-empty.service8
-rw-r--r--test/units/dml-discard-set-ml.service9
-rw-r--r--test/units/dml-discard.slice6
-rw-r--r--test/units/dml-override-empty.service8
-rw-r--r--test/units/dml-override.slice6
-rw-r--r--test/units/dml-passthrough-empty.service8
-rw-r--r--test/units/dml-passthrough-set-dml.service9
-rw-r--r--test/units/dml-passthrough-set-ml.service9
-rw-r--r--test/units/dml-passthrough.slice6
-rw-r--r--test/units/dml.slice6
-rw-r--r--test/units/e.service9
-rw-r--r--test/units/end.service11
-rw-r--r--test/units/f.service6
-rw-r--r--test/units/g.service7
-rw-r--r--test/units/grandchild.service8
-rw-r--r--test/units/h.service7
-rw-r--r--test/units/i.service9
-rw-r--r--test/units/loopy.service3
-rw-r--r--test/units/loopy.service.d/compat.conf6
-rw-r--r--test/units/loopy2.service3
-rw-r--r--test/units/loopy3.service6
-rw-r--r--test/units/loopy4.service6
-rw-r--r--test/units/nomem.slice6
-rw-r--r--test/units/nomemleaf.service10
-rw-r--r--test/units/parent-deep.slice6
-rw-r--r--test/units/parent.slice6
-rw-r--r--test/units/sched_idle_bad.service7
-rw-r--r--test/units/sched_idle_ok.service7
-rw-r--r--test/units/sched_rr_bad.service9
-rw-r--r--test/units/sched_rr_change.service10
-rw-r--r--test/units/sched_rr_ok.service7
-rw-r--r--test/units/shutdown.target14
-rw-r--r--test/units/sockets.target12
-rw-r--r--test/units/son.service9
-rw-r--r--test/units/sysinit.target15
-rw-r--r--test/units/testsuite-01.service10
-rw-r--r--test/units/testsuite-02.service8
-rwxr-xr-xtest/units/testsuite-02.sh101
-rw-r--r--test/units/testsuite-03.service9
-rwxr-xr-xtest/units/testsuite-03.sh130
-rw-r--r--test/units/testsuite-04.service8
-rwxr-xr-xtest/units/testsuite-04.sh188
-rw-r--r--test/units/testsuite-05.service8
-rwxr-xr-xtest/units/testsuite-05.sh27
-rw-r--r--test/units/testsuite-06.service11
-rwxr-xr-xtest/units/testsuite-06.sh16
-rw-r--r--test/units/testsuite-07.service8
-rwxr-xr-xtest/units/testsuite-07.sh39
-rw-r--r--test/units/testsuite-08.service8
-rw-r--r--test/units/testsuite-09.service11
-rw-r--r--test/units/testsuite-10.service16
-rw-r--r--test/units/testsuite-11.service8
-rwxr-xr-xtest/units/testsuite-11.sh14
-rw-r--r--test/units/testsuite-12.service9
-rwxr-xr-xtest/units/testsuite-12.sh49
-rw-r--r--test/units/testsuite-13.service8
-rwxr-xr-xtest/units/testsuite-13.sh268
-rw-r--r--test/units/testsuite-14.service9
-rwxr-xr-xtest/units/testsuite-14.sh39
-rw-r--r--test/units/testsuite-15.service8
-rwxr-xr-xtest/units/testsuite-15.sh717
-rw-r--r--test/units/testsuite-16.service20
-rwxr-xr-xtest/units/testsuite-16.sh120
-rwxr-xr-xtest/units/testsuite-17.01.sh75
-rwxr-xr-xtest/units/testsuite-17.02.sh105
-rwxr-xr-xtest/units/testsuite-17.03.sh74
-rwxr-xr-xtest/units/testsuite-17.04.sh49
-rwxr-xr-xtest/units/testsuite-17.05.sh23
-rwxr-xr-xtest/units/testsuite-17.06.sh69
-rwxr-xr-xtest/units/testsuite-17.07.sh205
-rwxr-xr-xtest/units/testsuite-17.08.sh72
-rwxr-xr-xtest/units/testsuite-17.09.sh70
-rw-r--r--test/units/testsuite-17.service8
-rwxr-xr-xtest/units/testsuite-17.sh15
-rw-r--r--test/units/testsuite-18.service8
-rwxr-xr-xtest/units/testsuite-18.sh17
-rw-r--r--test/units/testsuite-19.service8
-rwxr-xr-xtest/units/testsuite-19.sh54
-rw-r--r--test/units/testsuite-20.service11
-rwxr-xr-xtest/units/testsuite-20.sh165
-rw-r--r--test/units/testsuite-21.service10
-rwxr-xr-xtest/units/testsuite-21.sh111
-rwxr-xr-xtest/units/testsuite-22.01.sh13
-rwxr-xr-xtest/units/testsuite-22.02.sh151
-rwxr-xr-xtest/units/testsuite-22.03.sh246
-rwxr-xr-xtest/units/testsuite-22.04.sh43
-rwxr-xr-xtest/units/testsuite-22.05.sh45
-rwxr-xr-xtest/units/testsuite-22.06.sh38
-rwxr-xr-xtest/units/testsuite-22.07.sh30
-rwxr-xr-xtest/units/testsuite-22.08.sh32
-rwxr-xr-xtest/units/testsuite-22.09.sh59
-rwxr-xr-xtest/units/testsuite-22.10.sh28
-rwxr-xr-xtest/units/testsuite-22.11.sh141
-rwxr-xr-xtest/units/testsuite-22.12.sh196
-rwxr-xr-xtest/units/testsuite-22.13.sh75
-rwxr-xr-xtest/units/testsuite-22.14.sh37
-rwxr-xr-xtest/units/testsuite-22.15.sh32
-rw-r--r--test/units/testsuite-22.service11
-rwxr-xr-xtest/units/testsuite-22.sh13
-rw-r--r--test/units/testsuite-23.service8
-rwxr-xr-xtest/units/testsuite-23.sh65
-rw-r--r--test/units/testsuite-24.service9
-rwxr-xr-xtest/units/testsuite-24.sh216
-rw-r--r--test/units/testsuite-25.service8
-rwxr-xr-xtest/units/testsuite-25.sh145
-rw-r--r--test/units/testsuite-26.service8
-rwxr-xr-xtest/units/testsuite-26.sh416
-rw-r--r--test/units/testsuite-27.service8
-rwxr-xr-xtest/units/testsuite-27.sh62
-rw-r--r--test/units/testsuite-28.service12
-rw-r--r--test/units/testsuite-29.service8
-rwxr-xr-xtest/units/testsuite-29.sh252
-rw-r--r--test/units/testsuite-30.service8
-rwxr-xr-xtest/units/testsuite-30.sh31
-rw-r--r--test/units/testsuite-31.service8
-rwxr-xr-xtest/units/testsuite-31.sh11
-rw-r--r--test/units/testsuite-32.service9
-rwxr-xr-xtest/units/testsuite-32.sh38
-rw-r--r--test/units/testsuite-33.service8
-rwxr-xr-xtest/units/testsuite-33.sh320
-rw-r--r--test/units/testsuite-34.service8
-rwxr-xr-xtest/units/testsuite-34.sh162
-rw-r--r--test/units/testsuite-35.service8
-rwxr-xr-xtest/units/testsuite-35.sh592
-rw-r--r--test/units/testsuite-36.service8
-rwxr-xr-xtest/units/testsuite-36.sh353
-rw-r--r--test/units/testsuite-37.service8
-rwxr-xr-xtest/units/testsuite-37.sh20
-rw-r--r--test/units/testsuite-38-sleep.service3
-rw-r--r--test/units/testsuite-38.service7
-rwxr-xr-xtest/units/testsuite-38.sh304
-rw-r--r--test/units/testsuite-39.service8
-rwxr-xr-xtest/units/testsuite-39.sh63
-rw-r--r--test/units/testsuite-40.service8
-rwxr-xr-xtest/units/testsuite-40.sh46
-rw-r--r--test/units/testsuite-41.service8
-rwxr-xr-xtest/units/testsuite-41.sh54
-rw-r--r--test/units/testsuite-42.service10
-rwxr-xr-xtest/units/testsuite-42.sh106
-rw-r--r--test/units/testsuite-43.service10
-rwxr-xr-xtest/units/testsuite-43.sh143
-rw-r--r--test/units/testsuite-44.service12
-rwxr-xr-xtest/units/testsuite-44.sh20
-rw-r--r--test/units/testsuite-45.service8
-rwxr-xr-xtest/units/testsuite-45.sh293
-rw-r--r--test/units/testsuite-46.service13
-rwxr-xr-xtest/units/testsuite-46.sh313
-rw-r--r--test/units/testsuite-47-repro.service8
-rwxr-xr-xtest/units/testsuite-47-repro.sh6
-rw-r--r--test/units/testsuite-47.service8
-rwxr-xr-xtest/units/testsuite-47.sh25
-rw-r--r--test/units/testsuite-48.service8
-rwxr-xr-xtest/units/testsuite-48.sh86
-rw-r--r--test/units/testsuite-49-namespaced.service13
-rw-r--r--test/units/testsuite-49-non-namespaced.service6
-rw-r--r--test/units/testsuite-49.service8
-rwxr-xr-xtest/units/testsuite-49.sh44
-rw-r--r--test/units/testsuite-50.service8
-rwxr-xr-xtest/units/testsuite-50.sh405
-rw-r--r--test/units/testsuite-51-repro-1.service10
-rw-r--r--test/units/testsuite-51-repro-2.service10
-rw-r--r--test/units/testsuite-51-repro-3.service10
-rw-r--r--test/units/testsuite-51.service8
-rwxr-xr-xtest/units/testsuite-51.sh15
-rw-r--r--test/units/testsuite-52.service7
-rwxr-xr-xtest/units/testsuite-52.sh13
-rw-r--r--test/units/testsuite-53.service8
-rwxr-xr-xtest/units/testsuite-53.sh31
-rw-r--r--test/units/testsuite-54.service8
-rwxr-xr-xtest/units/testsuite-54.sh146
-rw-r--r--test/units/testsuite-55-testbloat.service10
-rw-r--r--test/units/testsuite-55-testchill.service8
-rw-r--r--test/units/testsuite-55-testmunch.service8
-rw-r--r--test/units/testsuite-55-workload.slice11
-rw-r--r--test/units/testsuite-55.service10
-rwxr-xr-xtest/units/testsuite-55.sh179
-rw-r--r--test/units/testsuite-56.service6
-rwxr-xr-xtest/units/testsuite-56.sh78
-rw-r--r--test/units/testsuite-57-binds-to.service10
-rw-r--r--test/units/testsuite-57-bound-by.service6
-rw-r--r--test/units/testsuite-57-fail.service7
-rw-r--r--test/units/testsuite-57-prop-stop-one.service10
-rw-r--r--test/units/testsuite-57-prop-stop-two.service6
-rw-r--r--test/units/testsuite-57-retry-fail.service9
-rw-r--r--test/units/testsuite-57-retry-upheld.service10
-rw-r--r--test/units/testsuite-57-retry-uphold.service7
-rw-r--r--test/units/testsuite-57-short-lived.service11
-rwxr-xr-xtest/units/testsuite-57-short-lived.sh18
-rw-r--r--test/units/testsuite-57-success.service7
-rw-r--r--test/units/testsuite-57-uphold.service7
-rw-r--r--test/units/testsuite-57.service8
-rwxr-xr-xtest/units/testsuite-57.sh96
-rw-r--r--test/units/testsuite-58.service7
-rwxr-xr-xtest/units/testsuite-58.sh937
-rw-r--r--test/units/testsuite-59.service7
-rwxr-xr-xtest/units/testsuite-59.sh90
-rw-r--r--test/units/testsuite-60.service8
-rwxr-xr-xtest/units/testsuite-60.sh311
-rw-r--r--test/units/testsuite-61.service8
-rwxr-xr-xtest/units/testsuite-61.sh10
-rw-r--r--test/units/testsuite-62-1.service9
-rw-r--r--test/units/testsuite-62-2.service10
-rw-r--r--test/units/testsuite-62-3.service10
-rw-r--r--test/units/testsuite-62-4.service10
-rw-r--r--test/units/testsuite-62-5.service11
-rw-r--r--test/units/testsuite-62.service8
-rwxr-xr-xtest/units/testsuite-62.sh65
-rw-r--r--test/units/testsuite-63.service8
-rwxr-xr-xtest/units/testsuite-63.sh85
-rw-r--r--test/units/testsuite-64.service8
-rwxr-xr-xtest/units/testsuite-64.sh1017
-rw-r--r--test/units/testsuite-65.service8
-rwxr-xr-xtest/units/testsuite-65.sh883
-rw-r--r--test/units/testsuite-66-deviceisolation.service10
-rw-r--r--test/units/testsuite-66.service8
-rwxr-xr-xtest/units/testsuite-66.sh26
-rw-r--r--test/units/testsuite-67.service9
-rwxr-xr-xtest/units/testsuite-67.sh119
-rw-r--r--test/units/testsuite-68.service7
-rwxr-xr-xtest/units/testsuite-68.sh217
-rw-r--r--test/units/testsuite-69.service7
-rw-r--r--test/units/testsuite-70.service7
-rwxr-xr-xtest/units/testsuite-70.sh234
-rw-r--r--test/units/testsuite-71.service8
-rwxr-xr-xtest/units/testsuite-71.sh98
-rw-r--r--test/units/testsuite-72.service8
-rwxr-xr-xtest/units/testsuite-72.sh170
-rw-r--r--test/units/testsuite-73.service8
-rwxr-xr-xtest/units/testsuite-73.sh407
-rwxr-xr-xtest/units/testsuite-74.cgls.sh27
-rwxr-xr-xtest/units/testsuite-74.cgtop.sh32
-rwxr-xr-xtest/units/testsuite-74.delta.sh59
-rwxr-xr-xtest/units/testsuite-74.firstboot.sh188
-rw-r--r--test/units/testsuite-74.service8
-rwxr-xr-xtest/units/testsuite-74.sh14
-rw-r--r--test/units/testsuite-75.service10
-rwxr-xr-xtest/units/testsuite-75.sh320
-rw-r--r--test/units/testsuite-76.service8
-rwxr-xr-xtest/units/testsuite-76.sh39
-rw-r--r--test/units/testsuite.target7
-rw-r--r--test/units/timers.target15
-rw-r--r--test/units/unit-.service.d/10-override.conf3
-rw-r--r--test/units/unit-with-.service.d/20-override.conf3
-rw-r--r--test/units/unit-with-multiple-.service.d/20-override.conf3
-rw-r--r--test/units/unit-with-multiple-.service.d/30-override.conf3
-rw-r--r--test/units/unit-with-multiple-dashes.service7
-rw-r--r--test/units/unit-with-multiple-dashes.service.d/10-override.conf3
256 files changed, 15881 insertions, 0 deletions
diff --git a/test/units/a-conj.service b/test/units/a-conj.service
new file mode 100644
index 0000000..3a7c9e1
--- /dev/null
+++ b/test/units/a-conj.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=A conjugate
+Requires=a.service
+After=a.service
+Before=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/a.service b/test/units/a.service
new file mode 100644
index 0000000..ec5d059
--- /dev/null
+++ b/test/units/a.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=A
+Requires=b.service
+Before=b.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/assert.sh b/test/units/assert.sh
new file mode 100755
index 0000000..2f4d93a
--- /dev/null
+++ b/test/units/assert.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# utility functions for shell tests
+
+assert_true() {(
+ set +ex
+
+ local rc
+
+ "$@"
+ rc=$?
+ if [[ $rc -ne 0 ]]; then
+ echo "FAIL: command '$*' failed with exit code $rc" >&2
+ exit 1
+ fi
+)}
+
+
+assert_eq() {(
+ set +ex
+
+ if [[ "${1?}" != "${2?}" ]]; then
+ echo "FAIL: expected: '$2' actual: '$1'" >&2
+ exit 1
+ fi
+)}
+
+assert_in() {(
+ set +ex
+
+ if ! [[ "${2?}" =~ ${1?} ]]; then
+ echo "FAIL: '$1' not found in:" >&2
+ echo "$2" >&2
+ exit 1
+ fi
+)}
+
+assert_not_in() {(
+ set +ex
+
+ if [[ "${2?}" =~ ${1?} ]]; then
+ echo "FAIL: '$1' found in:" >&2
+ echo "$2" >&2
+ exit 1
+ fi
+)}
+
+assert_rc() {(
+ set +ex
+
+ local rc exp="${1?}"
+
+ shift
+ "$@"
+ rc=$?
+ assert_eq "$rc" "$exp"
+)}
diff --git a/test/units/autorelabel.service b/test/units/autorelabel.service
new file mode 100644
index 0000000..1da1002
--- /dev/null
+++ b/test/units/autorelabel.service
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Relabel all filesystems
+DefaultDependencies=no
+Requires=local-fs.target
+Conflicts=shutdown.target
+After=local-fs.target
+Before=sysinit.target shutdown.target
+ConditionSecurity=selinux
+ConditionPathExists=|/.autorelabel
+
+[Service]
+ExecStart=sh -x -c 'echo 0 >/sys/fs/selinux/enforce && fixfiles -f -F relabel && rm /.autorelabel && systemctl --force reboot'
+Type=oneshot
+TimeoutSec=0
+RemainAfterExit=yes
+
+[Install]
+WantedBy=basic.target
diff --git a/test/units/b.service b/test/units/b.service
new file mode 100644
index 0000000..4503cf3
--- /dev/null
+++ b/test/units/b.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=B
+Wants=f.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/basic.target b/test/units/basic.target
new file mode 100644
index 0000000..d8cdd5a
--- /dev/null
+++ b/test/units/basic.target
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Basic System
+Documentation=man:systemd.special(7)
+Requires=sysinit.target
+Wants=sockets.target timers.target paths.target slices.target
+After=sysinit.target sockets.target paths.target slices.target tmp.mount
+
+# We support /var, /tmp, /var/tmp, being on NFS, but we don't pull in
+# remote-fs.target by default, hence pull them in explicitly here. Note that we
+# require /var and /var/tmp, but only add a Wants= type dependency on /tmp, as
+# we support that unit being masked, and this should not be considered an error.
+RequiresMountsFor=/var /var/tmp
+Wants=tmp.mount
diff --git a/test/units/c.service b/test/units/c.service
new file mode 100644
index 0000000..a1ce28c
--- /dev/null
+++ b/test/units/c.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=C
+Requires=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/d.service b/test/units/d.service
new file mode 100644
index 0000000..8202325
--- /dev/null
+++ b/test/units/d.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=D:Cyclic
+After=b.service
+Before=a.service
+Requires=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/daughter.service b/test/units/daughter.service
new file mode 100644
index 0000000..385fbed
--- /dev/null
+++ b/test/units/daughter.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Daughter Service
+
+[Service]
+Slice=parent.slice
+Type=oneshot
+ExecStart=/bin/true
+CPUAccounting=true
diff --git a/test/units/dml-discard-empty.service b/test/units/dml-discard-empty.service
new file mode 100644
index 0000000..720c1da
--- /dev/null
+++ b/test/units/dml-discard-empty.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML discard empty service
+
+[Service]
+Slice=dml-discard.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/units/dml-discard-set-ml.service b/test/units/dml-discard-set-ml.service
new file mode 100644
index 0000000..93246ac
--- /dev/null
+++ b/test/units/dml-discard-set-ml.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML discard set ml service
+
+[Service]
+Slice=dml-discard.slice
+Type=oneshot
+ExecStart=/bin/true
+MemoryLow=15
diff --git a/test/units/dml-discard.slice b/test/units/dml-discard.slice
new file mode 100644
index 0000000..dc8a397
--- /dev/null
+++ b/test/units/dml-discard.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML discard slice
+
+[Slice]
+DefaultMemoryLow=
diff --git a/test/units/dml-override-empty.service b/test/units/dml-override-empty.service
new file mode 100644
index 0000000..ac96de0
--- /dev/null
+++ b/test/units/dml-override-empty.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML override empty service
+
+[Service]
+Slice=dml-override.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/units/dml-override.slice b/test/units/dml-override.slice
new file mode 100644
index 0000000..ac664d1
--- /dev/null
+++ b/test/units/dml-override.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML override slice
+
+[Slice]
+DefaultMemoryLow=10
diff --git a/test/units/dml-passthrough-empty.service b/test/units/dml-passthrough-empty.service
new file mode 100644
index 0000000..1e1ba34
--- /dev/null
+++ b/test/units/dml-passthrough-empty.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML passthrough empty service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/units/dml-passthrough-set-dml.service b/test/units/dml-passthrough-set-dml.service
new file mode 100644
index 0000000..9a15311
--- /dev/null
+++ b/test/units/dml-passthrough-set-dml.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML passthrough set DML service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
+DefaultMemoryLow=15
diff --git a/test/units/dml-passthrough-set-ml.service b/test/units/dml-passthrough-set-ml.service
new file mode 100644
index 0000000..65083bc
--- /dev/null
+++ b/test/units/dml-passthrough-set-ml.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML passthrough set ML service
+
+[Service]
+Slice=dml-passthrough.slice
+Type=oneshot
+ExecStart=/bin/true
+MemoryLow=0
diff --git a/test/units/dml-passthrough.slice b/test/units/dml-passthrough.slice
new file mode 100644
index 0000000..1c8769d
--- /dev/null
+++ b/test/units/dml-passthrough.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML passthrough slice
+
+[Slice]
+MemoryLow=100
diff --git a/test/units/dml.slice b/test/units/dml.slice
new file mode 100644
index 0000000..8e00e7f
--- /dev/null
+++ b/test/units/dml.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=DML slice
+
+[Slice]
+DefaultMemoryLow=50
diff --git a/test/units/e.service b/test/units/e.service
new file mode 100644
index 0000000..5bbcde2
--- /dev/null
+++ b/test/units/e.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=E:Cyclic
+After=b.service
+Before=a.service
+Wants=a.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/end.service b/test/units/end.service
new file mode 100644
index 0000000..3626741
--- /dev/null
+++ b/test/units/end.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=End the test
+After=testsuite.target
+OnFailure=poweroff.target
+OnFailureJobMode=replace-irreversibly
+
+[Service]
+Type=oneshot
+ExecStart=/bin/sh -x -c 'systemctl poweroff --no-block'
+TimeoutStartSec=5m
diff --git a/test/units/f.service b/test/units/f.service
new file mode 100644
index 0000000..ca20053
--- /dev/null
+++ b/test/units/f.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=F
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/g.service b/test/units/g.service
new file mode 100644
index 0000000..5fd794d
--- /dev/null
+++ b/test/units/g.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=G
+Conflicts=e.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/grandchild.service b/test/units/grandchild.service
new file mode 100644
index 0000000..4fe77b4
--- /dev/null
+++ b/test/units/grandchild.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Grandchild Service
+
+[Service]
+Slice=parent-deep.slice
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/units/h.service b/test/units/h.service
new file mode 100644
index 0000000..5361d42
--- /dev/null
+++ b/test/units/h.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=H
+Wants=g.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/i.service b/test/units/i.service
new file mode 100644
index 0000000..2b5e821
--- /dev/null
+++ b/test/units/i.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=I
+Conflicts=a.service d.service
+Wants=b.service
+After=b.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/loopy.service b/test/units/loopy.service
new file mode 100644
index 0000000..7fc0e42
--- /dev/null
+++ b/test/units/loopy.service
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/loopy.service.d/compat.conf b/test/units/loopy.service.d/compat.conf
new file mode 100644
index 0000000..53d213c
--- /dev/null
+++ b/test/units/loopy.service.d/compat.conf
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+BindsTo=loopy2.service
+
+[Install]
+Also=loopy2.service
diff --git a/test/units/loopy2.service b/test/units/loopy2.service
new file mode 100644
index 0000000..7fc0e42
--- /dev/null
+++ b/test/units/loopy2.service
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/loopy3.service b/test/units/loopy3.service
new file mode 100644
index 0000000..b2af20a
--- /dev/null
+++ b/test/units/loopy3.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=/bin/true
+
+[Unit]
+Conflicts=loopy4.service
diff --git a/test/units/loopy4.service b/test/units/loopy4.service
new file mode 100644
index 0000000..b2af20a
--- /dev/null
+++ b/test/units/loopy4.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=/bin/true
+
+[Unit]
+Conflicts=loopy4.service
diff --git a/test/units/nomem.slice b/test/units/nomem.slice
new file mode 100644
index 0000000..f4837da
--- /dev/null
+++ b/test/units/nomem.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Nomem Parent Slice
+
+[Slice]
+DisableControllers=memory
diff --git a/test/units/nomemleaf.service b/test/units/nomemleaf.service
new file mode 100644
index 0000000..14ce5ad
--- /dev/null
+++ b/test/units/nomemleaf.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Nomem Leaf Service
+
+[Service]
+Slice=nomem.slice
+Type=oneshot
+ExecStart=/bin/true
+IOWeight=200
+MemoryAccounting=true
diff --git a/test/units/parent-deep.slice b/test/units/parent-deep.slice
new file mode 100644
index 0000000..983ed65
--- /dev/null
+++ b/test/units/parent-deep.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Deeper Parent Slice
+
+[Slice]
+MemoryLimit=3G
diff --git a/test/units/parent.slice b/test/units/parent.slice
new file mode 100644
index 0000000..f49530b
--- /dev/null
+++ b/test/units/parent.slice
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Parent Slice
+
+[Slice]
+IOWeight=200
diff --git a/test/units/sched_idle_bad.service b/test/units/sched_idle_bad.service
new file mode 100644
index 0000000..be8f1c2
--- /dev/null
+++ b/test/units/sched_idle_bad.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Bad sched priority for Idle
+
+[Service]
+ExecStart=/bin/true
+CPUSchedulingPriority=1
diff --git a/test/units/sched_idle_ok.service b/test/units/sched_idle_ok.service
new file mode 100644
index 0000000..5a1d809
--- /dev/null
+++ b/test/units/sched_idle_ok.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Sched idle with prio 0
+
+[Service]
+ExecStart=/bin/true
+CPUSchedulingPriority=0
diff --git a/test/units/sched_rr_bad.service b/test/units/sched_rr_bad.service
new file mode 100644
index 0000000..b51b868
--- /dev/null
+++ b/test/units/sched_rr_bad.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Bad sched priority for RR
+
+[Service]
+ExecStart=/bin/true
+CPUSchedulingPriority=-1
+CPUSchedulingPriority=100
+CPUSchedulingPolicy=rr
diff --git a/test/units/sched_rr_change.service b/test/units/sched_rr_change.service
new file mode 100644
index 0000000..6ae1feb
--- /dev/null
+++ b/test/units/sched_rr_change.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Change prio
+
+[Service]
+ExecStart=/bin/true
+CPUSchedulingPriority=1
+CPUSchedulingPriority=2
+CPUSchedulingPriority=99
+CPUSchedulingPolicy=rr
diff --git a/test/units/sched_rr_ok.service b/test/units/sched_rr_ok.service
new file mode 100644
index 0000000..00b9822
--- /dev/null
+++ b/test/units/sched_rr_ok.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Default prio for RR
+
+[Service]
+ExecStart=/bin/true
+CPUSchedulingPolicy=rr
diff --git a/test/units/shutdown.target b/test/units/shutdown.target
new file mode 100644
index 0000000..582ae6b
--- /dev/null
+++ b/test/units/shutdown.target
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Shutdown
+Documentation=man:systemd.special(7)
+DefaultDependencies=no
+RefuseManualStart=yes
diff --git a/test/units/sockets.target b/test/units/sockets.target
new file mode 100644
index 0000000..c6e20d7
--- /dev/null
+++ b/test/units/sockets.target
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Sockets
+Documentation=man:systemd.special(7)
diff --git a/test/units/son.service b/test/units/son.service
new file mode 100644
index 0000000..2059118
--- /dev/null
+++ b/test/units/son.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Son Service
+
+[Service]
+Slice=parent.slice
+Type=oneshot
+ExecStart=/bin/true
+CPUShares=100
diff --git a/test/units/sysinit.target b/test/units/sysinit.target
new file mode 100644
index 0000000..eed3d16
--- /dev/null
+++ b/test/units/sysinit.target
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=System Initialization
+Documentation=man:systemd.special(7)
+Conflicts=emergency.service emergency.target
+Wants=local-fs.target swap.target
+After=local-fs.target swap.target emergency.service emergency.target
diff --git a/test/units/testsuite-01.service b/test/units/testsuite-01.service
new file mode 100644
index 0000000..1c81efc
--- /dev/null
+++ b/test/units/testsuite-01.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-01-BASIC
+After=multi-user.target
+Wants=systemd-resolved.service systemd-networkd.service
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=sh -e -x -c 'systemctl --state=failed --no-legend --no-pager >/failed ; systemctl daemon-reload ; echo OK >/testok'
+Type=oneshot
diff --git a/test/units/testsuite-02.service b/test/units/testsuite-02.service
new file mode 100644
index 0000000..dea2c4f
--- /dev/null
+++ b/test/units/testsuite-02.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-02-UNITTESTS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-02.sh b/test/units/testsuite-02.sh
new file mode 100755
index 0000000..b7406d4
--- /dev/null
+++ b/test/units/testsuite-02.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+NPROC=$(nproc)
+MAX_QUEUE_SIZE=${NPROC:-2}
+TESTS_GLOB=${TESTS_GLOB:-test-*}
+mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
+
+# Reset state
+rm -fv /failed-tests /skipped-tests /skipped
+
+if ! systemd-detect-virt -qc; then
+ # Make sure ping works for unprivileged users (for test-bpf-firewall)
+ sysctl net.ipv4.ping_group_range="0 2147483647"
+fi
+
+# Check & report test results
+# Arguments:
+# $1: test path
+# $2: test exit code
+function report_result() {
+ if [[ $# -ne 2 ]]; then
+ echo >&2 "check_result: missing arguments"
+ exit 1
+ fi
+
+ local name="${1##*/}"
+ local ret=$2
+
+ if [[ $ret -ne 0 && $ret != 77 ]]; then
+ echo "$name failed with $ret"
+ echo "$name" >>/failed-tests
+ {
+ echo "--- $name begin ---"
+ cat "/$name.log"
+ echo "--- $name end ---"
+ } >>/failed
+ elif [[ $ret == 77 ]]; then
+ echo "$name skipped"
+ echo "$name" >>/skipped-tests
+ {
+ echo "--- $name begin ---"
+ cat "/$name.log"
+ echo "--- $name end ---"
+ } >>/skipped
+ else
+ echo "$name OK"
+ echo "$name" >>/testok
+ fi
+
+ systemd-cat echo "--- $name ---"
+ systemd-cat cat "/$name.log"
+}
+
+set +x
+# Associative array for running tasks, where running[test-path]=PID
+declare -A running=()
+for task in "${TEST_LIST[@]}"; do
+ # If there's MAX_QUEUE_SIZE running tasks, keep checking the running queue
+ # until one of the tasks finishes, so we can replace it.
+ while [[ ${#running[@]} -ge $MAX_QUEUE_SIZE ]]; do
+ for key in "${!running[@]}"; do
+ if ! kill -0 "${running[$key]}" &>/dev/null; then
+ # Task has finished, report its result and drop it from the queue
+ wait "${running[$key]}" && ec=0 || ec=$?
+ report_result "$key" $ec
+ unset "running[$key]"
+ # Break from inner for loop and outer while loop to skip
+ # the sleep below when we find a free slot in the queue
+ break 2
+ fi
+ done
+
+ # Precisely* calculated constant to keep the spinlock from burning the CPU(s)
+ sleep 0.01
+ done
+
+ if [[ -x $task ]]; then
+ echo "Executing test '$task'"
+ log_file="/${task##*/}.log"
+ $task &>"$log_file" &
+ running[$task]=$!
+ fi
+done
+
+# Wait for remaining running tasks
+for key in "${!running[@]}"; do
+ echo "Waiting for test '$key' to finish"
+ wait ${running[$key]} && ec=0 || ec=$?
+ report_result "$key" $ec
+ unset "running[$key]"
+done
+
+set -x
+
+# Test logs are sometimes lost, as the system shuts down immediately after
+journalctl --sync
+
+exit 0
diff --git a/test/units/testsuite-03.service b/test/units/testsuite-03.service
new file mode 100644
index 0000000..836f962
--- /dev/null
+++ b/test/units/testsuite-03.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-03-JOBS
+After=multi-user.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-03.sh b/test/units/testsuite-03.sh
new file mode 100755
index 0000000..6ed7d86
--- /dev/null
+++ b/test/units/testsuite-03.sh
@@ -0,0 +1,130 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Simple test for that daemon-reexec works in container.
+# See: https://github.com/systemd/systemd/pull/23883
+systemctl daemon-reexec
+
+# Test merging of a --job-mode=ignore-dependencies job into a previously
+# installed job.
+
+systemctl start --no-block hello-after-sleep.target
+
+systemctl list-jobs >/root/list-jobs.txt
+while ! grep 'sleep\.service.*running' /root/list-jobs.txt; do
+ systemctl list-jobs >/root/list-jobs.txt
+done
+
+grep 'hello\.service.*waiting' /root/list-jobs.txt
+
+# This is supposed to finish quickly, not wait for sleep to finish.
+START_SEC=$(date -u '+%s')
+systemctl start --job-mode=ignore-dependencies hello
+END_SEC=$(date -u '+%s')
+ELAPSED=$((END_SEC-START_SEC))
+
+test "$ELAPSED" -lt 3
+
+# sleep should still be running, hello not.
+systemctl list-jobs >/root/list-jobs.txt
+grep 'sleep\.service.*running' /root/list-jobs.txt
+grep 'hello\.service' /root/list-jobs.txt && exit 1
+systemctl stop sleep.service hello-after-sleep.target
+
+# Some basic testing that --show-transaction does something useful
+(! systemctl is-active systemd-importd)
+systemctl -T start systemd-importd
+systemctl is-active systemd-importd
+systemctl --show-transaction stop systemd-importd
+(! systemctl is-active systemd-importd)
+
+# Test for a crash when enqueuing a JOB_NOP when other job already exists
+systemctl start --no-block hello-after-sleep.target
+# hello.service should still be waiting, so these try-restarts will collapse
+# into NOPs.
+systemctl try-restart --job-mode=fail hello.service
+systemctl try-restart hello.service
+systemctl stop hello.service sleep.service hello-after-sleep.target
+
+# TODO: add more job queueing/merging tests here.
+
+# Test that restart propagates to activating units
+systemctl -T --no-block start always-activating.service
+systemctl list-jobs | grep 'always-activating.service'
+ACTIVATING_ID_PRE=$(systemctl show -P InvocationID always-activating.service)
+systemctl -T start always-activating.socket # Wait for the socket to come up
+systemctl -T restart always-activating.socket
+ACTIVATING_ID_POST=$(systemctl show -P InvocationID always-activating.service)
+[ "$ACTIVATING_ID_PRE" != "$ACTIVATING_ID_POST" ] || exit 1
+
+# Test for irreversible jobs
+systemctl start unstoppable.service
+
+# This is expected to fail with 'job cancelled'
+systemctl stop unstoppable.service && exit 1
+# But this should succeed
+systemctl stop --job-mode=replace-irreversibly unstoppable.service
+
+# We're going to shutdown soon. Let's see if it succeeds when
+# there's an active service that tries to be unstoppable.
+# Shutdown of the container/VM will hang if not.
+systemctl start unstoppable.service
+
+# Test waiting for a started units to terminate again
+cat <<EOF >/run/systemd/system/wait2.service
+[Unit]
+Description=Wait for 2 seconds
+[Service]
+ExecStart=/bin/sh -ec 'sleep 2'
+EOF
+cat <<EOF >/run/systemd/system/wait5fail.service
+[Unit]
+Description=Wait for 5 seconds and fail
+[Service]
+ExecStart=/bin/sh -ec 'sleep 5; false'
+EOF
+
+# wait2 succeeds
+START_SEC=$(date -u '+%s')
+systemctl start --wait wait2.service
+END_SEC=$(date -u '+%s')
+ELAPSED=$((END_SEC-START_SEC))
+[[ "$ELAPSED" -ge 2 ]] && [[ "$ELAPSED" -le 4 ]] || exit 1
+
+# wait5fail fails, so systemctl should fail
+START_SEC=$(date -u '+%s')
+(! systemctl start --wait wait2.service wait5fail.service)
+END_SEC=$(date -u '+%s')
+ELAPSED=$((END_SEC-START_SEC))
+[[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1
+
+# Test time-limited scopes
+START_SEC=$(date -u '+%s')
+set +e
+systemd-run --scope --property=RuntimeMaxSec=3s sleep 10
+RESULT=$?
+END_SEC=$(date -u '+%s')
+ELAPSED=$((END_SEC-START_SEC))
+[[ "$ELAPSED" -ge 3 ]] && [[ "$ELAPSED" -le 5 ]] || exit 1
+[[ "$RESULT" -ne 0 ]] || exit 1
+
+# Test transactions with cycles
+# Provides coverage for issues like https://github.com/systemd/systemd/issues/26872
+for i in {0..19}; do
+ cat >"/run/systemd/system/transaction-cycle$i.service" <<EOF
+[Unit]
+After=transaction-cycle$(((i + 1) % 20)).service
+Requires=transaction-cycle$(((i + 1) % 20)).service
+
+[Service]
+ExecStart=true
+EOF
+done
+systemctl daemon-reload
+for i in {0..19}; do
+ systemctl start "transaction-cycle$i.service"
+done
+
+touch /testok
diff --git a/test/units/testsuite-04.service b/test/units/testsuite-04.service
new file mode 100644
index 0000000..63a0104
--- /dev/null
+++ b/test/units/testsuite-04.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-04-JOURNAL
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-04.sh b/test/units/testsuite-04.sh
new file mode 100755
index 0000000..94a0021
--- /dev/null
+++ b/test/units/testsuite-04.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Limit the maximum journal size
+trap "journalctl --rotate --vacuum-size=16M" EXIT
+
+# Rotation/flush test, see https://github.com/systemd/systemd/issues/19895
+journalctl --relinquish-var
+[[ "$(systemd-detect-virt -v)" == "qemu" ]] && ITERATIONS=10 || ITERATIONS=50
+for ((i = 0; i < ITERATIONS; i++)); do
+ dd if=/dev/urandom bs=1M count=1 | base64 | systemd-cat
+done
+journalctl --rotate
+journalctl --flush
+journalctl --sync
+
+# Reset the ratelimit buckets for the subsequent tests below.
+systemctl restart systemd-journald
+
+# Test stdout stream
+
+# Skip empty lines
+ID=$(journalctl --new-id128 | sed -n 2p)
+: >/expected
+printf $'\n\n\n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+: >/expected
+printf $'<5>\n<6>\n<7>\n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# Remove trailing spaces
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "Trailing spaces\n">/expected
+printf $'<5>Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "Trailing spaces\n">/expected
+printf $'Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# Don't remove leading spaces
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $' \t Leading spaces\n'>/expected
+printf $'<5> \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $' \t Leading spaces\n'>/expected
+printf $' \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# --output-fields restricts output
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $'foo' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/output
+[[ $(grep -c . /output) -eq 6 ]]
+grep -q '^__CURSOR=' /output
+grep -q '^MESSAGE=foo$' /output
+grep -q '^PRIORITY=6$' /output
+(! grep '^FOO=' /output)
+(! grep '^SYSLOG_FACILITY=' /output)
+
+# '-b all' negates earlier use of -b (-b and -m are otherwise exclusive)
+journalctl -b -1 -b all -m >/dev/null
+
+# -b always behaves like -b0
+journalctl -q -b-1 -b0 | head -1 >/expected
+journalctl -q -b-1 -b | head -1 >/output
+cmp /expected /output
+# ... even when another option follows (both of these should fail due to -m)
+{ journalctl -ball -b0 -m 2>&1 || :; } | head -1 >/expected
+{ journalctl -ball -b -m 2>&1 || :; } | head -1 >/output
+cmp /expected /output
+
+# https://github.com/systemd/systemd/issues/13708
+ID=$(systemd-id128 new)
+systemd-cat -t "$ID" bash -c 'echo parent; (echo child) & wait' &
+PID=$!
+wait $PID
+journalctl --sync
+# We can drop this grep when https://github.com/systemd/systemd/issues/13937
+# has a fix.
+journalctl -b -o export -t "$ID" --output-fields=_PID | grep '^_PID=' >/output
+[[ $(grep -c . /output) -eq 2 ]]
+grep -q "^_PID=$PID" /output
+grep -vq "^_PID=$PID" /output
+
+# https://github.com/systemd/systemd/issues/15654
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "This will\nusually fail\nand be truncated\n">/expected
+systemd-cat -t "$ID" /bin/sh -c 'env echo -n "This will";echo;env echo -n "usually fail";echo;env echo -n "and be truncated";echo;'
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_TRANSPORT | grep -Pc "^stdout$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_LINE_BREAK | grep -Pc "^pid-change$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_PID | sort -u | grep -c "^.*$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=MESSAGE | grep -Pc "^(This will|usually fail|and be truncated)$") -eq 3 ]]
+
+# test that LogLevelMax can also suppress logging about services, not only by services
+systemctl start silent-success
+journalctl --sync
+[[ -z "$(journalctl -b -q -u silent-success.service)" ]]
+
+# Exercise the matching machinery
+SYSTEMD_LOG_LEVEL=debug journalctl -b -n 1 /dev/null /dev/zero /dev/null /dev/null /dev/null
+journalctl -b -n 1 /bin/true /bin/false
+journalctl -b -n 1 /bin/true + /bin/false
+journalctl -b -n 1 -r --unit "systemd*"
+
+systemd-run --user -M "testuser@.host" /bin/echo hello
+journalctl --sync
+journalctl -b -n 1 -r --user-unit "*"
+
+(! journalctl -b /dev/lets-hope-this-doesnt-exist)
+(! journalctl -b /dev/null /dev/zero /dev/this-also-shouldnt-exist)
+(! journalctl -b --unit "this-unit-should-not-exist*")
+
+# Facilities & priorities
+journalctl --facility help
+journalctl --facility kern -n 1
+journalctl --facility syslog --priority 0..3 -n 1
+journalctl --facility syslog --priority 3..0 -n 1
+journalctl --facility user --priority 0..0 -n 1
+journalctl --facility daemon --priority warning -n 1
+journalctl --facility daemon --priority warning..info -n 1
+journalctl --facility daemon --priority notice..crit -n 1
+journalctl --facility daemon --priority 5..crit -n 1
+
+(! journalctl --facility hopefully-an-unknown-facility)
+(! journalctl --priority hello-world)
+(! journalctl --priority 0..128)
+(! journalctl --priority 0..systemd)
+
+# Other options
+journalctl --disk-usage
+journalctl --dmesg -n 1
+journalctl --fields
+journalctl --list-boots
+journalctl --update-catalog
+journalctl --list-catalog
+
+# Add new tests before here, the journald restarts below
+# may make tests flappy.
+
+# Don't lose streams on restart
+systemctl start forever-print-hola
+sleep 3
+systemctl restart systemd-journald
+sleep 3
+systemctl stop forever-print-hola
+[[ ! -f "/i-lose-my-logs" ]]
+
+# https://github.com/systemd/systemd/issues/4408
+rm -f /i-lose-my-logs
+systemctl start forever-print-hola
+sleep 3
+systemctl kill --signal=SIGKILL systemd-journald
+sleep 3
+[[ ! -f "/i-lose-my-logs" ]]
+systemctl stop forever-print-hola
+
+set +o pipefail
+# https://github.com/systemd/systemd/issues/15528
+journalctl --follow --file=/var/log/journal/*/* | head -n1 | grep .
+# https://github.com/systemd/systemd/issues/24565
+journalctl --follow --merge | head -n1 | grep .
+set -o pipefail
+
+touch /testok
diff --git a/test/units/testsuite-05.service b/test/units/testsuite-05.service
new file mode 100644
index 0000000..ab72d8f
--- /dev/null
+++ b/test/units/testsuite-05.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-05-RLIMITS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-05.sh b/test/units/testsuite-05.sh
new file mode 100755
index 0000000..870845d
--- /dev/null
+++ b/test/units/testsuite-05.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+P=/run/systemd/system.conf.d
+mkdir $P
+
+cat >$P/rlimits.conf <<EOF
+[Manager]
+DefaultLimitNOFILE=10000:16384
+EOF
+
+systemctl daemon-reload
+
+[[ "$(systemctl show -P DefaultLimitNOFILESoft)" = "10000" ]]
+[[ "$(systemctl show -P DefaultLimitNOFILE)" = "16384" ]]
+
+[[ "$(systemctl show -P LimitNOFILESoft testsuite-05.service)" = "10000" ]]
+[[ "$(systemctl show -P LimitNOFILE testsuite-05.service)" = "16384" ]]
+
+# shellcheck disable=SC2016
+systemd-run --wait -t bash -c '[[ "$(ulimit -n -S)" = "10000" ]]'
+# shellcheck disable=SC2016
+systemd-run --wait -t bash -c '[[ "$(ulimit -n -H)" = "16384" ]]'
+
+touch /testok
diff --git a/test/units/testsuite-06.service b/test/units/testsuite-06.service
new file mode 100644
index 0000000..b91f93c
--- /dev/null
+++ b/test/units/testsuite-06.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-06-SELINUX
+
+Requires=load-systemd-test-module.service
+After=load-systemd-test-module.service
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-06.sh b/test/units/testsuite-06.sh
new file mode 100755
index 0000000..c57d8b9
--- /dev/null
+++ b/test/units/testsuite-06.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+echo 1 >/sys/fs/selinux/enforce || {
+ echo "Can't make selinux enforcing, skipping test"
+ touch /testok
+ exit
+}
+
+runcon -t systemd_test_start_t systemctl start hola
+runcon -t systemd_test_reload_t systemctl reload hola
+runcon -t systemd_test_stop_t systemctl stop hola
+
+touch /testok
diff --git a/test/units/testsuite-07.service b/test/units/testsuite-07.service
new file mode 100644
index 0000000..c478e12
--- /dev/null
+++ b/test/units/testsuite-07.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-07-ISSUE-1981
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-07.sh b/test/units/testsuite-07.sh
new file mode 100755
index 0000000..95ebe38
--- /dev/null
+++ b/test/units/testsuite-07.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+cat >/lib/systemd/system/my.service <<EOF
+[Service]
+Type=oneshot
+ExecStartPre=sh -c 'test "\$TRIGGER_UNIT" = my.timer'
+ExecStartPre=sh -c 'test -n "\$TRIGGER_TIMER_REALTIME_USEC"'
+ExecStartPre=sh -c 'test -n "\$TRIGGER_TIMER_MONOTONIC_USEC"'
+ExecStart=/bin/echo Timer runs me
+EOF
+
+cat >/lib/systemd/system/my.timer <<EOF
+[Timer]
+OnBootSec=10s
+OnUnitInactiveSec=1h
+EOF
+
+systemctl unmask my.timer
+
+systemctl start my.timer
+
+mkdir -p /etc/systemd/system/my.timer.d/
+cat >/etc/systemd/system/my.timer.d/override.conf <<EOF
+[Timer]
+OnBootSec=10s
+OnUnitInactiveSec=1h
+EOF
+
+systemctl daemon-reload
+
+systemctl mask my.timer
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-08.service b/test/units/testsuite-08.service
new file mode 100644
index 0000000..d693766
--- /dev/null
+++ b/test/units/testsuite-08.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-08-ISSUE-2730
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=sh -x -c 'mount -o remount,rw /dev/sda1 && echo OK >/testok; systemctl poweroff'
+Type=oneshot
diff --git a/test/units/testsuite-09.service b/test/units/testsuite-09.service
new file mode 100644
index 0000000..6f6cd9c
--- /dev/null
+++ b/test/units/testsuite-09.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-09-ISSUE-2691
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=sh -c '>/testok'
+ExecStop=sh -c 'kill -SEGV $$$$'
+Type=oneshot
+RemainAfterExit=yes
+TimeoutStopSec=270s
diff --git a/test/units/testsuite-10.service b/test/units/testsuite-10.service
new file mode 100644
index 0000000..9fcfd67
--- /dev/null
+++ b/test/units/testsuite-10.service
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-10-ISSUE-2467
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+Type=oneshot
+ExecStart=rm -f /tmp/nonexistent
+ExecStart=systemctl start test10.socket
+ExecStart=-nc -w20 -U /run/test.ctl
+# TriggerLimitIntervalSec= by default is set to 2s. A "sleep 10" should give
+# systemd enough time even on slower machines, to reach the trigger limit.
+ExecStart=sleep 10
+ExecStart=sh -x -c 'test "$(systemctl show test10.socket -P ActiveState)" = failed'
+ExecStart=sh -x -c 'test "$(systemctl show test10.socket -P Result)" = trigger-limit-hit'
+ExecStart=sh -x -c 'echo OK >/testok'
diff --git a/test/units/testsuite-11.service b/test/units/testsuite-11.service
new file mode 100644
index 0000000..5dfcf50
--- /dev/null
+++ b/test/units/testsuite-11.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-11-ISSUE-3166
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-11.sh b/test/units/testsuite-11.sh
new file mode 100755
index 0000000..7e1391d
--- /dev/null
+++ b/test/units/testsuite-11.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemctl --no-block start fail-on-restart.service
+active_state=$(systemctl show --value --property ActiveState fail-on-restart.service)
+while [[ "$active_state" == "activating" || "$active_state" =~ ^(in)?active$ ]]; do
+ sleep .5
+ active_state=$(systemctl show --value --property ActiveState fail-on-restart.service)
+done
+systemctl is-failed fail-on-restart.service || exit 1
+[[ "$(systemctl show --value --property NRestarts fail-on-restart.service)" -le 3 ]] || exit 1
+touch /testok
diff --git a/test/units/testsuite-12.service b/test/units/testsuite-12.service
new file mode 100644
index 0000000..b26cfa5
--- /dev/null
+++ b/test/units/testsuite-12.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-12-ISSUE-3171
+After=multi-user.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-12.sh b/test/units/testsuite-12.sh
new file mode 100755
index 0000000..8c22a8b
--- /dev/null
+++ b/test/units/testsuite-12.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+echo "g adm - - -" | systemd-sysusers -
+
+U=/run/systemd/system/test12.socket
+cat >$U <<EOF
+[Unit]
+Description=Test 12 socket
+[Socket]
+Accept=yes
+ListenStream=/run/test12.socket
+SocketGroup=adm
+SocketMode=0660
+EOF
+
+cat >/run/systemd/system/test12@.service <<EOF
+[Unit]
+Description=Test service
+[Service]
+StandardInput=socket
+ExecStart=/bin/sh -x -c cat
+EOF
+
+systemctl start test12.socket
+systemctl is-active test12.socket
+[[ "$(stat --format='%G' /run/test12.socket)" == adm ]]
+echo A | nc -w1 -U /run/test12.socket
+
+mv $U ${U}.disabled
+systemctl daemon-reload
+systemctl is-active test12.socket
+[[ "$(stat --format='%G' /run/test12.socket)" == adm ]]
+echo B | nc -w1 -U /run/test12.socket && exit 1
+
+mv ${U}.disabled $U
+systemctl daemon-reload
+systemctl is-active test12.socket
+echo C | nc -w1 -U /run/test12.socket && exit 1
+[[ "$(stat --format='%G' /run/test12.socket)" == adm ]]
+
+systemctl restart test12.socket
+systemctl is-active test12.socket
+echo D | nc -w1 -U /run/test12.socket
+[[ "$(stat --format='%G' /run/test12.socket)" == adm ]]
+
+touch /testok
diff --git a/test/units/testsuite-13.service b/test/units/testsuite-13.service
new file mode 100644
index 0000000..a964d8d
--- /dev/null
+++ b/test/units/testsuite-13.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-13-NSPAWN-SMOKE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-13.sh b/test/units/testsuite-13.sh
new file mode 100755
index 0000000..4ad7431
--- /dev/null
+++ b/test/units/testsuite-13.sh
@@ -0,0 +1,268 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+export SYSTEMD_LOG_LEVEL=debug
+export SYSTEMD_LOG_TARGET=journal
+
+# check cgroup-v2
+is_v2_supported=no
+mkdir -p /tmp/cgroup2
+if mount -t cgroup2 cgroup2 /tmp/cgroup2; then
+ is_v2_supported=yes
+ umount /tmp/cgroup2
+fi
+rmdir /tmp/cgroup2
+
+# check cgroup namespaces
+is_cgns_supported=no
+if [[ -f /proc/1/ns/cgroup ]]; then
+ is_cgns_supported=yes
+fi
+
+is_user_ns_supported=no
+# On some systems (e.g. CentOS 7) the default limit for user namespaces
+# is set to 0, which causes the following unshare syscall to fail, even
+# with enabled user namespaces support. By setting this value explicitly
+# we can ensure the user namespaces support to be detected correctly.
+sysctl -w user.max_user_namespaces=10000
+if unshare -U sh -c :; then
+ is_user_ns_supported=yes
+fi
+
+function check_bind_tmp_path {
+ # https://github.com/systemd/systemd/issues/4789
+ local _root="/var/lib/machines/testsuite-13.bind-tmp-path"
+ rm -rf "$_root"
+ /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
+ : >/tmp/bind
+ systemd-nspawn --register=no -D "$_root" --bind=/tmp/bind /bin/sh -c 'test -e /tmp/bind'
+}
+
+function check_norbind {
+ # https://github.com/systemd/systemd/issues/13170
+ local _root="/var/lib/machines/testsuite-13.norbind-path"
+ rm -rf "$_root"
+ mkdir -p /tmp/binddir/subdir
+ echo -n "outer" >/tmp/binddir/subdir/file
+ mount -t tmpfs tmpfs /tmp/binddir/subdir
+ echo -n "inner" >/tmp/binddir/subdir/file
+ /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
+ systemd-nspawn --register=no -D "$_root" --bind=/tmp/binddir:/mnt:norbind /bin/sh -c 'CONTENT=$(cat /mnt/subdir/file); if [[ $CONTENT != "outer" ]]; then echo "*** unexpected content: $CONTENT"; return 1; fi'
+}
+
+function check_rootidmap {
+ local _owner=1000
+ local _root="/var/lib/machines/testsuite-13.rootidmap-path"
+ local _command
+ rm -rf "$_root"
+
+ # Create ext4 image, as ext4 supports idmapped-mounts.
+ dd if=/dev/zero of=/tmp/ext4.img bs=4k count=2048
+ mkfs.ext4 /tmp/ext4.img
+ mkdir -p /tmp/rootidmapdir
+ mount /tmp/ext4.img /tmp/rootidmapdir
+
+ touch /tmp/rootidmapdir/file
+ chown -R $_owner:$_owner /tmp/rootidmapdir
+
+ /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
+ _command='PERMISSIONS=$(stat -c "%u:%g" /mnt/file); if [[ $PERMISSIONS != "0:0" ]]; then echo "*** wrong permissions: $PERMISSIONS"; return 1; fi; touch /mnt/other_file'
+ if ! SYSTEMD_LOG_TARGET=console systemd-nspawn \
+ --register=no -D "$_root" \
+ --bind=/tmp/rootidmapdir:/mnt:rootidmap \
+ /bin/sh -c "$_command" |& tee nspawn.out; then
+ if grep -q "Failed to map ids for bind mount.*: Function not implemented" nspawn.out; then
+ echo "idmapped mounts are not supported, skipping the test..."
+ return 0
+ fi
+
+ return 1
+ fi
+
+ PERMISSIONS=$(stat -c "%u:%g" /tmp/rootidmapdir/other_file)
+ if [[ $PERMISSIONS != "$_owner:$_owner" ]]; then
+ echo "*** wrong permissions: $PERMISSIONS"
+ [[ "$is_user_ns_supported" = "yes" ]] && return 1
+ fi
+}
+
+function check_notification_socket {
+ # https://github.com/systemd/systemd/issues/4944
+ local _cmd='echo a | $(busybox which nc) -U -u -w 1 /run/host/notify'
+ # /testsuite-13.nc-container is prepared by test.sh
+ systemd-nspawn --register=no -D /testsuite-13.nc-container /bin/sh -x -c "$_cmd"
+ systemd-nspawn --register=no -D /testsuite-13.nc-container -U /bin/sh -x -c "$_cmd"
+}
+
+function check_os_release {
+ local _cmd='. /tmp/os-release
+if [ -n "${ID:+set}" ] && [ "${ID}" != "${container_host_id}" ]; then exit 1; fi
+if [ -n "${VERSION_ID:+set}" ] && [ "${VERSION_ID}" != "${container_host_version_id}" ]; then exit 1; fi
+if [ -n "${BUILD_ID:+set}" ] && [ "${BUILD_ID}" != "${container_host_build_id}" ]; then exit 1; fi
+if [ -n "${VARIANT_ID:+set}" ] && [ "${VARIANT_ID}" != "${container_host_variant_id}" ]; then exit 1; fi
+cd /tmp; (cd /run/host; md5sum os-release) | md5sum -c
+if echo test >>/run/host/os-release; then exit 1; fi
+'
+
+ local _os_release_source="/etc/os-release"
+ if [[ ! -r "${_os_release_source}" ]]; then
+ _os_release_source="/usr/lib/os-release"
+ elif [[ -L "${_os_release_source}" ]] && rm /etc/os-release; then
+ # Ensure that /etc always wins if available
+ cp /usr/lib/os-release /etc
+ echo MARKER=1 >>/etc/os-release
+ fi
+
+ systemd-nspawn --register=no -D /testsuite-13.nc-container --bind="${_os_release_source}":/tmp/os-release /bin/sh -x -e -c "$_cmd"
+
+ if grep -q MARKER /etc/os-release; then
+ rm /etc/os-release
+ ln -s ../usr/lib/os-release /etc/os-release
+ fi
+}
+
+function check_machinectl_bind {
+ local _cmd='for i in $(seq 1 20); do if test -f /tmp/marker; then exit 0; fi; usleep 500000; done; exit 1;'
+
+ cat >/run/systemd/system/nspawn_machinectl_bind.service <<EOF
+[Service]
+Type=notify
+ExecStart=systemd-nspawn ${SUSE_OPTS[@]} -D /testsuite-13.nc-container --notify-ready=no /bin/sh -x -e -c "$_cmd"
+EOF
+
+ systemctl start nspawn_machinectl_bind.service
+
+ touch /tmp/marker
+
+ machinectl bind --mkdir testsuite-13.nc-container /tmp/marker
+
+ while systemctl show -P SubState nspawn_machinectl_bind.service | grep -q running
+ do
+ sleep 0.1
+ done
+
+ return "$(systemctl show -P ExecMainStatus nspawn_machinectl_bind.service)"
+}
+
+function check_selinux {
+ if ! command -v selinuxenabled >/dev/null || ! selinuxenabled; then
+ echo >&2 "SELinux is not enabled, skipping SELinux-related tests"
+ return 0
+ fi
+
+ # Basic test coverage to avoid issues like https://github.com/systemd/systemd/issues/19976
+ systemd-nspawn "${SUSE_OPTS[@]}" --register=no -b -D /testsuite-13.nc-container --selinux-apifs-context=system_u:object_r:container_file_t:s0:c0,c1 --selinux-context=system_u:system_r:container_t:s0:c0,c1
+}
+
+function check_ephemeral_config {
+ # https://github.com/systemd/systemd/issues/13297
+
+ mkdir -p /run/systemd/nspawn/
+ cat >/run/systemd/nspawn/testsuite-13.nc-container.nspawn <<EOF
+[Files]
+BindReadOnly=/tmp/ephemeral-config
+EOF
+ touch /tmp/ephemeral-config
+
+ # /testsuite-13.nc-container is prepared by test.sh
+ systemd-nspawn --register=no -D /testsuite-13.nc-container --ephemeral /bin/sh -x -c "test -f /tmp/ephemeral-config"
+
+ systemd-nspawn --register=no -D /testsuite-13.nc-container --ephemeral --machine foobar /bin/sh -x -c "! test -f /tmp/ephemeral-config"
+
+ rm -f /run/systemd/nspawn/testsuite-13.nc-container.nspawn
+}
+
+function run {
+ if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then
+ printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2
+ return 0
+ fi
+ if [[ "$2" = "yes" && "$is_cgns_supported" = "no" ]]; then
+ printf "CGroup namespaces are not supported. Skipping.\n" >&2
+ return 0
+ fi
+
+ local _root="/var/lib/machines/testsuite-13.unified-$1-cgns-$2-api-vfs-writable-$3"
+ rm -rf "$_root"
+ /usr/lib/systemd/tests/testdata/create-busybox-container "$_root"
+ SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b
+ SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" --private-network -b
+
+ if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -U -b; then
+ [[ "$is_user_ns_supported" = "yes" && "$3" = "network" ]] && return 1
+ else
+ [[ "$is_user_ns_supported" = "no" && "$3" = "network" ]] && return 1
+ fi
+
+ if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" --private-network -U -b; then
+ [[ "$is_user_ns_supported" = "yes" && "$3" = "yes" ]] && return 1
+ else
+ [[ "$is_user_ns_supported" = "no" && "$3" = "yes" ]] && return 1
+ fi
+
+ local _netns_opt="--network-namespace-path=/proc/self/ns/net"
+ local _net_opts=(
+ "--network-bridge=lo"
+ "--network-interface=lo"
+ "--network-ipvlan=lo"
+ "--network-macvlan=lo"
+ "--network-veth"
+ "--network-veth-extra=lo"
+ "--network-zone=zone"
+ )
+
+ # --network-namespace-path and network-related options cannot be used together
+ for netopt in "${_net_opts[@]}"; do
+ echo "$_netns_opt in combination with $netopt should fail"
+ if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b "$_netns_opt" "$netopt"; then
+ echo >&2 "unexpected pass"
+ return 1
+ fi
+ done
+
+ # allow combination of --network-namespace-path and --private-network
+ if ! SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" -b "$_netns_opt" --private-network; then
+ return 1
+ fi
+
+ # test --network-namespace-path works with a network namespace created by "ip netns"
+ ip netns add nspawn_test
+ _netns_opt="--network-namespace-path=/run/netns/nspawn_test"
+ SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$1" SYSTEMD_NSPAWN_USE_CGNS="$2" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$3" systemd-nspawn --register=no -D "$_root" "$_netns_opt" /bin/ip a | grep -v -E '^1: lo.*UP'
+ local r=$?
+ ip netns del nspawn_test
+
+ if [[ $r -ne 0 ]]; then
+ return 1
+ fi
+
+ return 0
+}
+
+check_bind_tmp_path
+
+check_norbind
+
+check_rootidmap
+
+check_notification_socket
+
+check_os_release
+
+for api_vfs_writable in yes no network; do
+ run no no $api_vfs_writable
+ run yes no $api_vfs_writable
+ run no yes $api_vfs_writable
+ run yes yes $api_vfs_writable
+done
+
+check_machinectl_bind
+
+check_selinux
+
+check_ephemeral_config
+
+touch /testok
diff --git a/test/units/testsuite-14.service b/test/units/testsuite-14.service
new file mode 100644
index 0000000..23644e5
--- /dev/null
+++ b/test/units/testsuite-14.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-14-MACHINE-ID
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+ExecStart=/bin/sh -e -x -c 'systemctl --state=failed --no-legend --no-pager >/failed ; echo OK >/testok'
+Type=oneshot
diff --git a/test/units/testsuite-14.sh b/test/units/testsuite-14.sh
new file mode 100755
index 0000000..5427591
--- /dev/null
+++ b/test/units/testsuite-14.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+function setup_root {
+ local _root="$1"
+ mkdir -p "$_root"
+ mount -t tmpfs tmpfs "$_root"
+ mkdir -p "$_root/etc" "$_root/run"
+}
+
+function check {
+ printf "Expected\n"
+ cat "$1"
+ printf "\nGot\n"
+ cat "$2"
+ cmp "$1" "$2"
+}
+
+r="$(pwd)/overwrite-broken-machine-id"
+setup_root "$r"
+systemd-machine-id-setup --print --root "$r"
+echo abc >>"$r/etc/machine-id"
+id="$(systemd-machine-id-setup --print --root "$r")"
+echo "$id" >expected
+check expected "$r/etc/machine-id"
+
+r="$PWD/transient-machine-id"
+setup_root "$r"
+systemd-machine-id-setup --print --root "$r"
+echo abc >>"$r/etc/machine-id"
+mount -o remount,ro "$r"
+mount -t tmpfs tmpfs "$r/run"
+transient_id="$(systemd-machine-id-setup --print --root "$r")"
+mount -o remount,rw "$r"
+commited_id="$(systemd-machine-id-setup --print --commit --root "$r")"
+[[ "$transient_id" = "$commited_id" ]]
+check "$r/etc/machine-id" "$r/run/machine-id"
diff --git a/test/units/testsuite-15.service b/test/units/testsuite-15.service
new file mode 100644
index 0000000..10af93f
--- /dev/null
+++ b/test/units/testsuite-15.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-15-DROPIN
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-15.sh b/test/units/testsuite-15.sh
new file mode 100755
index 0000000..2f80cf2
--- /dev/null
+++ b/test/units/testsuite-15.sh
@@ -0,0 +1,717 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+clear_unit () {
+ local UNIT_NAME="${1:?}"
+ systemctl stop "$UNIT_NAME" 2>/dev/null || :
+ rm -f /{etc,run,usr/lib}/systemd/system/"$UNIT_NAME"
+ rm -fr /{etc,run,usr/lib}/systemd/system/"$UNIT_NAME".d
+ rm -fr /{etc,run,usr/lib}/systemd/system/"$UNIT_NAME".{wants,requires}
+ if [[ $UNIT_NAME == *@ ]]; then
+ local base="${UNIT_NAME%@*}"
+ local suffix="${UNIT_NAME##*.}"
+ systemctl stop "$base@"*."$suffix" 2>/dev/null || :
+ rm -f /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix"
+ rm -fr /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix".d
+ rm -fr /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix".{wants,requires}
+ fi
+}
+
+clear_units () {
+ for u in "$@"; do
+ clear_unit "$u"
+ done
+ systemctl daemon-reload
+}
+
+create_service () {
+ local SERVICE_NAME="${1:?}"
+ clear_units "${SERVICE_NAME}".service
+
+ cat >/etc/systemd/system/"$SERVICE_NAME".service <<EOF
+[Unit]
+Description=$SERVICE_NAME unit
+
+[Service]
+ExecStart=sleep 100000
+EOF
+ mkdir -p /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME".service.{d,wants,requires}
+}
+
+create_services () {
+ for u in "$@"; do
+ create_service "$u"
+ done
+}
+
+check_ok () {
+ x="$(systemctl show --value -p "${2:?}" "${1:?}")"
+ case "$x" in
+ *${3:?}*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+check_ko () {
+ ! check_ok "$@"
+}
+
+test_basic_dropins () {
+ echo "Testing basic dropins..."
+
+ echo "*** test a wants b wants c"
+ create_services test15-a test15-b test15-c
+ ln -s ../test15-b.service /etc/systemd/system/test15-a.service.wants/
+ ln -s ../test15-c.service /etc/systemd/system/test15-b.service.wants/
+ check_ok test15-a Wants test15-b.service
+ check_ok test15-b Wants test15-c.service
+
+ echo "*** test a wants,requires b"
+ create_services test15-a test15-b test15-c
+ ln -s ../test15-b.service /etc/systemd/system/test15-a.service.wants/
+ ln -s ../test15-b.service /etc/systemd/system/test15-a.service.requires/
+ check_ok test15-a Wants test15-b.service
+ check_ok test15-a Requires test15-b.service
+
+ echo "*** test a wants nonexistent"
+ create_service test15-a
+ ln -s ../nonexistent.service /etc/systemd/system/test15-a.service.wants/
+ check_ok test15-a Wants nonexistent.service
+ systemctl start test15-a
+ systemctl stop test15-a
+
+ echo "*** test a requires nonexistent"
+ ln -sf ../nonexistent.service /etc/systemd/system/test15-a.service.requires/
+ systemctl daemon-reload
+ check_ok test15-a Requires nonexistent.service
+
+ # 'b' is already loaded when 'c' pulls it in via a dropin.
+ echo "*** test a,c require b"
+ create_services test15-a test15-b test15-c
+ ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
+ ln -sf ../test15-b.service /etc/systemd/system/test15-c.service.requires/
+ systemctl start test15-a
+ check_ok test15-c Requires test15-b.service
+ systemctl stop test15-a test15-b
+
+ # 'b' is already loaded when 'c' pulls it in via an alias dropin.
+ echo "*** test a wants alias"
+ create_services test15-a test15-b test15-c
+ ln -sf test15-c.service /etc/systemd/system/test15-c1.service
+ ln -sf ../test15-c.service /etc/systemd/system/test15-a.service.wants/
+ ln -sf ../test15-c1.service /etc/systemd/system/test15-b.service.wants/
+ systemctl start test15-a
+ check_ok test15-a Wants test15-c.service
+ check_ok test15-b Wants test15-c.service
+ systemctl stop test15-a test15-c
+
+ echo "*** test service.d/ top level drop-in"
+ create_services test15-a test15-b
+ check_ko test15-a ExecCondition "/bin/echo a"
+ check_ko test15-b ExecCondition "/bin/echo b"
+ mkdir -p /usr/lib/systemd/system/service.d
+ cat >/usr/lib/systemd/system/service.d/override.conf <<EOF
+[Service]
+ExecCondition=/bin/echo %n
+EOF
+ systemctl daemon-reload
+ check_ok test15-a ExecCondition "/bin/echo test15-a"
+ check_ok test15-b ExecCondition "/bin/echo test15-b"
+ rm -rf /usr/lib/systemd/system/service.d
+
+ clear_units test15-{a,b,c,c1}.service
+}
+
+test_linked_units () {
+ echo "Testing linked units..."
+ echo "*** test linked unit (same basename)"
+
+ create_service test15-a
+ mv /etc/systemd/system/test15-a.service /
+ ln -s /test15-a.service /etc/systemd/system/
+ ln -s test15-a.service /etc/systemd/system/test15-b.service
+
+ check_ok test15-a Names test15-a.service
+ check_ok test15-a Names test15-b.service
+
+ echo "*** test linked unit (cross basename)"
+
+ mv /test15-a.service /test15-a@.scope
+ ln -fs /test15-a@.scope /etc/systemd/system/test15-a.service
+ systemctl daemon-reload
+
+ check_ok test15-a Names test15-a.service
+ check_ok test15-a Names test15-b.service
+ check_ko test15-a Names test15-a@ # test15-a@.scope is the symlink target.
+ # Make sure it is completely ignored.
+
+ rm /test15-a@.scope
+ clear_units test15-{a,b}.service
+}
+
+test_template_alias() {
+ echo "Testing instance alias..."
+ echo "*** forward"
+
+ create_service test15-a@
+ ln -s test15-a@inst.service /etc/systemd/system/test15-b@inst.service # alias
+
+ check_ok test15-a@inst Names test15-a@inst.service
+ check_ok test15-a@inst Names test15-b@inst.service
+
+ check_ok test15-a@other Names test15-a@other.service
+ check_ko test15-a@other Names test15-b@other.service
+
+ echo "*** reverse"
+
+ systemctl daemon-reload
+
+ check_ok test15-b@inst Names test15-a@inst.service
+ check_ok test15-b@inst Names test15-b@inst.service
+
+ check_ko test15-b@other Names test15-a@other.service
+ check_ok test15-b@other Names test15-b@other.service
+
+ clear_units test15-{a,b}@.service
+}
+
+test_hierarchical_service_dropins () {
+ echo "Testing hierarchical service dropins..."
+ echo "*** test service.d/ top level drop-in"
+ create_services a-b-c
+ check_ko a-b-c ExecCondition "echo service.d"
+ check_ko a-b-c ExecCondition "echo a-.service.d"
+ check_ko a-b-c ExecCondition "echo a-b-.service.d"
+ check_ko a-b-c ExecCondition "echo a-b-c.service.d"
+
+ for dropin in service.d a-.service.d a-b-.service.d a-b-c.service.d; do
+ mkdir -p /usr/lib/systemd/system/$dropin
+ echo "
+[Service]
+ExecCondition=echo $dropin
+ " >/usr/lib/systemd/system/$dropin/override.conf
+ systemctl daemon-reload
+ check_ok a-b-c ExecCondition "echo $dropin"
+
+ # Check that we can start a transient service in presence of the drop-ins
+ systemd-run -u a-b-c2.service -p Description='sleepy' sleep infinity
+
+ # The transient setting replaces the default
+ check_ok a-b-c2.service Description "sleepy"
+
+ # The override takes precedence for ExecCondition
+ # (except the last iteration when it only applies to the other service)
+ if [ "$dropin" != "a-b-c.service.d" ]; then
+ check_ok a-b-c2.service ExecCondition "echo $dropin"
+ fi
+
+ # Check that things are the same after a reload
+ systemctl daemon-reload
+ check_ok a-b-c2.service Description "sleepy"
+ if [ "$dropin" != "a-b-c.service.d" ]; then
+ check_ok a-b-c2.service ExecCondition "echo $dropin"
+ fi
+
+ systemctl stop a-b-c2.service
+ done
+ for dropin in service.d a-.service.d a-b-.service.d a-b-c.service.d; do
+ rm -rf /usr/lib/systemd/system/$dropin
+ done
+
+ clear_units a-b-c.service
+}
+
+test_hierarchical_slice_dropins () {
+ echo "Testing hierarchical slice dropins..."
+ echo "*** test slice.d/ top level drop-in"
+ # Slice units don't even need a fragment, so we test the defaults here
+ check_ok a-b-c.slice Description "Slice /a/b/c"
+ check_ok a-b-c.slice MemoryMax "infinity"
+
+ # Test drop-ins
+ for dropin in slice.d a-.slice.d a-b-.slice.d a-b-c.slice.d; do
+ mkdir -p /usr/lib/systemd/system/$dropin
+ echo "
+[Slice]
+MemoryMax=1000000000
+ " >/usr/lib/systemd/system/$dropin/override.conf
+ systemctl daemon-reload
+ check_ok a-b-c.slice MemoryMax "1000000000"
+
+ busctl call \
+ org.freedesktop.systemd1 \
+ /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager \
+ StartTransientUnit 'ssa(sv)a(sa(sv))' \
+ 'a-b-c.slice' 'replace' \
+ 2 \
+ 'Description' s 'slice too' \
+ 'MemoryMax' t 1000000002 \
+ 0
+
+ # The override takes precedence for MemoryMax
+ check_ok a-b-c.slice MemoryMax "1000000000"
+ # The transient setting replaces the default
+ check_ok a-b-c.slice Description "slice too"
+
+ # Check that things are the same after a reload
+ systemctl daemon-reload
+ check_ok a-b-c.slice MemoryMax "1000000000"
+ check_ok a-b-c.slice Description "slice too"
+
+ busctl call \
+ org.freedesktop.systemd1 \
+ /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager \
+ StopUnit 'ss' \
+ 'a-b-c.slice' 'replace'
+
+ rm /usr/lib/systemd/system/$dropin/override.conf
+ done
+
+ # Test unit with a fragment
+ echo "
+[Slice]
+MemoryMax=1000000001
+ " >/usr/lib/systemd/system/a-b-c.slice
+ systemctl daemon-reload
+ check_ok a-b-c.slice MemoryMax "1000000001"
+
+ clear_units a-b-c.slice
+}
+
+test_transient_service_dropins () {
+ echo "Testing dropins for a transient service..."
+ echo "*** test transient service drop-ins"
+
+ mkdir -p /etc/systemd/system/service.d
+ mkdir -p /etc/systemd/system/a-.service.d
+ mkdir -p /etc/systemd/system/a-b-.service.d
+ mkdir -p /etc/systemd/system/a-b-c.service.d
+
+ echo -e '[Service]\nStandardInputText=aaa' >/etc/systemd/system/service.d/drop1.conf
+ echo -e '[Service]\nStandardInputText=bbb' >/etc/systemd/system/a-.service.d/drop2.conf
+ echo -e '[Service]\nStandardInputText=ccc' >/etc/systemd/system/a-b-.service.d/drop3.conf
+ echo -e '[Service]\nStandardInputText=ddd' >/etc/systemd/system/a-b-c.service.d/drop4.conf
+
+ # There's no fragment yet, so this fails
+ systemctl cat a-b-c.service && exit 1
+
+ # xxx → eHh4Cg==
+ systemd-run -u a-b-c.service -p StandardInputData=eHh4Cg== sleep infinity
+
+ data=$(systemctl show -P StandardInputData a-b-c.service)
+ # xxx\naaa\n\bbb\nccc\nddd\n → eHh4…
+ test "$data" = "eHh4CmFhYQpiYmIKY2NjCmRkZAo="
+
+ # Do a reload and check again
+ systemctl daemon-reload
+ data=$(systemctl show -P StandardInputData a-b-c.service)
+ test "$data" = "eHh4CmFhYQpiYmIKY2NjCmRkZAo="
+
+ clear_units a-b-c.service
+ rm /etc/systemd/system/service.d/drop1.conf \
+ /etc/systemd/system/a-.service.d/drop2.conf \
+ /etc/systemd/system/a-b-.service.d/drop3.conf
+}
+
+test_transient_slice_dropins () {
+ echo "Testing dropins for a transient slice..."
+ echo "*** test transient slice drop-ins"
+
+ # FIXME: implement reloading of individual units.
+ #
+ # The settings here are loaded twice. For most settings it doesn't matter,
+ # but Documentation is not deduplicated, so we current get repeated entried
+ # which is a bug.
+
+ mkdir -p /etc/systemd/system/slice.d
+ mkdir -p /etc/systemd/system/a-.slice.d
+ mkdir -p /etc/systemd/system/a-b-.slice.d
+ mkdir -p /etc/systemd/system/a-b-c.slice.d
+
+ echo -e '[Unit]\nDocumentation=man:drop1' >/etc/systemd/system/slice.d/drop1.conf
+ echo -e '[Unit]\nDocumentation=man:drop2' >/etc/systemd/system/a-.slice.d/drop2.conf
+ echo -e '[Unit]\nDocumentation=man:drop3' >/etc/systemd/system/a-b-.slice.d/drop3.conf
+ echo -e '[Unit]\nDocumentation=man:drop4' >/etc/systemd/system/a-b-c.slice.d/drop4.conf
+
+ # Invoke daemon-reload to make sure that the call below doesn't fail
+ systemctl daemon-reload
+
+ # No fragment is required, so this works
+ systemctl cat a-b-c.slice
+
+ busctl call \
+ org.freedesktop.systemd1 \
+ /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager \
+ StartTransientUnit 'ssa(sv)a(sa(sv))' \
+ 'a-b-c.slice' 'replace' \
+ 1 \
+ 'Documentation' as 1 'man:drop5' \
+ 0
+
+ data=$(systemctl show -P Documentation a-b-c.slice)
+ test "$data" = "man:drop1 man:drop2 man:drop3 man:drop4 man:drop5 man:drop1 man:drop2 man:drop3 man:drop4"
+
+ # Do a reload and check again
+ systemctl daemon-reload
+ data=$(systemctl show -P Documentation a-b-c.slice)
+ test "$data" = "man:drop5 man:drop1 man:drop2 man:drop3 man:drop4"
+
+ clear_units a-b-c.slice
+ rm /etc/systemd/system/slice.d/drop1.conf \
+ /etc/systemd/system/a-.slice.d/drop2.conf \
+ /etc/systemd/system/a-b-.slice.d/drop3.conf
+}
+
+test_template_dropins () {
+ echo "Testing template dropins..."
+
+ create_services foo bar@ yup@
+
+ # Declare some deps to check if the body was loaded
+ cat >>/etc/systemd/system/bar@.service <<EOF
+[Unit]
+After=bar-template-after.device
+EOF
+
+ cat >>/etc/systemd/system/yup@.service <<EOF
+[Unit]
+After=yup-template-after.device
+EOF
+
+ ln -s /etc/systemd/system/bar@.service /etc/systemd/system/foo.service.wants/bar@1.service
+ check_ok foo Wants bar@1.service
+
+ echo "*** test bar-alias@.service→bar@.service, but instance symlinks point to yup@.service ***"
+ ln -s bar@.service /etc/systemd/system/bar-alias@.service
+ ln -s bar@1.service /etc/systemd/system/bar-alias@1.service
+ ln -s yup@.service /etc/systemd/system/bar-alias@2.service
+ ln -s yup@3.service /etc/systemd/system/bar-alias@3.service
+
+ # create some dropin deps
+ mkdir -p /etc/systemd/system/bar@{,0,1,2,3}.service.requires/
+ mkdir -p /etc/systemd/system/yup@{,0,1,2,3}.service.requires/
+ mkdir -p /etc/systemd/system/bar-alias@{,0,1,2,3}.service.requires/
+
+ ln -s ../bar-template-requires.device /etc/systemd/system/bar@.service.requires/
+ ln -s ../bar-0-requires.device /etc/systemd/system/bar@0.service.requires/
+ ln -s ../bar-1-requires.device /etc/systemd/system/bar@1.service.requires/
+ ln -s ../bar-2-requires.device /etc/systemd/system/bar@2.service.requires/
+ ln -s ../bar-3-requires.device /etc/systemd/system/bar@3.service.requires/
+
+ ln -s ../yup-template-requires.device /etc/systemd/system/yup@.service.requires/
+ ln -s ../yup-0-requires.device /etc/systemd/system/yup@0.service.requires/
+ ln -s ../yup-1-requires.device /etc/systemd/system/yup@1.service.requires/
+ ln -s ../yup-2-requires.device /etc/systemd/system/yup@2.service.requires/
+ ln -s ../yup-3-requires.device /etc/systemd/system/yup@3.service.requires/
+
+ ln -s ../bar-alias-template-requires.device /etc/systemd/system/bar-alias@.service.requires/
+ ln -s ../bar-alias-0-requires.device /etc/systemd/system/bar-alias@0.service.requires/
+ ln -s ../bar-alias-1-requires.device /etc/systemd/system/bar-alias@1.service.requires/
+ ln -s ../bar-alias-2-requires.device /etc/systemd/system/bar-alias@2.service.requires/
+ ln -s ../bar-alias-3-requires.device /etc/systemd/system/bar-alias@3.service.requires/
+
+ systemctl daemon-reload
+
+ echo '*** bar@0 is aliased by bar-alias@0 ***'
+ systemctl show -p Names,Requires bar@0
+ systemctl show -p Names,Requires bar-alias@0
+ check_ok bar@0 Names bar@0
+ check_ok bar@0 Names bar-alias@0
+
+ check_ok bar@0 After bar-template-after.device
+
+ check_ok bar@0 Requires bar-0-requires.device
+ check_ok bar@0 Requires bar-alias-0-requires.device
+ check_ok bar@0 Requires bar-template-requires.device
+ check_ok bar@0 Requires bar-alias-template-requires.device
+ check_ko bar@0 Requires yup-template-requires.device
+
+ check_ok bar-alias@0 After bar-template-after.device
+
+ check_ok bar-alias@0 Requires bar-0-requires.device
+ check_ok bar-alias@0 Requires bar-alias-0-requires.device
+ check_ok bar-alias@0 Requires bar-template-requires.device
+ check_ok bar-alias@0 Requires bar-alias-template-requires.device
+ check_ko bar-alias@0 Requires yup-template-requires.device
+ check_ko bar-alias@0 Requires yup-0-requires.device
+
+ echo '*** bar@1 is aliased by bar-alias@1 ***'
+ systemctl show -p Names,Requires bar@1
+ systemctl show -p Names,Requires bar-alias@1
+ check_ok bar@1 Names bar@1
+ check_ok bar@1 Names bar-alias@1
+
+ check_ok bar@1 After bar-template-after.device
+
+ check_ok bar@1 Requires bar-1-requires.device
+ check_ok bar@1 Requires bar-alias-1-requires.device
+ check_ok bar@1 Requires bar-template-requires.device
+ # See https://github.com/systemd/systemd/pull/13119#discussion_r308145418
+ check_ok bar@1 Requires bar-alias-template-requires.device
+ check_ko bar@1 Requires yup-template-requires.device
+ check_ko bar@1 Requires yup-1-requires.device
+
+ check_ok bar-alias@1 After bar-template-after.device
+
+ check_ok bar-alias@1 Requires bar-1-requires.device
+ check_ok bar-alias@1 Requires bar-alias-1-requires.device
+ check_ok bar-alias@1 Requires bar-template-requires.device
+ check_ok bar-alias@1 Requires bar-alias-template-requires.device
+ check_ko bar-alias@1 Requires yup-template-requires.device
+ check_ko bar-alias@1 Requires yup-1-requires.device
+
+ echo '*** bar-alias@2 aliases yup@2, bar@2 is independent ***'
+ systemctl show -p Names,Requires bar@2
+ systemctl show -p Names,Requires bar-alias@2
+ check_ok bar@2 Names bar@2
+ check_ko bar@2 Names bar-alias@2
+
+ check_ok bar@2 After bar-template-after.device
+
+ check_ok bar@2 Requires bar-2-requires.device
+ check_ko bar@2 Requires bar-alias-2-requires.device
+ check_ok bar@2 Requires bar-template-requires.device
+ check_ko bar@2 Requires bar-alias-template-requires.device
+ check_ko bar@2 Requires yup-template-requires.device
+ check_ko bar@2 Requires yup-2-requires.device
+
+ check_ko bar-alias@2 After bar-template-after.device
+
+ check_ko bar-alias@2 Requires bar-2-requires.device
+ check_ok bar-alias@2 Requires bar-alias-2-requires.device
+ check_ko bar-alias@2 Requires bar-template-requires.device
+ check_ok bar-alias@2 Requires bar-alias-template-requires.device
+ check_ok bar-alias@2 Requires yup-template-requires.device
+ check_ok bar-alias@2 Requires yup-2-requires.device
+
+ echo '*** bar-alias@3 aliases yup@3, bar@3 is independent ***'
+ systemctl show -p Names,Requires bar@3
+ systemctl show -p Names,Requires bar-alias@3
+ check_ok bar@3 Names bar@3
+ check_ko bar@3 Names bar-alias@3
+
+ check_ok bar@3 After bar-template-after.device
+
+ check_ok bar@3 Requires bar-3-requires.device
+ check_ko bar@3 Requires bar-alias-3-requires.device
+ check_ok bar@3 Requires bar-template-requires.device
+ check_ko bar@3 Requires bar-alias-template-requires.device
+ check_ko bar@3 Requires yup-template-requires.device
+ check_ko bar@3 Requires yup-3-requires.device
+
+ check_ko bar-alias@3 After bar-template-after.device
+
+ check_ko bar-alias@3 Requires bar-3-requires.device
+ check_ok bar-alias@3 Requires bar-alias-3-requires.device
+ check_ko bar-alias@3 Requires bar-template-requires.device
+ check_ok bar-alias@3 Requires bar-alias-template-requires.device
+ check_ok bar-alias@3 Requires yup-template-requires.device
+ check_ok bar-alias@3 Requires yup-3-requires.device
+
+ clear_units foo.service {bar,yup,bar-alias}@{,1,2,3}.service
+}
+
+test_alias_dropins () {
+ echo "Testing alias dropins..."
+
+ echo "*** test a wants b1 alias of b"
+ create_services test15-a test15-b
+ ln -sf test15-b.service /etc/systemd/system/test15-b1.service
+ ln -sf ../test15-b1.service /etc/systemd/system/test15-a.service.wants/
+ check_ok test15-a Wants test15-b.service
+ systemctl start test15-a
+ systemctl --quiet is-active test15-b
+ systemctl stop test15-a test15-b
+ rm /etc/systemd/system/test15-b1.service
+ clear_units test15-{a,b}.service
+
+ # Check that dependencies don't vary.
+ echo "*** test 2"
+ create_services test15-a test15-x test15-y
+ mkdir -p /etc/systemd/system/test15-a1.service.wants/
+ ln -sf test15-a.service /etc/systemd/system/test15-a1.service
+ ln -sf ../test15-x.service /etc/systemd/system/test15-a.service.wants/
+ ln -sf ../test15-y.service /etc/systemd/system/test15-a1.service.wants/
+ check_ok test15-a1 Wants test15-x.service # see [1]
+ check_ok test15-a1 Wants test15-y.service
+ systemctl start test15-a
+ check_ok test15-a1 Wants test15-x.service # see [2]
+ check_ok test15-a1 Wants test15-y.service
+ systemctl stop test15-a test15-x test15-y
+ rm /etc/systemd/system/test15-a1.service
+
+ clear_units test15-{a,x,y}.service
+}
+
+test_masked_dropins () {
+ echo "Testing masked dropins..."
+
+ create_services test15-a test15-b
+
+ # 'b' is masked for both deps
+ echo "*** test a wants,requires b is masked"
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b.service
+ check_ko test15-a Wants test15-b.service
+ check_ko test15-a Requires test15-b.service
+
+ # 'a' wants 'b' and 'b' is masked at a lower level
+ echo "*** test a wants b, mask override"
+ ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.wants/test15-b.service
+ ln -sf /dev/null /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
+ check_ok test15-a Wants test15-b.service
+
+ # 'a' wants 'b' and 'b' is masked at a higher level
+ echo "*** test a wants b, mask"
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
+ ln -sf ../test15-b.service /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
+ check_ko test15-a Wants test15-b.service
+
+ # 'a' is masked but has an override config file
+ echo "*** test a is masked but has an override"
+ create_services test15-a test15-b
+ ln -sf /dev/null /etc/systemd/system/test15-a.service
+ cat >/usr/lib/systemd/system/test15-a.service.d/override.conf <<EOF
+[Unit]
+After=test15-b.service
+EOF
+ check_ok test15-a UnitFileState masked
+
+ # 'b1' is an alias for 'b': masking 'b' dep should not influence 'b1' dep
+ echo "*** test a wants b, b1, and one is masked"
+ create_services test15-a test15-b
+ ln -sf test15-b.service /etc/systemd/system/test15-b1.service
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
+ ln -sf ../test15-b1.service /usr/lib/systemd/system/test15-a.service.wants/test15-b1.service
+ systemctl cat test15-a
+ systemctl show -p Wants,Requires test15-a
+ systemctl cat test15-b1
+ systemctl show -p Wants,Requires test15-b1
+ check_ok test15-a Wants test15-b.service
+ check_ko test15-a Wants test15-b1.service # the alias does not show up in the list of units
+ rm /etc/systemd/system/test15-b1.service
+
+ # 'b1' is an alias for 'b': masking 'b1' should not influence 'b' dep
+ echo "*** test a wants b, alias dep is masked"
+ create_services test15-a test15-b
+ ln -sf test15-b.service /etc/systemd/system/test15-b1.service
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b1.service
+ ln -sf ../test15-b.service /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
+ check_ok test15-a Wants test15-b.service
+ check_ko test15-a Wants test15-b1.service # the alias does not show up in the list of units
+ rm /etc/systemd/system/test15-b1.service
+
+ # 'a' has Wants=b.service but also has a masking
+ # dropin 'b': 'b' should still be pulled in.
+ echo "*** test a wants b both ways"
+ create_services test15-a test15-b
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
+ cat >/usr/lib/systemd/system/test15-a.service.d/wants-b.conf<<EOF
+[Unit]
+Wants=test15-b.service
+EOF
+ check_ok test15-a Wants test15-b.service
+
+ # mask a dropin that points to an nonexistent unit.
+ echo "*** test a wants nonexistent is masked"
+ create_services test15-a
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/nonexistent.service
+ ln -sf ../nonexistent.service /usr/lib/systemd/system/test15-a.service.requires/
+ check_ko test15-a Requires nonexistent.service
+
+ # 'b' is already loaded when 'c' pulls it in via a dropin but 'b' is
+ # masked at a higher level.
+ echo "*** test a wants b is masked"
+ create_services test15-a test15-b test15-c
+ ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
+ ln -sf ../test15-b.service /run/systemd/system/test15-c.service.requires/
+ ln -sf /dev/null /etc/systemd/system/test15-c.service.requires/test15-b.service
+ systemctl start test15-a
+ check_ko test15-c Requires test15-b.service
+ systemctl stop test15-a test15-b
+
+ # 'b' is already loaded when 'c' pulls it in via a dropin but 'b' is
+ # masked at a lower level.
+ echo "*** test a requires b is masked"
+ create_services test15-a test15-b test15-c
+ ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
+ ln -sf ../test15-b.service /etc/systemd/system/test15-c.service.requires/
+ ln -sf /dev/null /run/systemd/system/test15-c.service.requires/test15-b.service
+ systemctl start test15-a
+ check_ok test15-c Requires test15-b.service
+ systemctl stop test15-a test15-b
+
+ # 'a' requires 2 aliases of 'b' and one of them is a mask.
+ echo "*** test a requires alias of b, other alias masked"
+ create_services test15-a test15-b
+ ln -sf test15-b.service /etc/systemd/system/test15-b1.service
+ ln -sf test15-b.service /etc/systemd/system/test15-b2.service
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b1.service
+ ln -sf ../test15-b1.service /run/systemd/system/test15-a.service.requires/
+ ln -sf ../test15-b2.service /usr/lib/systemd/system/test15-a.service.requires/
+ check_ok test15-a Requires test15-b
+
+ # Same as above but now 'b' is masked.
+ echo "*** test a requires alias of b, b dep masked"
+ create_services test15-a test15-b
+ ln -sf test15-b.service /etc/systemd/system/test15-b1.service
+ ln -sf test15-b.service /etc/systemd/system/test15-b2.service
+ ln -sf ../test15-b1.service /run/systemd/system/test15-a.service.requires/
+ ln -sf ../test15-b2.service /usr/lib/systemd/system/test15-a.service.requires/
+ ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b.service
+ check_ok test15-a Requires test15-b
+
+ clear_units test15-{a,b}.service
+}
+
+test_invalid_dropins () {
+ echo "Testing invalid dropins..."
+ # Assertion failed on earlier versions, command exits unsuccessfully on later versions
+ systemctl cat nonexistent@.service || true
+ create_services a
+ systemctl daemon-reload
+ # Assertion failed on earlier versions, command exits unsuccessfully on later versions
+ systemctl cat a@.service || true
+ systemctl stop a
+ clear_units a.service
+ return 0
+}
+
+test_symlink_dropin_directory () {
+ # For issue #21920.
+ echo "Testing symlink drop-in directory..."
+ create_services test15-a
+ rmdir /{etc,run,usr/lib}/systemd/system/test15-a.service.d
+ mkdir -p /tmp/testsuite-15-test15-a-dropin-directory
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory /etc/systemd/system/test15-a.service.d
+ cat >/tmp/testsuite-15-test15-a-dropin-directory/override.conf <<EOF
+[Unit]
+Description=hogehoge
+EOF
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory-nonexistent /run/systemd/system/test15-a.service.d
+ touch /tmp/testsuite-15-test15-a-dropin-directory-regular
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory-regular /usr/lib/systemd/system/test15-a.service.d
+ check_ok test15-a Description hogehoge
+
+ clear_units test15-a.service
+}
+
+test_basic_dropins
+test_linked_units
+test_template_alias
+test_hierarchical_service_dropins
+test_hierarchical_slice_dropins
+test_transient_service_dropins
+test_transient_slice_dropins
+test_template_dropins
+test_alias_dropins
+test_masked_dropins
+test_invalid_dropins
+test_symlink_dropin_directory
+
+touch /testok
diff --git a/test/units/testsuite-16.service b/test/units/testsuite-16.service
new file mode 100644
index 0000000..d5494ae
--- /dev/null
+++ b/test/units/testsuite-16.service
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-16-EXTEND-TIMEOUT
+# Testsuite: Assess all other testsuite-*.services worked as expected
+
+Wants=success-all.service
+Wants=success-start.service
+Wants=success-runtime.service
+Wants=success-stop.service
+Wants=fail-start.service
+Wants=fail-stop.service
+Wants=fail-runtime.service
+StopWhenUnneeded=yes
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+Type=exec
+TimeoutStartSec=infinity
+ExecStartPre=/usr/lib/systemd/tests/testdata/units/%N.sh
+ExecStart=true
diff --git a/test/units/testsuite-16.sh b/test/units/testsuite-16.sh
new file mode 100755
index 0000000..9b8a7bd
--- /dev/null
+++ b/test/units/testsuite-16.sh
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+rm -f /test.log
+
+TESTLOG=/test.log.XXXXXXXX
+
+function wait_for()
+{
+ local service="${1:-wait_for: missing service argument}"
+ local result="${2:-success}"
+ local time="${3:-45}"
+
+ while [[ ! -f /${service}.terminated && ! -f /${service}.success && $time -gt 0 ]]; do
+ sleep 1
+ time=$((time - 1))
+ done
+
+ if [[ ! -f /${service}.${result} ]]; then
+ journalctl -u "${service/_/-}.service" >>"$TESTLOG"
+ fi
+}
+
+function wait_for_timeout()
+{
+ local unit="$1"
+ local time="$2"
+
+ while [[ $time -gt 0 ]]; do
+ if [[ "$(systemctl show --property=Result "$unit")" == "Result=timeout" ]]; then
+ return 0
+ fi
+
+ sleep 1
+ time=$((time - 1))
+ done
+
+ journalctl -u "$unit" >>"$TESTLOG"
+
+ return 1
+}
+
+# This checks all stages, start, runtime and stop, can be extended by
+# EXTEND_TIMEOUT_USEC
+
+wait_for success_all
+
+# These check that EXTEND_TIMEOUT_USEC that occurs at greater than the
+# extend timeout interval but less then the stage limit (TimeoutStartSec,
+# RuntimeMaxSec, TimeoutStopSec) still succeed.
+
+wait_for success_start
+wait_for success_runtime
+wait_for success_stop
+
+# These ensure that EXTEND_TIMEOUT_USEC will still timeout in the
+# appropriate stage, after the stage limit, when the EXTEND_TIMEOUT_USEC
+# message isn't sent within the extend timeout interval.
+
+wait_for fail_start startfail
+wait_for fail_stop stopfail
+wait_for fail_runtime runtimefail
+
+# These ensure that RuntimeMaxSec is honored for scope and service units
+# when they are created.
+runtime_max_sec=5
+
+systemd-run \
+ --property=RuntimeMaxSec=${runtime_max_sec}s \
+ -u runtime-max-sec-test-1.service \
+ /usr/bin/sh -c "while true; do sleep 1; done"
+wait_for_timeout runtime-max-sec-test-1.service $((runtime_max_sec + 2))
+
+systemd-run \
+ --property=RuntimeMaxSec=${runtime_max_sec}s \
+ --scope \
+ -u runtime-max-sec-test-2.scope \
+ /usr/bin/sh -c "while true; do sleep 1; done" &
+wait_for_timeout runtime-max-sec-test-2.scope $((runtime_max_sec + 2))
+
+# These ensure that RuntimeMaxSec is honored for scope and service
+# units if the value is changed and then the manager is reloaded.
+systemd-run \
+ -u runtime-max-sec-test-3.service \
+ /usr/bin/sh -c "while true; do sleep 1; done"
+mkdir -p /etc/systemd/system/runtime-max-sec-test-3.service.d/
+cat > /etc/systemd/system/runtime-max-sec-test-3.service.d/override.conf << EOF
+[Service]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-3.service $((runtime_max_sec + 2))
+
+systemd-run \
+ --scope \
+ -u runtime-max-sec-test-4.scope \
+ /usr/bin/sh -c "while true; do sleep 1; done" &
+
+# Wait until the unit is running to avoid race with creating the override.
+until systemctl is-active runtime-max-sec-test-4.scope; do
+ sleep 1
+done
+mkdir -p /etc/systemd/system/runtime-max-sec-test-4.scope.d/
+cat > /etc/systemd/system/runtime-max-sec-test-4.scope.d/override.conf << EOF
+[Scope]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-4.scope $((runtime_max_sec + 2))
+
+if [[ -f "$TESTLOG" ]]; then
+ # no mv
+ cp "$TESTLOG" /test.log
+ exit 1
+fi
+
+touch /testok
+exit 0
diff --git a/test/units/testsuite-17.01.sh b/test/units/testsuite-17.01.sh
new file mode 100755
index 0000000..44f36f5
--- /dev/null
+++ b/test/units/testsuite-17.01.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+mkdir -p /run/udev/rules.d/
+
+rm -f /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+udevadm trigger --settle /dev/sda
+
+while : ; do
+ (
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=foobar.service
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=waldo.service
+ systemctl show -p WantedBy foobar.service | grep -q -v sda
+ systemctl show -p WantedBy waldo.service | grep -q -v sda
+ ) && break
+
+ sleep .5
+done
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="block", KERNEL=="sda", OPTIONS="log_level=debug"
+ACTION!="remove", SUBSYSTEM=="block", KERNEL=="sda", ENV{SYSTEMD_WANTS}="foobar.service"
+EOF
+udevadm control --reload
+udevadm trigger --settle /dev/sda
+
+while : ; do
+ (
+ udevadm info /dev/sda | grep -q SYSTEMD_WANTS=foobar.service
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=waldo.service
+ systemctl show -p WantedBy foobar.service | grep -q sda
+ systemctl show -p WantedBy waldo.service | grep -q -v sda
+ ) && break
+
+ sleep .5
+done
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="block", KERNEL=="sda", OPTIONS="log_level=debug"
+ACTION!="remove", SUBSYSTEM=="block", KERNEL=="sda", ENV{SYSTEMD_WANTS}="waldo.service"
+EOF
+udevadm control --reload
+udevadm trigger --settle /dev/sda
+
+while : ; do
+ (
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=foobar.service
+ udevadm info /dev/sda | grep -q SYSTEMD_WANTS=waldo.service
+ systemctl show -p WantedBy foobar.service | grep -q -v sda
+ systemctl show -p WantedBy waldo.service | grep -q sda
+ ) && break
+
+ sleep .5
+done
+
+rm /run/udev/rules.d/50-testsuite.rules
+
+udevadm control --reload
+udevadm trigger --settle /dev/sda
+
+while : ; do
+ (
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=foobar.service
+ udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=waldo.service
+ systemctl show -p WantedBy foobar.service | grep -q -v sda
+ systemctl show -p WantedBy waldo.service | grep -q -v sda
+ ) && break
+
+ sleep .5
+done
+
+exit 0
diff --git a/test/units/testsuite-17.02.sh b/test/units/testsuite-17.02.sh
new file mode 100755
index 0000000..7abbce7
--- /dev/null
+++ b/test/units/testsuite-17.02.sh
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# disable shellcheck warning about '"aaa"' type quotation
+# shellcheck disable=SC2016
+
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+mkdir -p /run/udev/rules.d/
+
+# test for ID_RENAMING= udev property and device unit state
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+ACTION=="remove", GOTO="hoge_end"
+SUBSYSTEM!="net", GOTO="hoge_end"
+KERNEL!="hoge", GOTO="hoge_end"
+
+OPTIONS="log_level=debug"
+
+# emulate renaming
+ACTION=="online", ENV{ID_RENAMING}="1"
+
+LABEL="hoge_end"
+EOF
+
+udevadm control --log-priority=debug --reload --timeout=30
+
+ip link add hoge type dummy
+udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
+assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
+
+udevadm trigger --action=online --settle /sys/devices/virtual/net/hoge
+assert_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
+
+udevadm trigger --action=move --settle /sys/devices/virtual/net/hoge
+assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
+
+# test for renaming interface with NAME= (issue #25106)
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+ACTION!="add", GOTO="hoge_end"
+SUBSYSTEM!="net", GOTO="hoge_end"
+
+OPTIONS="log_level=debug"
+
+KERNEL=="hoge", NAME="foobar"
+KERNEL=="foobar", NAME="hoge"
+
+LABEL="hoge_end"
+EOF
+
+udevadm control --log-priority=debug --reload --timeout=30
+
+# FIXME(?): the 'add' uevent is broadcast as for 'foobar', instead of 'hoge'. Hence, we cannot use --settle here.
+# See issue #25115.
+udevadm trigger --action=add /sys/devices/virtual/net/hoge
+udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar
+assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/foobar)"
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done'
+
+udevadm trigger --action=add /sys/devices/virtual/net/foobar
+udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
+assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)"
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "inactive" ]]; do sleep .5; done'
+
+# cleanup
+rm -f /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload --timeout=30
+
+# test for renaming interface with an external tool (issue #16967)
+
+ip link set hoge name foobar
+udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done'
+
+ip link set foobar name hoge
+udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/hoge)" != "active" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "inactive" ]]; do sleep .5; done'
+timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "inactive" ]]; do sleep .5; done'
+
+# cleanup
+ip link del hoge
+
+exit 0
diff --git a/test/units/testsuite-17.03.sh b/test/units/testsuite-17.03.sh
new file mode 100755
index 0000000..3e41759
--- /dev/null
+++ b/test/units/testsuite-17.03.sh
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+
+TEST_RULE="/run/udev/rules.d/49-test.rules"
+KILL_PID=
+
+setup() {
+ mkdir -p "${TEST_RULE%/*}"
+ [[ -e /etc/udev/udev.conf ]] && cp -f /etc/udev/udev.conf /etc/udev/udev.conf.bak
+ # Don't bother storing the coredumps in journal for this particular test
+ mkdir -p /run/systemd/coredump.conf.d/
+ echo -ne "[Coredump]\nStorage=external\n" >/run/systemd/coredump.conf.d/99-storage-journal.conf
+
+ cat >"${TEST_RULE}" <<EOF
+ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", OPTIONS="log_level=debug"
+ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", PROGRAM=="/bin/sleep 60"
+EOF
+ cat >/etc/udev/udev.conf <<EOF
+event_timeout=10
+timeout_signal=SIGABRT
+EOF
+
+ systemctl restart systemd-udevd.service
+}
+
+teardown() {
+ set +e
+
+ if [[ -n "$KILL_PID" ]]; then
+ kill "$KILL_PID"
+ fi
+
+ rm -rf "$TMPDIR"
+ rm -f "$TEST_RULE"
+ [[ -e /etc/udev/udev.conf.bak ]] && mv -f /etc/udev/udev.conf.bak /etc/udev/udev.conf
+ rm /run/systemd/coredump.conf.d/99-storage-journal.conf
+ systemctl restart systemd-udevd.service
+}
+
+run_test() {
+ local since
+
+ since="$(date '+%F %T')"
+
+ TMPDIR=$(mktemp -d -p /tmp udev-tests.XXXXXX)
+ udevadm monitor --udev --property --subsystem-match=mem >"$TMPDIR"/monitor.txt &
+ KILL_PID="$!"
+
+ SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --action add /dev/null
+
+ for _ in {1..40}; do
+ if coredumpctl --since "$since" --no-legend --no-pager | grep /bin/udevadm ; then
+ kill "$KILL_PID"
+ KILL_PID=
+
+ cat "$TMPDIR"/monitor.txt
+ grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt
+ grep -q 'UDEV_WORKER_SIGNAL=6' "$TMPDIR"/monitor.txt
+ grep -q 'UDEV_WORKER_SIGNAL_NAME=ABRT' "$TMPDIR"/monitor.txt
+ return 0
+ fi
+ sleep .5
+ done
+
+ return 1
+}
+
+trap teardown EXIT
+
+setup
+run_test
+
+exit 0
diff --git a/test/units/testsuite-17.04.sh b/test/units/testsuite-17.04.sh
new file mode 100755
index 0000000..d1c3c85
--- /dev/null
+++ b/test/units/testsuite-17.04.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+mkdir -p /run/udev/rules.d/
+
+test ! -f /run/udev/tags/added/c1:3
+test ! -f /run/udev/tags/changed/c1:3
+udevadm info /dev/null | grep -E 'E: (TAGS|CURRENT_TAGS)=.*:(added|changed):' && exit 1
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="mem", KERNEL=="null", OPTIONS="log_level=debug"
+ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", TAG+="added"
+ACTION=="change", SUBSYSTEM=="mem", KERNEL=="null", TAG+="changed"
+EOF
+
+udevadm control --reload
+SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --settle --action add /dev/null
+
+test -f /run/udev/tags/added/c1:3
+test ! -f /run/udev/tags/changed/c1:3
+udevadm info /dev/null | grep -q 'E: TAGS=.*:added:.*'
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:added:.*'
+udevadm info /dev/null | grep -q 'E: TAGS=.*:changed:.*' && { echo 'unexpected TAGS='; exit 1; }
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:changed:.*' && { echo 'unexpected CURRENT_TAGS='; exit 1; }
+
+SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --settle --action change /dev/null
+
+test -f /run/udev/tags/added/c1:3
+test -f /run/udev/tags/changed/c1:3
+udevadm info /dev/null | grep -q 'E: TAGS=.*:added:.*'
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:added:.*' && { echo 'unexpected CURRENT_TAGS='; exit 1; }
+udevadm info /dev/null | grep -q 'E: TAGS=.*:changed:.*'
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:changed:.*'
+
+SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --settle --action add /dev/null
+
+test -f /run/udev/tags/added/c1:3
+test -f /run/udev/tags/changed/c1:3
+udevadm info /dev/null | grep -q 'E: TAGS=.*:added:.*'
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:added:.*'
+udevadm info /dev/null | grep -q 'E: TAGS=.*:changed:.*'
+udevadm info /dev/null | grep -q 'E: CURRENT_TAGS=.*:changed:.*' && { echo 'unexpected CURRENT_TAGS='; exit 1; }
+
+rm /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+
+exit 0
diff --git a/test/units/testsuite-17.05.sh b/test/units/testsuite-17.05.sh
new file mode 100755
index 0000000..60be31a
--- /dev/null
+++ b/test/units/testsuite-17.05.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+mkdir -p /run/udev/rules.d/
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="mem", KERNEL=="null", OPTIONS="log_level=debug"
+ACTION=="add", SUBSYSTEM=="mem", KERNEL=="null", IMPORT{program}="/bin/echo -e HOGE=aa\\\\x20\\\\x20\\\\x20bb\nFOO=\\\\x20aaa\\\\x20\n\n\n"
+EOF
+
+udevadm control --reload
+SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --settle --action add /dev/null
+
+test -f /run/udev/data/c1:3
+udevadm info /dev/null | grep -q 'E: HOGE=aa\\x20\\x20\\x20bb'
+udevadm info /dev/null | grep -q 'E: FOO=\\x20aaa\\x20'
+
+rm /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+
+exit 0
diff --git a/test/units/testsuite-17.06.sh b/test/units/testsuite-17.06.sh
new file mode 100755
index 0000000..6d83645
--- /dev/null
+++ b/test/units/testsuite-17.06.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# tests for udev watch
+
+function check_validity() {
+ local f ID_OR_HANDLE
+
+ for f in /run/udev/watch/*; do
+ ID_OR_HANDLE="$(readlink "$f")"
+ test -L "/run/udev/watch/${ID_OR_HANDLE}"
+ test "$(readlink "/run/udev/watch/${ID_OR_HANDLE}")" = "$(basename "$f")"
+ done
+}
+
+function check() {
+ for _ in {1..2}; do
+ systemctl restart systemd-udevd.service
+ udevadm control --ping
+ udevadm settle
+ check_validity
+
+ for _ in {1..2}; do
+ udevadm trigger -w --action add --subsystem-match=block
+ check_validity
+ done
+
+ for _ in {1..2}; do
+ udevadm trigger -w --action change --subsystem-match=block
+ check_validity
+ done
+ done
+}
+
+mkdir -p /run/udev/rules.d/
+
+cat >/run/udev/rules.d/00-debug.rules <<EOF
+SUBSYSTEM=="block", KERNEL=="sda*", OPTIONS="log_level=debug"
+EOF
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+ACTION=="add", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="watch"
+EOF
+
+check
+
+MAJOR=$(udevadm info /dev/sda | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')
+MINOR=$(udevadm info /dev/sda | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')
+test -L "/run/udev/watch/b${MAJOR}:${MINOR}"
+
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+ACTION=="change", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="nowatch"
+EOF
+
+check
+
+MAJOR=$(udevadm info /dev/sda | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')
+MINOR=$(udevadm info /dev/sda | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')
+test ! -e "/run/udev/watch/b${MAJOR}:${MINOR}"
+
+rm /run/udev/rules.d/00-debug.rules
+rm /run/udev/rules.d/50-testsuite.rules
+
+udevadm control --reload
+systemctl reset-failed systemd-udevd.service
+
+exit 0
diff --git a/test/units/testsuite-17.07.sh b/test/units/testsuite-17.07.sh
new file mode 100755
index 0000000..b4dfd90
--- /dev/null
+++ b/test/units/testsuite-17.07.sh
@@ -0,0 +1,205 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+wait_service_active() {(
+ set +ex
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
+ if systemctl --quiet is-active "${1?}"; then
+ return 0
+ fi
+ done
+ return 1
+)}
+
+wait_service_inactive() {(
+ set +ex
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
+ systemctl --quiet is-active "${1?}"
+ if [[ "$?" == "3" ]]; then
+ return 0
+ fi
+ done
+ return 1
+)}
+
+mkdir -p /run/systemd/system
+cat >/run/systemd/system/both.service <<EOF
+[Service]
+ExecStart=sleep 1000
+EOF
+
+cat >/run/systemd/system/on-add.service <<EOF
+[Service]
+ExecStart=sleep 1000
+EOF
+
+cat >/run/systemd/system/on-change.service <<EOF
+[Service]
+ExecStart=sleep 1000
+EOF
+
+systemctl daemon-reload
+
+mkdir -p /run/udev/rules.d/
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="net", KERNEL=="dummy9?", OPTIONS="log_level=debug"
+SUBSYSTEM=="net", KERNEL=="dummy9?", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="both.service", ENV{SYSTEMD_WANTS}+="on-add.service"
+SUBSYSTEM=="net", KERNEL=="dummy9?", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}+="both.service", ENV{SYSTEMD_WANTS}+="on-change.service"
+EOF
+
+udevadm control --reload
+
+# StopWhenUnneeded=no
+ip link add dummy99 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy99
+wait_service_active both.service
+wait_service_active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+systemctl stop both.service on-add.service
+
+udevadm trigger --action=change --settle /sys/class/net/dummy99
+udevadm info /sys/class/net/dummy99
+wait_service_active both.service
+assert_rc 3 systemctl --quiet is-active on-add.service
+wait_service_active on-change.service
+systemctl stop both.service on-change.service
+
+ip link del dummy99
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy99
+assert_rc 3 systemctl --quiet is-active both.service
+assert_rc 3 systemctl --quiet is-active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+# StopWhenUnneeded=yes
+cat >/run/systemd/system/both.service <<EOF
+[Unit]
+StopWhenUnneeded=yes
+
+[Service]
+ExecStart=sleep 1000
+Type=simple
+EOF
+
+cat >/run/systemd/system/on-add.service <<EOF
+[Unit]
+StopWhenUnneeded=yes
+
+[Service]
+ExecStart=sleep 1000
+Type=simple
+EOF
+
+cat >/run/systemd/system/on-change.service <<EOF
+[Unit]
+StopWhenUnneeded=yes
+
+[Service]
+ExecStart=echo changed
+RemainAfterExit=true
+Type=oneshot
+EOF
+
+systemctl daemon-reload
+
+# StopWhenUnneeded=yes (single device, only add event)
+ip link add dummy99 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy99
+wait_service_active both.service
+wait_service_active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+ip link del dummy99
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy99
+wait_service_inactive both.service
+wait_service_inactive on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+# StopWhenUnneeded=yes (single device, add and change event)
+ip link add dummy99 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy99
+wait_service_active both.service
+wait_service_active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+udevadm trigger --action=change --settle /sys/class/net/dummy99
+assert_rc 0 systemctl --quiet is-active both.service
+wait_service_inactive on-add.service
+wait_service_active on-change.service
+
+ip link del dummy99
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy99
+wait_service_inactive both.service
+assert_rc 3 systemctl --quiet is-active on-add.service
+wait_service_inactive on-change.service
+
+# StopWhenUnneeded=yes (multiple devices, only add events)
+ip link add dummy99 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy99
+wait_service_active both.service
+wait_service_active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+ip link add dummy98 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy98
+assert_rc 0 systemctl --quiet is-active both.service
+assert_rc 0 systemctl --quiet is-active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+ip link del dummy99
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy99
+assert_rc 0 systemctl --quiet is-active both.service
+assert_rc 0 systemctl --quiet is-active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+ip link del dummy98
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy98
+wait_service_inactive both.service
+wait_service_inactive on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+# StopWhenUnneeded=yes (multiple devices, add and change events)
+ip link add dummy99 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy99
+wait_service_active both.service
+wait_service_active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+ip link add dummy98 type dummy
+udevadm wait --settle --timeout=30 /sys/class/net/dummy98
+assert_rc 0 systemctl --quiet is-active both.service
+assert_rc 0 systemctl --quiet is-active on-add.service
+assert_rc 3 systemctl --quiet is-active on-change.service
+
+udevadm trigger --action=change --settle /sys/class/net/dummy99
+assert_rc 0 systemctl --quiet is-active both.service
+assert_rc 0 systemctl --quiet is-active on-add.service
+wait_service_active on-change.service
+
+ip link del dummy98
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy98
+assert_rc 0 systemctl --quiet is-active both.service
+wait_service_inactive on-add.service
+assert_rc 0 systemctl --quiet is-active on-change.service
+
+ip link del dummy99
+udevadm wait --settle --timeout=30 --removed /sys/class/net/dummy99
+wait_service_inactive both.service
+assert_rc 3 systemctl --quiet is-active on-add.service
+wait_service_inactive on-change.service
+
+# cleanup
+rm -f /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+
+rm -f /run/systemd/system/on-add.service
+rm -f /run/systemd/system/on-change.service
+systemctl daemon-reload
+
+exit 0
diff --git a/test/units/testsuite-17.08.sh b/test/units/testsuite-17.08.sh
new file mode 100755
index 0000000..a49a77d
--- /dev/null
+++ b/test/units/testsuite-17.08.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+# This is a test for issue #24518.
+
+mkdir -p /run/udev/rules.d/
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM=="mem", KERNEL=="null", OPTIONS="log_level=debug", TAG+="systemd"
+SUBSYSTEM=="mem", KERNEL=="null", ACTION=="add", SYMLINK+="test/symlink-to-null-on-add", ENV{SYSTEMD_ALIAS}+="/sys/test/alias-to-null-on-add"
+SUBSYSTEM=="mem", KERNEL=="null", ACTION=="change", SYMLINK+="test/symlink-to-null-on-change", ENV{SYSTEMD_ALIAS}+="/sys/test/alias-to-null-on-change"
+EOF
+
+udevadm control --reload
+
+udevadm trigger --settle --action add /dev/null
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
+
+ (
+ systemctl -q is-active /dev/test/symlink-to-null-on-add
+ ! systemctl -q is-active /dev/test/symlink-to-null-on-change
+ systemctl -q is-active /sys/test/alias-to-null-on-add
+ ! systemctl -q is-active /sys/test/alias-to-null-on-change
+ ) && break
+done
+assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-add
+assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-change
+assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-add
+assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change
+
+udevadm trigger --settle --action change /dev/null
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
+
+ (
+ ! systemctl -q is-active /dev/test/symlink-to-null-on-add
+ systemctl -q is-active /dev/test/symlink-to-null-on-change
+ ! systemctl -q is-active /sys/test/alias-to-null-on-add
+ systemctl -q is-active /sys/test/alias-to-null-on-change
+ ) && break
+done
+assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-add
+assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-change
+assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-add
+assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-change
+
+udevadm trigger --settle --action add /dev/null
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
+
+ (
+ systemctl -q is-active /dev/test/symlink-to-null-on-add
+ ! systemctl -q is-active /dev/test/symlink-to-null-on-change
+ systemctl -q is-active /sys/test/alias-to-null-on-add
+ ! systemctl -q is-active /sys/test/alias-to-null-on-change
+ ) && break
+done
+assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-add
+assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-change
+assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-add
+assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change
+
+# cleanup
+rm -f /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+
+exit 0
diff --git a/test/units/testsuite-17.09.sh b/test/units/testsuite-17.09.sh
new file mode 100755
index 0000000..31fc9d6
--- /dev/null
+++ b/test/units/testsuite-17.09.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+# This is a test for issue #24987.
+
+mkdir -p /run/udev/rules.d/
+cat >/run/udev/rules.d/50-testsuite.rules <<EOF
+SUBSYSTEM!="mem", GOTO="test-end"
+KERNEL!="null", GOTO="test-end"
+ACTION=="remove", GOTO="test-end"
+
+# add 100 * 100byte of properties
+$(for i in {1..100}; do printf 'ENV{XXX%03i}="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"\n' "$i"; done)
+
+LABEL="test-end"
+EOF
+
+udevadm control --reload
+
+TMPDIR=$(mktemp -d -p /tmp udev-tests.XXXXXX)
+SYSTEMD_LOG_LEVEL=debug udevadm monitor --udev --property --subsystem-match=mem >"$TMPDIR"/monitor.txt 2>&1 &
+KILL_PID="$!"
+
+FOUND=
+for _ in {1..40}; do
+ if grep -F 'UDEV - the event which udev sends out after rule processing' "$TMPDIR"/monitor.txt; then
+ FOUND=1
+ break
+ fi
+ sleep .5
+done
+[[ -n "$FOUND" ]]
+
+udevadm trigger --verbose --settle --action add /dev/null
+
+FOUND=
+for _ in {1..40}; do
+ if ! grep -e 'UDEV *\[[0-9.]*\] *add *\/devices\/virtual\/mem\/null (mem)' "$TMPDIR"/monitor.txt; then
+ sleep .5
+ continue
+ fi
+
+ FOUND=1
+ for i in {1..100}; do
+ if ! grep -F "$(printf 'XXX%03i=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' "$i")" "$TMPDIR"/monitor.txt; then
+ FOUND=
+ break
+ fi
+ done
+ if [[ -n "$FOUND" ]]; then
+ break;
+ fi
+
+ sleep .5
+done
+[[ -n "$FOUND" ]]
+
+# cleanup
+rm -f /run/udev/rules.d/50-testsuite.rules
+udevadm control --reload
+
+kill "$KILL_PID"
+rm -rf "$TMPDIR"
+
+exit 0
diff --git a/test/units/testsuite-17.service b/test/units/testsuite-17.service
new file mode 100644
index 0000000..d218d72
--- /dev/null
+++ b/test/units/testsuite-17.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-17-UDEV
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-17.sh b/test/units/testsuite-17.sh
new file mode 100755
index 0000000..b389875
--- /dev/null
+++ b/test/units/testsuite-17.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+udevadm settle
+
+for t in "${0%.sh}".*.sh; do
+ echo "Running $t"; ./"$t"
+done
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-18.service b/test/units/testsuite-18.service
new file mode 100644
index 0000000..16d90a1
--- /dev/null
+++ b/test/units/testsuite-18.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-18-FAILUREACTION
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-18.sh b/test/units/testsuite-18.sh
new file mode 100755
index 0000000..44b792f
--- /dev/null
+++ b/test/units/testsuite-18.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-run --wait -p FailureAction=poweroff true
+(! systemd-run --wait -p SuccessAction=poweroff false)
+
+if ! test -f /firstphase ; then
+ echo OK >/firstphase
+ systemd-run --wait -p SuccessAction=reboot true
+else
+ echo OK >/testok
+ systemd-run --wait -p FailureAction=poweroff false
+fi
+
+sleep infinity
diff --git a/test/units/testsuite-19.service b/test/units/testsuite-19.service
new file mode 100644
index 0000000..9ee5fc9
--- /dev/null
+++ b/test/units/testsuite-19.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-19-DELEGATE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-19.sh b/test/units/testsuite-19.sh
new file mode 100755
index 0000000..1e705ea
--- /dev/null
+++ b/test/units/testsuite-19.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+test_scope_unpriv_delegation() {
+ useradd test ||:
+ trap "userdel -r test" RETURN
+
+ systemd-run --uid=test -p User=test -p Delegate=yes --slice workload.slice --unit test-workload0.scope --scope \
+ test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \
+ -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \
+ -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control
+}
+
+if grep -q cgroup2 /proc/filesystems ; then
+ systemd-run --wait --unit=test-0.service -p "DynamicUser=1" -p "Delegate=" \
+ test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \
+ -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \
+ -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control
+
+ systemd-run --wait --unit=test-1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+ grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers
+
+ systemd-run --wait --unit=test-2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+ grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers
+
+ # "io" is not among the controllers enabled by default for all units, verify that
+ grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
+
+ # Run a service with "io" enabled, and verify it works
+ systemd-run --wait --unit=test-3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice" \
+ grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers
+
+ # We want to check if "io" is removed again from the controllers
+ # list. However, PID 1 (rightfully) does this asynchronously. In order
+ # to force synchronization on this, let's start a short-lived service
+ # which requires PID 1 to refresh the cgroup tree, so that we can
+ # verify that this all works.
+ systemd-run --wait --unit=test-4.service true
+
+ # And now check again, "io" should have vanished
+ grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
+
+ # Check that unprivileged delegation works for scopes
+ test_scope_unpriv_delegation
+
+else
+ echo "Skipping TEST-19-DELEGATE, as the kernel doesn't actually support cgroup v2" >&2
+fi
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-20.service b/test/units/testsuite-20.service
new file mode 100644
index 0000000..4228d0b
--- /dev/null
+++ b/test/units/testsuite-20.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-20-MAINPIDGAMES
+Before=getty-pre.target
+Wants=getty-pre.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+NotifyAccess=all
diff --git a/test/units/testsuite-20.sh b/test/units/testsuite-20.sh
new file mode 100755
index 0000000..6ce992f
--- /dev/null
+++ b/test/units/testsuite-20.sh
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Start a test process inside of our own cgroup
+sleep infinity &
+INTERNALPID=$!
+disown
+
+# Start a test process outside of our own cgroup
+systemd-run -p DynamicUser=1 --unit=test-sleep.service /bin/sleep infinity
+EXTERNALPID="$(systemctl show -P MainPID test-sleep.service)"
+
+# Update our own main PID to the external test PID, this should work
+systemd-notify MAINPID="$EXTERNALPID"
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq "$EXTERNALPID"
+
+# Update our own main PID to the internal test PID, this should work, too
+systemd-notify MAINPID=$INTERNALPID
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq "$INTERNALPID"
+
+# Update it back to our own PID, this should also work
+systemd-notify MAINPID=$$
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Try to set it to PID 1, which it should ignore, because that's the manager
+systemd-notify MAINPID=1
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Try to set it to PID 0, which is invalid and should be ignored
+systemd-notify MAINPID=0
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Try to set it to a valid but non-existing PID, which should be ignored. (Note
+# that we set the PID to a value well above any known /proc/sys/kernel/pid_max,
+# which means we can be pretty sure it doesn't exist by coincidence)
+systemd-notify MAINPID=1073741824
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Change it again to the external PID, without privileges this time. This should be ignored, because the PID is from outside of our cgroup and we lack privileges.
+systemd-notify --uid=1000 MAINPID="$EXTERNALPID"
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+# Change it again to the internal PID, without privileges this time. This should work, as the process is on our cgroup, and that's enough even if we lack privileges.
+systemd-notify --uid=1000 MAINPID="$INTERNALPID"
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq "$INTERNALPID"
+
+# Update it back to our own PID, this should also work
+systemd-notify --uid=1000 MAINPID=$$
+test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
+
+cat >/tmp/test-mainpid.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+set -o pipefail
+
+# Create a number of children, and make one the main one
+sleep infinity &
+disown
+
+sleep infinity &
+MAINPID=\$!
+disown
+
+sleep infinity &
+disown
+
+echo \$MAINPID >/run/mainpidsh/pid
+EOF
+chmod +x /tmp/test-mainpid.sh
+
+systemd-run --unit=test-mainpidsh.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh -p PIDFile=/run/mainpidsh/pid /tmp/test-mainpid.sh
+test "$(systemctl show -P MainPID test-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)"
+
+cat >/tmp/test-mainpid2.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+set -o pipefail
+
+# Create a number of children, and make one the main one
+sleep infinity &
+disown
+
+sleep infinity &
+MAINPID=\$!
+disown
+
+sleep infinity &
+disown
+
+echo \$MAINPID >/run/mainpidsh2/pid
+chown 1001:1001 /run/mainpidsh2/pid
+EOF
+chmod +x /tmp/test-mainpid2.sh
+
+systemd-run --unit=test-mainpidsh2.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh2 -p PIDFile=/run/mainpidsh2/pid /tmp/test-mainpid2.sh
+test "$(systemctl show -P MainPID test-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)"
+
+cat >/dev/shm/test-mainpid3.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+set -o pipefail
+
+sleep infinity &
+disown
+
+sleep infinity &
+disown
+
+sleep infinity &
+disown
+
+# Let's try to play games, and link up a privileged PID file
+ln -s ../mainpidsh/pid /run/mainpidsh3/pid
+
+# Quick assertion that the link isn't dead
+test -f /run/mainpidsh3/pid
+EOF
+chmod 755 /dev/shm/test-mainpid3.sh
+
+# This has to fail, as we shouldn't accept the dangerous PID file, and then
+# inotify-wait on it to be corrected which we never do.
+(! systemd-run \
+ --unit=test-mainpidsh3.service \
+ -p StandardOutput=tty \
+ -p StandardError=tty \
+ -p Type=forking \
+ -p RuntimeDirectory=mainpidsh3 \
+ -p PIDFile=/run/mainpidsh3/pid \
+ -p DynamicUser=1 \
+ -p TimeoutStartSec=2s \
+ /dev/shm/test-mainpid3.sh)
+
+# Test that this failed due to timeout, and not some other error
+test "$(systemctl show -P Result test-mainpidsh3.service)" = timeout
+
+# Test that scope units work
+systemd-run --scope --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
+
+# Test that user scope units work as well
+
+runas() {
+ declare userid=$1
+ shift
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
+}
+
+systemctl start user@4711.service
+runas testuser systemd-run --scope --user --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-21.service b/test/units/testsuite-21.service
new file mode 100644
index 0000000..a5f77d0
--- /dev/null
+++ b/test/units/testsuite-21.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Fuzz our D-Bus interfaces with dfuzzer
+After=dbus.service multi-user.target
+Wants=dbus.service multi-user.target
+
+[Service]
+ExecStartPre=rm -f /failed /skipped /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-21.sh b/test/units/testsuite-21.sh
new file mode 100755
index 0000000..36f647c
--- /dev/null
+++ b/test/units/testsuite-21.sh
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Save the end.service state before we start fuzzing, as it might get changed
+# on the fly by one of the fuzzers
+systemctl list-jobs | grep -F 'end.service' && SHUTDOWN_AT_EXIT=1 || SHUTDOWN_AT_EXIT=0
+
+# shellcheck disable=SC2317
+at_exit() {
+ set +e
+ # We have to call the end.service/poweroff explicitly even if it's specified on
+ # the kernel cmdline via systemd.wants=end.service, since dfuzzer calls
+ # org.freedesktop.systemd1.Manager.ClearJobs() which drops the service
+ # from the queue
+ if [[ $SHUTDOWN_AT_EXIT -ne 0 ]] && ! systemctl poweroff; then
+ # PID1 is down let's try to save the journal
+ journalctl --sync # journal can be down as well so let's ignore exit codes here
+ systemctl -ff poweroff # sync() and reboot(RB_POWER_OFF)
+ fi
+}
+
+trap at_exit EXIT
+
+systemctl log-level info
+
+# FIXME: systemd-run doesn't play well with daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27204
+sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf
+
+# TODO
+# * check for possibly newly introduced buses?
+BUS_LIST=(
+ org.freedesktop.home1
+ org.freedesktop.hostname1
+ org.freedesktop.import1
+ org.freedesktop.locale1
+ org.freedesktop.login1
+ org.freedesktop.machine1
+ org.freedesktop.portable1
+ org.freedesktop.resolve1
+ org.freedesktop.systemd1
+ org.freedesktop.timedate1
+)
+
+# systemd-oomd requires PSI
+if tail -n +1 /proc/pressure/{cpu,io,memory}; then
+ BUS_LIST+=(
+ org.freedesktop.oom1
+ )
+fi
+
+# Some services require specific conditions:
+# - systemd-timesyncd can't run in a container
+# - systemd-networkd can run in a container if it has CAP_NET_ADMIN capability
+if ! systemd-detect-virt --container; then
+ BUS_LIST+=(
+ org.freedesktop.network1
+ org.freedesktop.timesync1
+ )
+elif busctl introspect org.freedesktop.network1 / &>/dev/null; then
+ BUS_LIST+=(
+ org.freedesktop.network1
+ )
+fi
+
+SESSION_BUS_LIST=(
+ org.freedesktop.systemd1
+)
+
+# Maximum payload size generated by dfuzzer (in bytes) - default: 50K
+PAYLOAD_MAX=50000
+# Tweak the maximum payload size if we're running under sanitizers, since
+# with larger payloads we start hitting reply timeouts
+if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
+ PAYLOAD_MAX=10000 # 10K
+fi
+
+# Overmount /var/lib/machines with a size-limited tmpfs, as fuzzing
+# the org.freedesktop.machine1 stuff makes quite a mess
+mount -t tmpfs -o size=50M tmpfs /var/lib/machines
+
+# Fuzz both the system and the session buses (where applicable)
+for bus in "${BUS_LIST[@]}"; do
+ echo "Bus: $bus (system)"
+ systemd-run --pipe --wait \
+ -- dfuzzer -b "$PAYLOAD_MAX" -n "$bus"
+
+ # Let's reload the systemd daemon to test (de)serialization as well
+ systemctl daemon-reload
+ # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+ systemctl daemon-reexec
+done
+
+umount /var/lib/machines
+
+for bus in "${SESSION_BUS_LIST[@]}"; do
+ echo "Bus: $bus (session)"
+ systemd-run --machine 'testuser@.host' --user --pipe --wait \
+ -- dfuzzer -b "$PAYLOAD_MAX" -n "$bus"
+
+ # Let's reload the systemd user daemon to test (de)serialization as well
+ systemctl --machine 'testuser@.host' --user daemon-reload
+ # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+ systemctl --machine 'testuser@.host' --user daemon-reexec
+done
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-22.01.sh b/test/units/testsuite-22.01.sh
new file mode 100755
index 0000000..2276b75
--- /dev/null
+++ b/test/units/testsuite-22.01.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# With "e" don't attempt to set permissions when file doesn't exist, see
+# https://github.com/systemd/systemd/pull/6682.
+set -eux
+set -o pipefail
+
+rm -fr /tmp/test
+
+echo "e /tmp/test - root root 1d" | systemd-tmpfiles --create -
+
+test ! -e /tmp/test
diff --git a/test/units/testsuite-22.02.sh b/test/units/testsuite-22.02.sh
new file mode 100755
index 0000000..de28fa7
--- /dev/null
+++ b/test/units/testsuite-22.02.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Basic tests for types creating directories
+set -eux
+set -o pipefail
+
+rm -fr /tmp/{C,d,D,e}
+mkdir /tmp/{C,d,D,e}
+
+#
+# 'd'
+#
+mkdir /tmp/d/2
+chmod 777 /tmp/d/2
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/d/1 0755 daemon daemon - -
+d /tmp/d/2 0755 daemon daemon - -
+EOF
+
+test -d /tmp/d/1
+test "$(stat -c %U:%G:%a /tmp/d/1)" = "daemon:daemon:755"
+
+test -d /tmp/d/2
+test "$(stat -c %U:%G:%a /tmp/d/2)" = "daemon:daemon:755"
+
+#
+# 'D'
+#
+mkdir /tmp/D/2
+chmod 777 /tmp/D/2
+touch /tmp/D/2/foo
+
+systemd-tmpfiles --create - <<EOF
+D /tmp/D/1 0755 daemon daemon - -
+D /tmp/D/2 0755 daemon daemon - -
+EOF
+
+test -d /tmp/D/1
+test "$(stat -c %U:%G:%a /tmp/D/1)" = "daemon:daemon:755"
+
+test -d /tmp/D/2
+test "$(stat -c %U:%G:%a /tmp/D/2)" = "daemon:daemon:755"
+
+systemd-tmpfiles --remove - <<EOF
+D /tmp/D/2 0755 daemon daemon - -
+EOF
+
+# the content of '2' should be removed
+test "$(echo /tmp/D/2/*)" = "/tmp/D/2/*"
+
+#
+# 'e'
+#
+mkdir -p /tmp/e/2/{d1,d2}
+chmod 777 /tmp/e/2
+chmod 777 /tmp/e/2/d*
+
+systemd-tmpfiles --create - <<EOF
+e /tmp/e/1 0755 daemon daemon - -
+e /tmp/e/2/* 0755 daemon daemon - -
+EOF
+
+test ! -d /tmp/e/1
+
+test -d /tmp/e/2
+test "$(stat -c %U:%G:%a /tmp/e/2)" = "root:root:777"
+
+test -d /tmp/e/2/d1
+test "$(stat -c %U:%G:%a /tmp/e/2/d1)" = "daemon:daemon:755"
+test -d /tmp/e/2/d2
+test "$(stat -c %U:%G:%a /tmp/e/2/d2)" = "daemon:daemon:755"
+
+# 'e' operates on directories only
+mkdir -p /tmp/e/3/{d1,d2}
+chmod 777 /tmp/e/3
+chmod 777 /tmp/e/3/d*
+touch /tmp/e/3/f1
+chmod 644 /tmp/e/3/f1
+
+systemd-tmpfiles --create - <<EOF
+e /tmp/e/3/* 0755 daemon daemon - -
+EOF
+
+# the directories should have been processed although systemd-tmpfiles failed
+# previously due to the presence of a file.
+test -d /tmp/e/3/d1
+test "$(stat -c %U:%G:%a /tmp/e/3/d1)" = "daemon:daemon:755"
+test -d /tmp/e/3/d2
+test "$(stat -c %U:%G:%a /tmp/e/3/d2)" = "daemon:daemon:755"
+
+test -f /tmp/e/3/f1
+test "$(stat -c %U:%G:%a /tmp/e/3/f1)" = "root:root:644"
+
+#
+# 'C'
+#
+
+mkdir /tmp/C/{0,1,2,3}-origin
+touch /tmp/C/{1,2,3}-origin/f1
+chmod 755 /tmp/C/{1,2,3}-origin/f1
+
+mkdir /tmp/C/{2,3}
+touch /tmp/C/3/f1
+
+systemd-tmpfiles --create - <<EOF
+C /tmp/C/1 0755 daemon daemon - /tmp/C/1-origin
+C /tmp/C/2 0755 daemon daemon - /tmp/C/2-origin
+EOF
+
+test -d /tmp/C/1
+test "$(stat -c %U:%G:%a /tmp/C/1/f1)" = "daemon:daemon:755"
+test -d /tmp/C/2
+test "$(stat -c %U:%G:%a /tmp/C/2/f1)" = "daemon:daemon:755"
+
+systemd-tmpfiles --create - <<EOF
+C /tmp/C/3 0755 daemon daemon - /tmp/C/3-origin
+C /tmp/C/4 0755 daemon daemon - /tmp/C/definitely-missing
+EOF
+
+test "$(stat -c %U:%G:%a /tmp/C/3/f1)" = "root:root:644"
+test ! -e /tmp/C/4
+
+# Check that %U expands to 0, both in the path and in the argument.
+home='/tmp/C'
+systemd-tmpfiles --create - <<EOF
+C $home/%U - - - - $home/%U-origin
+EOF
+
+test -d "$home/0"
+
+# Check that %h expands to $home, both in the path and in the argument.
+HOME="$home" \
+systemd-tmpfiles --create - <<EOF
+C %h/5 - - - - %h/3-origin
+EOF
+
+test -f "$home/5/f1"
+
+# Check that %h in the path is expanded, but
+# the result of this expansion is not expanded once again.
+root='/tmp/C/6'
+home='/%U'
+mkdir -p "$root/usr/share/factory$home"
+HOME="$home" \
+systemd-tmpfiles --create --root="$root" - <<EOF
+C %h - - - -
+EOF
+
+test -d "$root$home"
diff --git a/test/units/testsuite-22.03.sh b/test/units/testsuite-22.03.sh
new file mode 100755
index 0000000..6fce4c0
--- /dev/null
+++ b/test/units/testsuite-22.03.sh
@@ -0,0 +1,246 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Basic tests for types creating/writing files
+set -eux
+set -o pipefail
+
+rm -fr /tmp/{f,F,w}
+mkdir /tmp/{f,F,w}
+touch /tmp/file-owned-by-root
+
+#
+# 'f'
+#
+systemd-tmpfiles --create - <<EOF
+f /tmp/f/1 0644 - - - -
+f /tmp/f/2 0644 - - - This string should be written
+EOF
+
+### '1' should exist and be empty
+test -f /tmp/f/1; test ! -s /tmp/f/1
+test "$(stat -c %U:%G:%a /tmp/f/1)" = "root:root:644"
+
+test "$(stat -c %U:%G:%a /tmp/f/2)" = "root:root:644"
+test "$(< /tmp/f/2)" = "This string should be written"
+
+### The perms are supposed to be updated even if the file already exists.
+systemd-tmpfiles --create - <<EOF
+f /tmp/f/1 0666 daemon daemon - This string should not be written
+EOF
+
+# file should be empty
+test ! -s /tmp/f/1
+test "$(stat -c %U:%G:%a /tmp/f/1)" = "daemon:daemon:666"
+
+### But we shouldn't try to set perms on an existing file which is not a
+### regular one.
+mkfifo /tmp/f/fifo
+chmod 644 /tmp/f/fifo
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/f/fifo 0666 daemon daemon - This string should not be written
+EOF
+
+test -p /tmp/f/fifo
+test "$(stat -c %U:%G:%a /tmp/f/fifo)" = "root:root:644"
+
+### 'f' should not follow symlinks.
+ln -s missing /tmp/f/dangling
+ln -s /tmp/file-owned-by-root /tmp/f/symlink
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/f/dangling 0644 daemon daemon - -
+f /tmp/f/symlink 0644 daemon daemon - -
+EOF
+test ! -e /tmp/f/missing
+test "$(stat -c %U:%G:%a /tmp/file-owned-by-root)" = "root:root:644"
+
+### Handle read-only filesystem gracefully: we shouldn't fail if the target
+### already exists and have the correct perms.
+mkdir /tmp/f/rw-fs
+mkdir /tmp/f/ro-fs
+
+touch /tmp/f/rw-fs/foo
+chmod 644 /tmp/f/rw-fs/foo
+
+mount -o bind,ro /tmp/f/rw-fs /tmp/f/ro-fs
+
+systemd-tmpfiles --create - <<EOF
+f /tmp/f/ro-fs/foo 0644 - - - - This string should not be written
+EOF
+test -f /tmp/f/ro-fs/foo; test ! -s /tmp/f/ro-fs/foo
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/f/ro-fs/foo 0666 - - - -
+EOF
+test "$(stat -c %U:%G:%a /tmp/f/fifo)" = "root:root:644"
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/f/ro-fs/bar 0644 - - - -
+EOF
+test ! -e /tmp/f/ro-fs/bar
+
+### 'f' shouldn't follow unsafe paths.
+mkdir /tmp/f/daemon
+ln -s /root /tmp/f/daemon/unsafe-symlink
+chown -R --no-dereference daemon:daemon /tmp/f/daemon
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/f/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
+EOF
+test ! -e /tmp/f/daemon/unsafe-symlink/exploit
+
+#
+# 'F'
+#
+echo "This should be truncated" >/tmp/F/truncated
+echo "This should be truncated" >/tmp/F/truncated-with-content
+
+systemd-tmpfiles --create - <<EOF
+F /tmp/F/created 0644 - - - -
+F /tmp/F/created-with-content 0644 - - - new content
+F /tmp/F/truncated 0666 daemon daemon - -
+F /tmp/F/truncated-with-content 0666 daemon daemon - new content
+EOF
+
+test -f /tmp/F/created; test ! -s /tmp/F/created
+test -f /tmp/F/created-with-content
+test "$(< /tmp/F/created-with-content)" = "new content"
+test -f /tmp/F/truncated; test ! -s /tmp/F/truncated
+test "$(stat -c %U:%G:%a /tmp/F/truncated)" = "daemon:daemon:666"
+test -s /tmp/F/truncated-with-content
+test "$(stat -c %U:%G:%a /tmp/F/truncated-with-content)" = "daemon:daemon:666"
+
+### We shouldn't try to truncate anything but regular files since the behavior is
+### unspecified in the other cases.
+mkfifo /tmp/F/fifo
+
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/fifo 0644 - - - -
+EOF
+
+test -p /tmp/F/fifo
+
+### 'F' should not follow symlinks.
+ln -s missing /tmp/F/dangling
+ln -s /tmp/file-owned-by-root /tmp/F/symlink
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/F/dangling 0644 daemon daemon - -
+f /tmp/F/symlink 0644 daemon daemon - -
+EOF
+test ! -e /tmp/F/missing
+test "$(stat -c %U:%G:%a /tmp/file-owned-by-root)" = "root:root:644"
+
+### Handle read-only filesystem gracefully: we shouldn't fail if the target
+### already exists and is empty.
+mkdir /tmp/F/rw-fs
+mkdir /tmp/F/ro-fs
+
+touch /tmp/F/rw-fs/foo
+chmod 644 /tmp/F/rw-fs/foo
+
+mount -o bind,ro /tmp/F/rw-fs /tmp/F/ro-fs
+
+systemd-tmpfiles --create - <<EOF
+F /tmp/F/ro-fs/foo 0644 - - - -
+EOF
+test -f /tmp/F/ro-fs/foo; test ! -s /tmp/F/ro-fs/foo
+
+echo "truncating is not allowed anymore" >/tmp/F/rw-fs/foo
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/ro-fs/foo 0644 - - - -
+EOF
+
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/ro-fs/foo 0644 - - - - This string should not be written
+EOF
+test -f /tmp/F/ro-fs/foo
+grep -q 'truncating is not allowed' /tmp/F/ro-fs/foo
+
+# Trying to change the perms should fail.
+: >/tmp/F/rw-fs/foo
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/ro-fs/foo 0666 - - - -
+EOF
+test "$(stat -c %U:%G:%a /tmp/F/ro-fs/foo)" = "root:root:644"
+
+### Try to create a new file.
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/ro-fs/bar 0644 - - - -
+EOF
+test ! -e /tmp/F/ro-fs/bar
+
+### 'F' shouldn't follow unsafe paths.
+mkdir /tmp/F/daemon
+ln -s /root /tmp/F/daemon/unsafe-symlink
+chown -R --no-dereference daemon:daemon /tmp/F/daemon
+
+(! systemd-tmpfiles --create -) <<EOF
+F /tmp/F/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
+EOF
+test ! -e /tmp/F/daemon/unsafe-symlink/exploit
+
+#
+# 'w'
+#
+touch /tmp/w/overwritten
+touch /tmp/w/appended
+
+### nop if the target does not exist.
+systemd-tmpfiles --create - <<EOF
+w /tmp/w/unexistent 0644 - - - new content
+EOF
+test ! -e /tmp/w/unexistent
+
+### no argument given -> fails.
+(! systemd-tmpfiles --create -) <<EOF
+w /tmp/w/unexistent 0644 - - - -
+EOF
+
+### write into an empty file.
+systemd-tmpfiles --create - <<EOF
+w /tmp/w/overwritten 0644 - - - old content
+EOF
+test -f /tmp/w/overwritten
+test "$(< /tmp/w/overwritten)" = "old content"
+
+### old content is overwritten
+systemd-tmpfiles --create - <<EOF
+w /tmp/w/overwritten 0644 - - - new content
+EOF
+test -f /tmp/w/overwritten
+test "$(< /tmp/w/overwritten)" = "new content"
+
+### append lines
+systemd-tmpfiles --create - <<EOF
+w+ /tmp/w/appended 0644 - - - 1
+w+ /tmp/w/appended 0644 - - - 2\n
+w+ /tmp/w/appended 0644 - - - 3
+EOF
+test -f /tmp/w/appended
+test "$(< /tmp/w/appended)" = "$(echo -ne '12\n3')"
+
+### writing into an 'exotic' file should be allowed.
+systemd-tmpfiles --create - <<EOF
+w /dev/null - - - - new content
+EOF
+
+### 'w' follows symlinks
+ln -s ./overwritten /tmp/w/symlink
+systemd-tmpfiles --create - <<EOF
+w /tmp/w/symlink - - - - $(readlink -e /tmp/w/symlink)
+EOF
+readlink -e /tmp/w/symlink
+test "$(< /tmp/w/overwritten)" = "/tmp/w/overwritten"
+
+### 'w' shouldn't follow unsafe paths.
+mkdir /tmp/w/daemon
+ln -s /root /tmp/w/daemon/unsafe-symlink
+chown -R --no-dereference daemon:daemon /tmp/w/daemon
+
+(! systemd-tmpfiles --create -) <<EOF
+f /tmp/w/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
+EOF
+test ! -e /tmp/w/daemon/unsafe-symlink/exploit
diff --git a/test/units/testsuite-22.04.sh b/test/units/testsuite-22.04.sh
new file mode 100755
index 0000000..7bf2b28
--- /dev/null
+++ b/test/units/testsuite-22.04.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Basic tests for types creating fifos
+set -eux
+set -o pipefail
+
+rm -fr /tmp/p
+mkdir /tmp/p
+touch /tmp/p/f1
+
+systemd-tmpfiles --create - <<EOF
+p /tmp/p/fifo1 0666 - - - -
+EOF
+
+test -p /tmp/p/fifo1
+test "$(stat -c %U:%G:%a /tmp/p/fifo1)" = "root:root:666"
+
+# Refuse to overwrite an existing file. Error is not propagated.
+systemd-tmpfiles --create - <<EOF
+p /tmp/p/f1 0666 - - - -
+EOF
+
+test -f /tmp/p/f1
+
+# unless '+' prefix is used
+systemd-tmpfiles --create - <<EOF
+p+ /tmp/p/f1 0666 - - - -
+EOF
+
+test -p /tmp/p/f1
+test "$(stat -c %U:%G:%a /tmp/p/f1)" = "root:root:666"
+
+#
+# Must be fixed
+#
+# mkdir /tmp/p/daemon
+# #ln -s /root /tmp/F/daemon/unsafe-symlink
+# chown -R --no-dereference daemon:daemon /tmp/p/daemon
+#
+# systemd-tmpfiles --create - <<EOF
+# p /tmp/p/daemon/fifo2 0666 daemon daemon - -
+# EOF
diff --git a/test/units/testsuite-22.05.sh b/test/units/testsuite-22.05.sh
new file mode 100755
index 0000000..cde9b5d
--- /dev/null
+++ b/test/units/testsuite-22.05.sh
@@ -0,0 +1,45 @@
+#! /bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+rm -fr /tmp/{z,Z}
+mkdir /tmp/{z,Z}
+
+#
+# 'z'
+#
+mkdir /tmp/z/d{1,2}
+touch /tmp/z/f1 /tmp/z/d1/f11 /tmp/z/d2/f21
+
+systemd-tmpfiles --create - <<EOF
+z /tmp/z/f1 0755 daemon daemon - -
+z /tmp/z/d1 0755 daemon daemon - -
+EOF
+
+test "$(stat -c %U:%G /tmp/z/f1)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/z/d1)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/z/d1/f11)" = "root:root"
+
+systemd-tmpfiles --create - <<EOF
+z /tmp/z/d2/* 0755 daemon daemon - -
+EOF
+
+test "$(stat -c %U:%G /tmp/z/d2/f21)" = "daemon:daemon"
+
+#
+# 'Z'
+#
+mkdir /tmp/Z/d1 /tmp/Z/d1/d11
+touch /tmp/Z/f1 /tmp/Z/d1/f11 /tmp/Z/d1/d11/f111
+
+systemd-tmpfiles --create - <<EOF
+Z /tmp/Z/f1 0755 daemon daemon - -
+Z /tmp/Z/d1 0755 daemon daemon - -
+EOF
+
+test "$(stat -c %U:%G /tmp/Z/f1)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/Z/d1)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/Z/d1/d11)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/Z/d1/f11)" = "daemon:daemon"
+test "$(stat -c %U:%G /tmp/Z/d1/d11/f111)" = "daemon:daemon"
diff --git a/test/units/testsuite-22.06.sh b/test/units/testsuite-22.06.sh
new file mode 100755
index 0000000..f64a95c
--- /dev/null
+++ b/test/units/testsuite-22.06.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Inspired by https://github.com/systemd/systemd/issues/9508
+set -eux
+set -o pipefail
+
+test_snippet() {
+ systemd-tmpfiles "$@" - <<EOF
+d /var/tmp/foobar-test-06
+d /var/tmp/foobar-test-06/important
+R /var/tmp/foobar-test-06
+EOF
+}
+
+test_snippet --create --remove
+test -d /var/tmp/foobar-test-06
+test -d /var/tmp/foobar-test-06/important
+
+test_snippet --remove
+test ! -f /var/tmp/foobar-test-06
+test ! -f /var/tmp/foobar-test-06/important
+
+test_snippet --create
+test -d /var/tmp/foobar-test-06
+test -d /var/tmp/foobar-test-06/important
+
+touch /var/tmp/foobar-test-06/something-else
+
+test_snippet --create
+test -d /var/tmp/foobar-test-06
+test -d /var/tmp/foobar-test-06/important
+test -f /var/tmp/foobar-test-06/something-else
+
+test_snippet --create --remove
+test -d /var/tmp/foobar-test-06
+test -d /var/tmp/foobar-test-06/important
+test ! -f /var/tmp/foobar-test-06/something-else
diff --git a/test/units/testsuite-22.07.sh b/test/units/testsuite-22.07.sh
new file mode 100755
index 0000000..de20d5e
--- /dev/null
+++ b/test/units/testsuite-22.07.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Verifies the issues described by https://github.com/systemd/systemd/issues/10191
+set -eux
+set -o pipefail
+
+rm -rf /tmp/test-prefix
+
+mkdir /tmp/test-prefix
+touch /tmp/test-prefix/file
+
+systemd-tmpfiles --remove - <<EOF
+r /tmp/test-prefix
+r /tmp/test-prefix/file
+EOF
+
+test ! -f /tmp/test-prefix/file
+test ! -f /tmp/test-prefix
+
+mkdir /tmp/test-prefix
+touch /tmp/test-prefix/file
+
+systemd-tmpfiles --remove - <<EOF
+r /tmp/test-prefix/file
+r /tmp/test-prefix
+EOF
+
+test ! -f /tmp/test-prefix/file
+test ! -f /tmp/test-prefix
diff --git a/test/units/testsuite-22.08.sh b/test/units/testsuite-22.08.sh
new file mode 100755
index 0000000..40fafd3
--- /dev/null
+++ b/test/units/testsuite-22.08.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Verify tmpfiles can run in a root directory under a path prefix that contains
+# directories owned by unprivileged users, for example when a root file system
+# is mounted in a regular user's home directory.
+#
+# https://github.com/systemd/systemd/pull/11820
+set -eux
+set -o pipefail
+
+rm -fr /tmp/root /tmp/user
+mkdir -p /tmp/root /tmp/user/root
+chown daemon:daemon /tmp/user
+
+# Verify the command works as expected with no prefix or a root-owned prefix.
+echo 'd /tmp/root/test1' | systemd-tmpfiles --create -
+test -d /tmp/root/test1
+echo 'd /test2' | systemd-tmpfiles --root=/tmp/root --create -
+test -d /tmp/root/test2
+
+# Verify the command fails to write to a root-owned subdirectory under an
+# unprivileged user's directory when it's not part of the prefix, as expected
+# by the unsafe_transition function.
+echo 'd /tmp/user/root/test' | (! systemd-tmpfiles --create -)
+test ! -e /tmp/user/root/test
+echo 'd /user/root/test' | (! systemd-tmpfiles --root=/tmp --create -)
+test ! -e /tmp/user/root/test
+
+# Verify the above works when all user-owned directories are in the prefix.
+echo 'd /test' | systemd-tmpfiles --root=/tmp/user/root --create -
+test -d /tmp/user/root/test
diff --git a/test/units/testsuite-22.09.sh b/test/units/testsuite-22.09.sh
new file mode 100755
index 0000000..0857773
--- /dev/null
+++ b/test/units/testsuite-22.09.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Make sure that the "stat" output is not locale dependent.
+export LANG=C LC_ALL=C
+
+# first, create file without suid/sgid
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx 0755 1 1 - -
+f /tmp/yyy 0755 1 1 - -
+EOF
+
+test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:755"
+test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:755"
+
+# then, add suid/sgid
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx 04755
+f /tmp/yyy 02755
+EOF
+
+test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:4755"
+test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:2755"
+
+# then, chown the files to somebody else
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx - 2 2
+f /tmp/yyy - 2 2
+EOF
+
+test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:2:2:4755"
+test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:2:2:2755"
+
+# then, chown the files to a third user/group but also drop to a mask that has
+# both more and fewer bits set
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx 0770 3 3
+f /tmp/yyy 0770 3 3
+EOF
+
+test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:3:3:770"
+test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:3:3:770"
+
+# return to the beginning
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx 0755 1 1 - -
+f /tmp/yyy 0755 1 1 - -
+EOF
+
+test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:755"
+test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:755"
+
+# remove everything
+systemd-tmpfiles --remove - <<EOF
+r /tmp/xxx
+r /tmp/yyy
+EOF
diff --git a/test/units/testsuite-22.10.sh b/test/units/testsuite-22.10.sh
new file mode 100755
index 0000000..99052c8
--- /dev/null
+++ b/test/units/testsuite-22.10.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-tmpfiles --create - <<EOF
+f /tmp/xxx1 0644 - - - foo
+f /tmp/xxx2 0644 - - - foo bar
+f /tmp/xxx3 0644 - - - foo\x20bar
+f /tmp/xxx4 0644 - - - \x20foobar
+f /tmp/xxx5 0644 - - - foobar\x20
+f /tmp/xxx6 0644 - - - foo bar
+f /tmp/xxx7 0644 - - - foo bar \n
+f /tmp/xxx8 0644 - - - " foo bar "
+f /tmp/xxx9 0644 - - - ' foo bar '
+EOF
+
+echo -n "foo" | cmp /tmp/xxx1 -
+echo -n "foo bar" | cmp /tmp/xxx2 -
+echo -n "foo bar" | cmp /tmp/xxx3 -
+echo -n " foobar" | cmp /tmp/xxx4 -
+echo -n "foobar " | cmp /tmp/xxx5 -
+echo -n "foo bar" | cmp /tmp/xxx6 -
+echo "foo bar " | cmp /tmp/xxx7 -
+echo -n "\" foo bar \"" | cmp /tmp/xxx8 -
+echo -n "' foo bar '" | cmp /tmp/xxx9 -
+
+rm /tmp/xxx{1,2,3,4,5,6,7,8,9}
diff --git a/test/units/testsuite-22.11.sh b/test/units/testsuite-22.11.sh
new file mode 100755
index 0000000..f71a95f
--- /dev/null
+++ b/test/units/testsuite-22.11.sh
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+set -x
+
+rm -fr /tmp/x
+mkdir /tmp/x
+
+#
+# 'x'
+#
+mkdir -p /tmp/x/{1,2}
+touch /tmp/x/1/{x1,x2} /tmp/x/2/{y1,y2} /tmp/x/{z1,z2}
+
+systemd-tmpfiles --clean - <<EOF
+d /tmp/x - - - 0
+x /tmp/x/1
+EOF
+
+find /tmp/x | sort
+test -d /tmp/x/1
+test -f /tmp/x/1/x1
+test -f /tmp/x/1/x2
+test ! -d /tmp/x/2
+test ! -f /tmp/x/2/x1
+test ! -f /tmp/x/2/x2
+test ! -f /tmp/x/z1
+test ! -f /tmp/x/z2
+
+#
+# 'X'
+#
+
+mkdir -p /tmp/x/{1,2}
+touch /tmp/x/1/{x1,x2} /tmp/x/2/{y1,y2} /tmp/x/{z1,z2}
+
+systemd-tmpfiles --clean - <<EOF
+d /tmp/x - - - 0
+X /tmp/x/1
+EOF
+
+find /tmp/x | sort
+test -d /tmp/x/1
+test ! -f /tmp/x/1/x1
+test ! -f /tmp/x/1/x2
+test ! -d /tmp/x/2
+test ! -f /tmp/x/2/x1
+test ! -f /tmp/x/2/x2
+test ! -f /tmp/x/z1
+test ! -f /tmp/x/z2
+
+#
+# 'x' with glob
+#
+
+mkdir -p /tmp/x/{1,2}
+touch /tmp/x/1/{x1,x2} /tmp/x/2/{y1,y2} /tmp/x/{z1,z2}
+
+systemd-tmpfiles --clean - <<EOF
+d /tmp/x - - - 0
+x /tmp/x/[1345]
+x /tmp/x/z*
+EOF
+
+find /tmp/x | sort
+test -d /tmp/x/1
+test -f /tmp/x/1/x1
+test -f /tmp/x/1/x2
+test ! -d /tmp/x/2
+test ! -f /tmp/x/2/x1
+test ! -f /tmp/x/2/x2
+test -f /tmp/x/z1
+test -f /tmp/x/z2
+
+#
+# 'X' with glob
+#
+
+mkdir -p /tmp/x/{1,2}
+touch /tmp/x/1/{x1,x2} /tmp/x/2/{y1,y2} /tmp/x/{z1,z2}
+
+systemd-tmpfiles --clean - <<EOF
+d /tmp/x - - - 0
+X /tmp/x/[1345]
+X /tmp/x/?[12]
+EOF
+
+find /tmp/x | sort
+test -d /tmp/x/1
+test ! -f /tmp/x/1/x1
+test ! -f /tmp/x/1/x2
+test ! -d /tmp/x/2
+test ! -f /tmp/x/2/x1
+test ! -f /tmp/x/2/x2
+test -f /tmp/x/z1
+test -f /tmp/x/z2
+
+#
+# 'x' with 'r'
+#
+
+mkdir -p /tmp/x/{1,2}/a
+touch /tmp/x/1/a/{x1,x2} /tmp/x/2/a/{y1,y2}
+
+systemd-tmpfiles --clean - <<EOF
+# x/X is not supposed to influence r
+x /tmp/x/1/a
+X /tmp/x/2/a
+r /tmp/x/1
+r /tmp/x/2
+EOF
+
+find /tmp/x | sort
+test -d /tmp/x/1
+test -d /tmp/x/1/a
+test -f /tmp/x/1/a/x1
+test -f /tmp/x/1/a/x2
+test -f /tmp/x/2/a/y1
+test -f /tmp/x/2/a/y2
+
+#
+# 'x' with 'R'
+#
+
+mkdir -p /tmp/x/{1,2}/a
+touch /tmp/x/1/a/{x1,x2} /tmp/x/2/a/{y1,y2}
+
+systemd-tmpfiles --remove - <<EOF
+# X is not supposed to influence R
+X /tmp/x/1/a
+X /tmp/x/2/a
+R /tmp/x/1
+EOF
+
+find /tmp/x | sort
+test ! -d /tmp/x/1
+test ! -d /tmp/x/1/a
+test ! -f /tmp/x/1/a/x1
+test ! -f /tmp/x/1/a/x2
+test -f /tmp/x/2/a/y1
+test -f /tmp/x/2/a/y2
diff --git a/test/units/testsuite-22.12.sh b/test/units/testsuite-22.12.sh
new file mode 100755
index 0000000..b8c4da8
--- /dev/null
+++ b/test/units/testsuite-22.12.sh
@@ -0,0 +1,196 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Test the "Age" parameter (with age-by) for systemd-tmpfiles.
+set -e
+set -x
+
+# Test directory structure looks like this:
+# /tmp/ageby/
+# ├── d1
+# │   ├── f1
+# │   ├── f2
+# │   ├── f3
+# │   └── f4
+# ├── d2
+# │   ├── f1
+# │   ├── f2
+# ...
+
+export SYSTEMD_LOG_LEVEL="debug"
+
+rm -rf /tmp/ageby
+mkdir -p /tmp/ageby/d{1..4}
+
+# TODO: There is probably a better way to figure this out.
+# Test for [bB] age-by arguments only on filesystems that expose
+# the creation time. Note that this is _not_ an accurate way to
+# check if the filesystem or kernel version don't provide the
+# timestamp. But, if the timestamp is visible in "stat" it is a
+# good indicator that the test can be run.
+TEST_TMPFILES_AGEBY_BTIME=${TEST_TMPFILES_AGEBY_BTIME:-0}
+if stat --format "%w" /tmp/ageby 2>/dev/null | grep -qv '^[\?\-]$'; then
+ TEST_TMPFILES_AGEBY_BTIME=1
+fi
+
+touch -a --date "2 minutes ago" /tmp/ageby/d1/f1
+touch -m --date "4 minutes ago" /tmp/ageby/d2/f1
+
+# Create a bunch of other files.
+touch /tmp/ageby/d{1,2}/f{2..4}
+
+# For "ctime".
+touch /tmp/ageby/d3/f1
+chmod +x /tmp/ageby/d3/f1
+sleep 1
+
+# For "btime".
+touch /tmp/ageby/d4/f1
+sleep 1
+
+# More files with recent "{a,b}time" values.
+touch /tmp/ageby/d{3,4}/f{2..4}
+
+# Check for cleanup of "f1" in each of "/tmp/d{1..4}".
+systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/d1 - - - a:1m -
+e /tmp/ageby/d2 - - - m:3m -
+D /tmp/ageby/d3 - - - c:2s -
+EOF
+
+for d in d{1..3}; do
+ test ! -f "/tmp/ageby/${d}/f1"
+done
+
+if [[ $TEST_TMPFILES_AGEBY_BTIME -gt 0 ]]; then
+ systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/d4 - - - b:1s -
+EOF
+
+ test ! -f "/tmp/ageby/d4/f1"
+else
+ # Remove the file manually.
+ rm "/tmp/ageby/d4/f1"
+fi
+
+# Check for an invalid "age" and "age-by" arguments.
+for a in ':' ':1s' '2:1h' 'nope:42h' '" :7m"' 'm:' '::' '"+r^w-x:2/h"' 'b ar::64'; do
+ systemd-tmpfiles --clean - <<EOF 2>&1 | grep -q -F 'Invalid age'
+d /tmp/ageby - - - ${a} -
+EOF
+done
+
+for d in d{1..4}; do
+ for f in f{2..4}; do
+ test -f "/tmp/ageby/${d}/${f}"
+ done
+done
+
+# Check for parsing with whitespace, repeated values
+# for "age-by" (valid arguments).
+for a in '" a:24h"' 'cccaab:2h' '" aa : 4h"' '" a A B C c:1h"'; do
+ systemd-tmpfiles --clean - <<EOF
+d /tmp/ageby - - - ${a} -
+EOF
+done
+
+for d in d{1..4}; do
+ for f in f{2..4}; do
+ test -f "/tmp/ageby/${d}/${f}"
+ done
+done
+
+# Check that all files are removed if the "Age" is
+# set to "0" (regardless of "age-by" argument).
+systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/d1 - - - abc:0 -
+e /tmp/ageby/d2 - - - cmb:0 -
+EOF
+
+for d in d{1,2}; do
+ for f in f{2..4}; do
+ test ! -f "/tmp/ageby/${d}/${f}"
+ done
+done
+
+# Check for combinations:
+# - "/tmp/ageby/d3/f2" has file timestamps that
+# are older than the specified age, it will be
+# removed
+# - "/tmp/ageby/d4/f2", has not aged for the given
+# timestamp combination, it will not be removed
+touch -a -m --date "4 minutes ago" /tmp/ageby/d3/f2
+touch -a -m --date "8 minutes ago" /tmp/ageby/d4/f2
+systemd-tmpfiles --clean - <<-EOF
+e /tmp/ageby/d3 - - - am:3m -
+D /tmp/ageby/d4 - - - mc:7m -
+EOF
+
+test ! -f "/tmp/ageby/d3/f2"
+test -f "/tmp/ageby/d4/f2"
+
+# Check that all files are removed if only "Age" is set to 0.
+systemd-tmpfiles --clean - <<-EOF
+e /tmp/ageby/d3 - - - 0s
+d /tmp/ageby/d4 - - - 0s
+EOF
+
+for d in d{3,4}; do
+ for f in f{2..4}; do
+ test ! -f "/tmp/ageby/$d/${f}"
+ done
+done
+
+# Check "age-by" argument for sub-directories in "/tmp/ageby".
+systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/ - - - A:1m -
+EOF
+
+for d in d{1..4}; do
+ test -d "/tmp/ageby/${d}"
+done
+
+# Check for combinations.
+touch -a -m --date "5 seconds ago" /tmp/ageby/d{1,2}
+systemd-tmpfiles --clean - <<-EOF
+e /tmp/ageby/ - - - AM:4s -
+EOF
+
+for d in d{1,2}; do
+ test ! -d "/tmp/ageby/${d}"
+done
+
+for d in d{3,4}; do
+ test -d "/tmp/ageby/${d}"
+done
+
+# Check "btime" for directories.
+if [[ $TEST_TMPFILES_AGEBY_BTIME -gt 0 ]]; then
+ systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/ - - - B:8s -
+EOF
+
+ for d in d{3,4}; do
+ test -d "/tmp/ageby/${d}"
+ done
+fi
+
+# To bump "atime".
+touch -a --date "1 second ago" /tmp/ageby/d3
+systemd-tmpfiles --clean - <<-EOF
+d /tmp/ageby/ - - - A:2s -
+EOF
+
+test -d /tmp/ageby/d3
+test ! -d /tmp/ageby/d4
+
+# Check if sub-directories are removed regardless
+# of "age-by", when "Age" is set to "0".
+systemd-tmpfiles --clean - <<-EOF
+D /tmp/ageby/ - - - AM:0 -
+EOF
+
+test ! -d /tmp/ageby/d3
+
+# Cleanup the test directory (fail if not empty).
+rmdir /tmp/ageby
diff --git a/test/units/testsuite-22.13.sh b/test/units/testsuite-22.13.sh
new file mode 100755
index 0000000..33ef451
--- /dev/null
+++ b/test/units/testsuite-22.13.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Tests for configuration directory and file precedences
+#
+set -eux
+
+rm -f /{usr/lib,etc}/tmpfiles.d/{L,w}-*.conf
+rm -fr /tmp/precedence/{L,w}
+
+mkdir -p /{usr/lib,etc}/tmpfiles.d
+mkdir -p /tmp/precedence/{L,w}
+
+#
+# 'L'
+#
+ln -s /dev/null /tmp/precedence/L
+
+# Overwrite the existing symlink
+cat >/usr/lib/tmpfiles.d/L-z.conf<<EOF
+L+ /tmp/precedence/L - - - - /usr/lib/tmpfiles.d/L-z.conf
+EOF
+
+systemd-tmpfiles --create
+test "$(readlink /tmp/precedence/L)" = "/usr/lib/tmpfiles.d/L-z.conf"
+
+# Files in /etc should override those in /usr
+cat >/etc/tmpfiles.d/L-z.conf<<EOF
+L+ /tmp/precedence/L - - - - /etc/tmpfiles.d/L-z.conf
+EOF
+
+systemd-tmpfiles --create
+test "$(readlink /tmp/precedence/L)" = "/etc/tmpfiles.d/L-z.conf"
+
+# /usr/…/L-a.conf has higher prio than /etc/…/L-z.conf
+cat >/usr/lib/tmpfiles.d/L-a.conf<<EOF
+L+ /tmp/precedence/L - - - - /usr/lib/tmpfiles.d/L-a.conf
+EOF
+
+systemd-tmpfiles --create
+test "$(readlink /tmp/precedence/L)" = "/usr/lib/tmpfiles.d/L-a.conf"
+
+# Files in /etc should override those in /usr
+cat >/etc/tmpfiles.d/L-a.conf<<EOF
+L+ /tmp/precedence/L - - - - /etc/tmpfiles.d/L-a.conf
+EOF
+
+systemd-tmpfiles --create
+test "$(readlink /tmp/precedence/L)" = "/etc/tmpfiles.d/L-a.conf"
+
+#
+# 'w'
+#
+touch /tmp/precedence/w/f
+
+# Multiple configuration files specifying 'w+' for the same path is allowed.
+for i in a c; do
+ cat >/usr/lib/tmpfiles.d/w-$i.conf<<EOF
+w+ /tmp/precedence/w/f - - - - /usr/lib/tmpfiles.d/w-$i.conf\n
+EOF
+ cat >/etc/tmpfiles.d/w-$i.conf<<EOF
+w+ /tmp/precedence/w/f - - - - /etc/tmpfiles.d/w-$i.conf\n
+EOF
+done
+
+cat >/usr/lib/tmpfiles.d/w-b.conf<<EOF
+w+ /tmp/precedence/w/f - - - - /usr/lib/tmpfiles.d/w-b.conf\n
+EOF
+
+systemd-tmpfiles --create
+cmp /tmp/precedence/w/f <<EOF
+/etc/tmpfiles.d/w-a.conf
+/usr/lib/tmpfiles.d/w-b.conf
+/etc/tmpfiles.d/w-c.conf
+EOF
diff --git a/test/units/testsuite-22.14.sh b/test/units/testsuite-22.14.sh
new file mode 100755
index 0000000..2132de7
--- /dev/null
+++ b/test/units/testsuite-22.14.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Tests for the ":" uid/gid/mode modifier
+#
+set -eux
+
+rm -rf /tmp/someinode
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/someinode :0123 :1 :1
+EOF
+test "$(stat -c %F:%u:%g:%a /tmp/someinode)" = "directory:1:1:123"
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/someinode :0321 :2 :2
+EOF
+test "$(stat -c %F:%u:%g:%a /tmp/someinode)" = "directory:1:1:123"
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/someinode 0321 2 2
+EOF
+test "$(stat -c %F:%u:%g:%a /tmp/someinode)" = "directory:2:2:321"
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/someinode :0123 :1 :1
+EOF
+test "$(stat -c %F:%u:%g:%a /tmp/someinode)" = "directory:2:2:321"
+
+rm -rf /tmp/someinode
+
+systemd-tmpfiles --create - <<EOF
+d /tmp/someinode :0123 :1 :1
+EOF
+test "$(stat -c %F:%u:%g:%a /tmp/someinode)" = "directory:1:1:123"
+
+rm -rf /tmp/someinode
diff --git a/test/units/testsuite-22.15.sh b/test/units/testsuite-22.15.sh
new file mode 100755
index 0000000..6cbb498
--- /dev/null
+++ b/test/units/testsuite-22.15.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Check specifier expansion in L lines.
+#
+set -eux
+
+rm -fr /tmp/L
+mkdir /tmp/L
+
+# Check that %h expands to $home.
+home='/somewhere'
+dst='/tmp/L/1'
+src="$home"
+HOME="$home" \
+systemd-tmpfiles --create - <<EOF
+L $dst - - - - %h
+EOF
+test "$(readlink "$dst")" = "$src"
+
+# Check that %h in the path is expanded, but
+# the result of this expansion is not expanded once again.
+root='/tmp/L/2'
+home='/%U'
+src="/usr/share/factory$home"
+mkdir -p "$root$src"
+dst="$root$home"
+HOME="$home" \
+systemd-tmpfiles --create --root="$root" - <<EOF
+L %h - - - -
+EOF
+test "$(readlink "$dst")" = "$src"
diff --git a/test/units/testsuite-22.service b/test/units/testsuite-22.service
new file mode 100644
index 0000000..a5ed660
--- /dev/null
+++ b/test/units/testsuite-22.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-22-TMPFILES
+After=systemd-tmpfiles-setup.service
+Before=getty-pre.target
+Wants=getty-pre.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-22.sh b/test/units/testsuite-22.sh
new file mode 100755
index 0000000..43823f1
--- /dev/null
+++ b/test/units/testsuite-22.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+for t in "${0%.sh}".*.sh; do
+ echo "Running $t"; ./"$t"
+done
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-23.service b/test/units/testsuite-23.service
new file mode 100644
index 0000000..26f5226
--- /dev/null
+++ b/test/units/testsuite-23.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-23-TYPE-EXEC
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-23.sh b/test/units/testsuite-23.sh
new file mode 100755
index 0000000..4ce205f
--- /dev/null
+++ b/test/units/testsuite-23.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+# Create a binary for which execve() will fail
+touch /tmp/brokenbinary
+chmod +x /tmp/brokenbinary
+
+# These three commands should succeed.
+systemd-run --unit=one -p Type=simple /bin/sleep infinity
+systemd-run --unit=two -p Type=simple -p User=idontexist /bin/sleep infinity
+systemd-run --unit=three -p Type=simple /tmp/brokenbinary
+
+# And now, do the same with Type=exec, where the latter two should fail
+systemd-run --unit=four -p Type=exec /bin/sleep infinity
+(! systemd-run --unit=five -p Type=exec -p User=idontexist /bin/sleep infinity)
+(! systemd-run --unit=six -p Type=exec /tmp/brokenbinary)
+
+systemd-run --unit=seven -p KillSignal=SIGTERM -p RestartKillSignal=SIGINT -p Type=exec /bin/sleep infinity
+# Both TERM and SIGINT happen to have the same number on all architectures
+test "$(systemctl show --value -p KillSignal seven.service)" -eq 15
+test "$(systemctl show --value -p RestartKillSignal seven.service)" -eq 2
+
+systemctl restart seven.service
+systemctl stop seven.service
+
+# For issue #20933
+
+# Should work normally
+busctl call \
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-ok.service replace 1 \
+ ExecStart "a(sasb)" 1 \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ 0
+
+# DBus call should fail but not crash systemd
+(! busctl call \
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-bad.service replace 1 \
+ ExecStart "a(sasb)" 1 \
+ /usr/bin/sleep 0 true \
+ 0)
+
+# Same but with the empty argv in the middle
+(! busctl call \
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-bad-middle.service replace 1 \
+ ExecStart "a(sasb)" 3 \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ /usr/bin/sleep 0 true \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ 0)
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-24.service b/test/units/testsuite-24.service
new file mode 100644
index 0000000..e192d1c
--- /dev/null
+++ b/test/units/testsuite-24.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-24-CRYPTSETUP
+After=multi-user.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-24.sh b/test/units/testsuite-24.sh
new file mode 100755
index 0000000..391dcf9
--- /dev/null
+++ b/test/units/testsuite-24.sh
@@ -0,0 +1,216 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# TODO:
+# - /proc/cmdline parsing
+# - figure out token support (apart from TPM2, as that's covered by TEST-70-TPM2)
+# - this might help https://www.qemu.org/docs/master/system/devices/ccid.html
+# - expect + interactive auth?
+
+# We set up an encrypted /var partition which should get mounted automatically
+# on boot
+mountpoint /var
+
+systemctl --state=failed --no-legend --no-pager | tee /failed
+if [[ -s /failed ]]; then
+ echo >&2 "Found units in failed state"
+ exit 1
+fi
+
+at_exit() {
+ set +e
+
+ mountpoint -q /proc/cmdline && umount /proc/cmdline
+ rm -f /etc/crypttab
+ [[ -e /tmp/crypttab.bak ]] && cp -fv /tmp/crypttab.bak /etc/crypttab
+ [[ -n "${STORE_LOOP:-}" ]] && losetup -d "$STORE_LOOP"
+ [[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"
+
+ systemctl daemon-reload
+}
+
+trap at_exit EXIT
+
+cryptsetup_start_and_check() {
+ local expect_fail=0
+ local ec volume unit
+
+ if [[ "${1:?}" == "-f" ]]; then
+ expect_fail=1
+ shift
+ fi
+
+ for volume in "$@"; do
+ unit="systemd-cryptsetup@$volume.service"
+
+ # The unit existence check should always pass
+ [[ "$(systemctl show -P LoadState "$unit")" == loaded ]]
+ systemctl list-unit-files "$unit"
+
+ systemctl start "$unit" && ec=0 || ec=$?
+ if [[ "$expect_fail" -ne 0 ]]; then
+ if [[ "$ec" -eq 0 ]]; then
+ echo >&2 "Unexpected pass when starting $unit"
+ return 1
+ fi
+
+ return 0
+ fi
+
+ if [[ "$ec" -ne 0 ]]; then
+ echo >&2 "Unexpected fail when starting $unit"
+ return 1
+ fi
+
+ systemctl status "$unit"
+ test -e "/dev/mapper/$volume"
+ systemctl stop "$unit"
+ test ! -e "/dev/mapper/$volume"
+ done
+
+ return 0
+}
+
+# Note: some stuff (especially TPM-related) is already tested by TEST-70-TPM2,
+# so focus more on other areas instead
+
+# Use a common workdir to make the cleanup easier
+WORKDIR="$(mktemp -d)"
+
+# Prepare a couple of LUKS2-encrypted disk images
+#
+# 1) Image with an empty password
+IMAGE_EMPTY="$WORKDIR/empty.img)"
+IMAGE_EMPTY_KEYFILE="$WORKDIR/empty.keyfile"
+IMAGE_EMPTY_KEYFILE_ERASE="$WORKDIR/empty-erase.keyfile"
+IMAGE_EMPTY_KEYFILE_ERASE_FAIL="$WORKDIR/empty-erase-fail.keyfile)"
+truncate -s 32M "$IMAGE_EMPTY"
+echo -n passphrase >"$IMAGE_EMPTY_KEYFILE"
+chmod 0600 "$IMAGE_EMPTY_KEYFILE"
+cryptsetup luksFormat --batch-mode \
+ --pbkdf pbkdf2 \
+ --pbkdf-force-iterations 1000 \
+ --use-urandom \
+ "$IMAGE_EMPTY" "$IMAGE_EMPTY_KEYFILE"
+PASSWORD=passphrase NEWPASSWORD="" systemd-cryptenroll --password "$IMAGE_EMPTY"
+# Duplicate the key file to test keyfile-erase as well
+cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE"
+# The key should get erased even on a failed attempt, so test that too
+cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
+
+# 2) Image with a detached header and a key file offset + size
+IMAGE_DETACHED="$WORKDIR/detached.img"
+IMAGE_DETACHED_KEYFILE="$WORKDIR/detached.keyfile"
+IMAGE_DETACHED_KEYFILE2="$WORKDIR/detached.keyfile2"
+IMAGE_DETACHED_HEADER="$WORKDIR/detached.header"
+truncate -s 32M "$IMAGE_DETACHED"
+dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE" count=64 bs=1
+dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE2" count=32 bs=1
+chmod 0600 "$IMAGE_DETACHED_KEYFILE" "$IMAGE_DETACHED_KEYFILE2"
+cryptsetup luksFormat --batch-mode \
+ --pbkdf pbkdf2 \
+ --pbkdf-force-iterations 1000 \
+ --use-urandom \
+ --header "$IMAGE_DETACHED_HEADER" \
+ --keyfile-offset 32 \
+ --keyfile-size 16 \
+ "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE"
+# Also, add a second key file to key slot 8
+# Note: --key-slot= behaves as --new-key-slot= when used alone for backwards compatibility
+cryptsetup luksAddKey --batch-mode \
+ --header "$IMAGE_DETACHED_HEADER" \
+ --key-file "$IMAGE_DETACHED_KEYFILE" \
+ --keyfile-offset 32 \
+ --keyfile-size 16 \
+ --key-slot 8 \
+ "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE2"
+
+# Prepare a couple of dummy devices we'll store a copy of the detached header
+# and one of the keys on to test if systemd-cryptsetup correctly mounts them
+# when necessary
+STORE_IMAGE="$WORKDIR/store.img"
+truncate -s 64M "$STORE_IMAGE"
+STORE_LOOP="$(losetup --show --find --partscan "$STORE_IMAGE")"
+sfdisk "$STORE_LOOP" <<EOF
+label: gpt
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=header_store size=32M
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=keyfile_store
+EOF
+udevadm settle --timeout=30
+mkdir -p /mnt
+mkfs.ext4 -L header_store "/dev/disk/by-partlabel/header_store"
+mount "/dev/disk/by-partlabel/header_store" /mnt
+cp "$IMAGE_DETACHED_HEADER" /mnt/header
+umount /mnt
+mkfs.ext4 -L keyfile_store "/dev/disk/by-partlabel/keyfile_store"
+mount "/dev/disk/by-partlabel/keyfile_store" /mnt
+cp "$IMAGE_DETACHED_KEYFILE2" /mnt/keyfile
+umount /mnt
+udevadm settle --timeout=30
+
+# Prepare our test crypttab
+[[ -e /etc/crypttab ]] && cp -fv /etc/crypttab /tmp/crypttab.bak
+cat >/etc/crypttab <<EOF
+# headless should translate to headless=1
+empty_key $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE headless,x-systemd.device-timeout=1m
+empty_key_erase $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE_ERASE headless=1,keyfile-erase=1
+empty_key_erase_fail $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE_ERASE_FAIL headless=1,keyfile-erase=1,keyfile-offset=4
+# Empty passphrase without try-empty-password(=yes) shouldn't work
+empty_fail0 $IMAGE_EMPTY - headless=1
+empty_fail1 $IMAGE_EMPTY - headless=1,try-empty-password=0
+empty0 $IMAGE_EMPTY - headless=1,try-empty-password
+empty1 $IMAGE_EMPTY - headless=1,try-empty-password=1
+# This one expects the key to be under /{etc,run}/cryptsetup-keys.d/empty_nokey.key
+empty_nokey $IMAGE_EMPTY - headless=1
+
+detached $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32,keyfile-size=16
+detached_store0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=/header:LABEL=header_store,keyfile-offset=32,keyfile-size=16
+detached_store1 $IMAGE_DETACHED /keyfile:LABEL=keyfile_store headless=1,header=$IMAGE_DETACHED_HEADER
+detached_store2 $IMAGE_DETACHED /keyfile:LABEL=keyfile_store headless=1,header=/header:LABEL=header_store
+detached_fail0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32
+detached_fail1 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER
+detached_fail2 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1
+detached_fail3 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=16,keyfile-size=16
+detached_fail4 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32,keyfile-size=8
+detached_slot0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER
+detached_slot1 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER,key-slot=8
+detached_slot_fail $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER,key-slot=0
+EOF
+
+# Temporarily drop luks.name=/luks.uuid= from the kernel command line, as it makes
+# systemd-cryptsetup-generator ignore mounts from /etc/crypttab that are not also
+# specified on the kernel command line
+sed -r 's/luks.(name|uuid)=[^[:space:]+]//' /proc/cmdline >/tmp/cmdline.tmp
+mount --bind /tmp/cmdline.tmp /proc/cmdline
+# Run the systemd-cryptsetup-generator once explicitly, to collect coverage,
+# as during daemon-reload we run generators in a sandbox
+mkdir -p /tmp/systemd-cryptsetup-generator.out
+/usr/lib/systemd/system-generators/systemd-cryptsetup-generator /tmp/systemd-cryptsetup-generator.out/
+systemctl daemon-reload
+systemctl list-unit-files "systemd-cryptsetup@*"
+
+cryptsetup_start_and_check empty_key
+test -e "$IMAGE_EMPTY_KEYFILE_ERASE"
+cryptsetup_start_and_check empty_key_erase
+test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE"
+test -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
+cryptsetup_start_and_check -f empty_key_erase_fail
+test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
+cryptsetup_start_and_check -f empty_fail{0..1}
+cryptsetup_start_and_check empty{0..1}
+# First, check if we correctly fail without any key
+cryptsetup_start_and_check -f empty_nokey
+# And now provide the key via /{etc,run}/cryptsetup-keys.d/
+mkdir -p /run/cryptsetup-keys.d
+cp "$IMAGE_EMPTY_KEYFILE" /run/cryptsetup-keys.d/empty_nokey.key
+cryptsetup_start_and_check empty_nokey
+
+cryptsetup_start_and_check detached
+cryptsetup_start_and_check detached_store{0..2}
+cryptsetup_start_and_check -f detached_fail{0..4}
+cryptsetup_start_and_check detached_slot{0..1}
+cryptsetup_start_and_check -f detached_slot_fail
+
+echo OK >/testok
diff --git a/test/units/testsuite-25.service b/test/units/testsuite-25.service
new file mode 100644
index 0000000..503eabb
--- /dev/null
+++ b/test/units/testsuite-25.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-25-IMPORT
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-25.sh b/test/units/testsuite-25.sh
new file mode 100755
index 0000000..860dc5b
--- /dev/null
+++ b/test/units/testsuite-25.sh
@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+export SYSTEMD_PAGER=cat
+
+dd if=/dev/urandom of=/var/tmp/testimage.raw bs=$((1024*1024+7)) count=5
+
+# Test import
+machinectl import-raw /var/tmp/testimage.raw
+machinectl image-status testimage
+test -f /var/lib/machines/testimage.raw
+cmp /var/tmp/testimage.raw /var/lib/machines/testimage.raw
+
+# Test export
+machinectl export-raw testimage /var/tmp/testimage2.raw
+cmp /var/tmp/testimage.raw /var/tmp/testimage2.raw
+rm /var/tmp/testimage2.raw
+
+# Test compressed export (gzip)
+machinectl export-raw testimage /var/tmp/testimage2.raw.gz
+gunzip /var/tmp/testimage2.raw.gz
+cmp /var/tmp/testimage.raw /var/tmp/testimage2.raw
+rm /var/tmp/testimage2.raw
+
+# Test clone
+machinectl clone testimage testimage3
+test -f /var/lib/machines/testimage3.raw
+machinectl image-status testimage3
+test -f /var/lib/machines/testimage.raw
+machinectl image-status testimage
+cmp /var/tmp/testimage.raw /var/lib/machines/testimage.raw
+cmp /var/tmp/testimage.raw /var/lib/machines/testimage3.raw
+
+# Test removal
+machinectl remove testimage
+test ! -f /var/lib/machines/testimage.raw
+(! machinectl image-status testimage)
+
+# Test export of clone
+machinectl export-raw testimage3 /var/tmp/testimage3.raw
+cmp /var/tmp/testimage.raw /var/tmp/testimage3.raw
+rm /var/tmp/testimage3.raw
+
+# Test rename
+machinectl rename testimage3 testimage4
+test -f /var/lib/machines/testimage4.raw
+machinectl image-status testimage4
+test ! -f /var/lib/machines/testimage3.raw
+(! machinectl image-status testimage3)
+cmp /var/tmp/testimage.raw /var/lib/machines/testimage4.raw
+
+# Test export of rename
+machinectl export-raw testimage4 /var/tmp/testimage4.raw
+cmp /var/tmp/testimage.raw /var/tmp/testimage4.raw
+rm /var/tmp/testimage4.raw
+
+# Test removal
+machinectl remove testimage4
+test ! -f /var/lib/machines/testimage4.raw
+(! machinectl image-status testimage4)
+
+# → And now, let's test directory trees ← #
+
+# Set up a directory we can import
+mkdir /var/tmp/scratch
+mv /var/tmp/testimage.raw /var/tmp/scratch/
+touch /var/tmp/scratch/anotherfile
+mkdir /var/tmp/scratch/adirectory
+echo "piep" >/var/tmp/scratch/adirectory/athirdfile
+
+# Test import-fs
+machinectl import-fs /var/tmp/scratch/
+test -d /var/lib/machines/scratch
+machinectl image-status scratch
+
+# Test export-tar
+machinectl export-tar scratch /var/tmp/scratch.tar.gz
+test -f /var/tmp/scratch.tar.gz
+mkdir /var/tmp/extract
+(cd /var/tmp/extract ; tar xzf /var/tmp/scratch.tar.gz)
+diff -r /var/tmp/scratch/ /var/tmp/extract/
+rm -rf /var/tmp/extract
+
+# Test import-tar
+machinectl import-tar /var/tmp/scratch.tar.gz scratch2
+test -d /var/lib/machines/scratch2
+machinectl image-status scratch2
+diff -r /var/tmp/scratch/ /var/lib/machines/scratch2
+
+# Test removal
+machinectl remove scratch
+test ! -f /var/lib/machines/scratch
+(! machinectl image-status scratch)
+
+# Test clone
+machinectl clone scratch2 scratch3
+test -d /var/lib/machines/scratch2
+machinectl image-status scratch2
+test -d /var/lib/machines/scratch3
+machinectl image-status scratch3
+diff -r /var/tmp/scratch/ /var/lib/machines/scratch3
+
+# Test removal
+machinectl remove scratch2
+test ! -f /var/lib/machines/scratch2
+(! machinectl image-status scratch2)
+
+# Test rename
+machinectl rename scratch3 scratch4
+test -d /var/lib/machines/scratch4
+machinectl image-status scratch4
+test ! -f /var/lib/machines/scratch3
+(! machinectl image-status scratch3)
+diff -r /var/tmp/scratch/ /var/lib/machines/scratch4
+
+# Test removal
+machinectl remove scratch4
+test ! -f /var/lib/machines/scratch4
+(! machinectl image-status scratch4)
+
+# Test import-tar hyphen/stdin pipe behavior
+# shellcheck disable=SC2002
+cat /var/tmp/scratch.tar.gz | machinectl import-tar - scratch5
+test -d /var/lib/machines/scratch5
+machinectl image-status scratch5
+diff -r /var/tmp/scratch/ /var/lib/machines/scratch5
+
+# Test export-tar hyphen/stdout pipe behavior
+mkdir -p /var/tmp/extract
+machinectl export-tar scratch5 - | tar xvf - -C /var/tmp/extract/
+diff -r /var/tmp/scratch/ /var/tmp/extract/
+rm -rf /var/tmp/extract
+
+rm -rf /var/tmp/scratch
+
+# Test removal
+machinectl remove scratch5
+test ! -f /var/lib/machines/scratch5
+(! machinectl image-status scratch5)
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-26.service b/test/units/testsuite-26.service
new file mode 100644
index 0000000..d8fdaff
--- /dev/null
+++ b/test/units/testsuite-26.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-26-SYSTEMCTL
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-26.sh b/test/units/testsuite-26.sh
new file mode 100755
index 0000000..98ffe56
--- /dev/null
+++ b/test/units/testsuite-26.sh
@@ -0,0 +1,416 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+: >/failed
+
+at_exit() {
+ if [[ -v UNIT_NAME && -e "/usr/lib/systemd/system/$UNIT_NAME" ]]; then
+ rm -fv "/usr/lib/systemd/system/$UNIT_NAME"
+ fi
+
+ rm -f /etc/init.d/issue-24990
+ return 0
+}
+
+trap at_exit EXIT
+
+# Create a simple unit file for testing
+# Note: the service file is created under /usr on purpose to test
+# the 'revert' verb as well
+UNIT_NAME="systemctl-test-$RANDOM.service"
+cat >"/usr/lib/systemd/system/$UNIT_NAME" <<\EOF
+[Unit]
+Description=systemctl test
+
+[Service]
+ExecStart=sleep infinity
+ExecReload=true
+
+# For systemctl clean
+CacheDirectory=%n
+ConfigurationDirectory=%n
+LogsDirectory=%n
+RuntimeDirectory=%n
+StateDirectory=%n
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+# Configure the preset setting for the unit file
+mkdir /run/systemd/system-preset/
+echo "disable $UNIT_NAME" >/run/systemd/system-preset/99-systemd-test.preset
+
+systemctl daemon-reload
+
+# Double free when editing a template unit (#26483)
+EDITOR='true' script -ec 'systemctl edit user@0' /dev/null
+
+# Argument help
+systemctl --state help
+systemctl --signal help
+systemctl --type help
+
+# list-dependencies
+systemctl list-dependencies systemd-journald
+systemctl list-dependencies --after systemd-journald
+systemctl list-dependencies --before systemd-journald
+systemctl list-dependencies --after --reverse systemd-journald
+systemctl list-dependencies --before --reverse systemd-journald
+systemctl list-dependencies --plain systemd-journald
+
+# list-* verbs
+systemctl list-units
+systemctl list-units --recursive
+systemctl list-units --type=socket
+systemctl list-units --type=service,timer
+# Compat: --type= allows load states for compatibility reasons
+systemctl list-units --type=loaded
+systemctl list-units --type=loaded,socket
+systemctl list-units --legend=yes -a "systemd-*"
+systemctl list-units --state=active
+systemctl list-units --with-dependencies systemd-journald.service
+systemctl list-units --with-dependencies --after systemd-journald.service
+systemctl list-units --with-dependencies --before --reverse systemd-journald.service
+systemctl list-sockets
+systemctl list-sockets --legend=no -a "*journal*"
+systemctl list-sockets --show-types
+systemctl list-sockets --state=listening
+systemctl list-timers -a -l
+systemctl list-unit-files
+systemctl list-unit-files "*journal*"
+systemctl list-jobs
+systemctl list-jobs --after
+systemctl list-jobs --before
+systemctl list-jobs --after --before
+systemctl list-jobs "*"
+
+# Basic service management
+systemctl start --show-transaction "$UNIT_NAME"
+systemctl status -n 5 "$UNIT_NAME"
+systemctl is-active "$UNIT_NAME"
+systemctl reload -T "$UNIT_NAME"
+systemctl restart -T "$UNIT_NAME"
+systemctl try-restart --show-transaction "$UNIT_NAME"
+systemctl try-reload-or-restart --show-transaction "$UNIT_NAME"
+systemctl kill "$UNIT_NAME"
+(! systemctl is-active "$UNIT_NAME")
+systemctl restart "$UNIT_NAME"
+systemctl is-active "$UNIT_NAME"
+systemctl restart "$UNIT_NAME"
+systemctl stop "$UNIT_NAME"
+(! systemctl is-active "$UNIT_NAME")
+
+# enable/disable/preset
+(! systemctl is-enabled "$UNIT_NAME")
+systemctl enable "$UNIT_NAME"
+systemctl is-enabled -l "$UNIT_NAME"
+# We created a preset file for this unit above with a "disable" policy
+systemctl preset "$UNIT_NAME"
+(! systemctl is-enabled "$UNIT_NAME")
+systemctl reenable "$UNIT_NAME"
+systemctl is-enabled "$UNIT_NAME"
+systemctl preset --preset-mode=enable-only "$UNIT_NAME"
+systemctl is-enabled "$UNIT_NAME"
+systemctl preset --preset-mode=disable-only "$UNIT_NAME"
+(! systemctl is-enabled "$UNIT_NAME")
+systemctl enable --runtime "$UNIT_NAME"
+[[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]]
+systemctl is-enabled "$UNIT_NAME"
+systemctl disable "$UNIT_NAME"
+# The unit should be still enabled, as we didn't use the --runtime switch
+systemctl is-enabled "$UNIT_NAME"
+systemctl disable --runtime "$UNIT_NAME"
+(! systemctl is-enabled "$UNIT_NAME")
+
+# mask/unmask/revert
+systemctl disable "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+systemctl mask "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
+systemctl unmask "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+systemctl mask "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
+systemctl revert "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+systemctl mask --runtime "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
+# This should be a no-op without the --runtime switch
+systemctl unmask "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
+systemctl unmask --runtime "$UNIT_NAME"
+[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+
+# add-wants/add-requires
+(! systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service")
+systemctl add-wants "$UNIT_NAME" "systemd-journald.service"
+systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service"
+(! systemctl show -P Requires "$UNIT_NAME" | grep "systemd-journald.service")
+systemctl add-requires "$UNIT_NAME" "systemd-journald.service"
+systemctl show -P Requires "$UNIT_NAME" | grep "systemd-journald.service"
+
+# set-property
+systemctl set-property "$UNIT_NAME" IPAccounting=yes MemoryMax=1234567
+systemctl cat "$UNIT_NAME"
+# These properties should be saved to a persistent storage
+grep -r "IPAccounting=yes" "/etc/systemd/system.control/${UNIT_NAME}.d/"
+grep -r "MemoryMax=1234567" "/etc/systemd/system.control/${UNIT_NAME}.d"
+systemctl revert "$UNIT_NAME"
+(! grep -r "IPAccounting=" "/etc/systemd/system.control/${UNIT_NAME}.d/")
+(! grep -r "MemoryMax=" "/etc/systemd/system.control/${UNIT_NAME}.d/")
+# Same stuff, but with --runtime, which should use /run
+systemctl set-property --runtime "$UNIT_NAME" CPUAccounting=no CPUQuota=10%
+systemctl cat "$UNIT_NAME"
+grep -r "CPUAccounting=no" "/run/systemd/system.control/${UNIT_NAME}.d/"
+grep -r "CPUQuota=10%" "/run/systemd/system.control/${UNIT_NAME}.d/"
+systemctl revert "$UNIT_NAME"
+(! grep -r "CPUAccounting=" "/run/systemd/system.control/${UNIT_NAME}.d/")
+(! grep -r "CPUQuota=" "/run/systemd/system.control/${UNIT_NAME}.d/")
+
+# Failed-unit related tests
+(! systemd-run --wait --unit "failed.service" /bin/false)
+systemctl is-failed failed.service
+systemctl --state=failed | grep failed.service
+systemctl --failed | grep failed.service
+systemctl reset-failed "fail*.service"
+(! systemctl is-failed failed.service)
+
+# clean
+systemctl restart "$UNIT_NAME"
+systemctl stop "$UNIT_NAME"
+# Check if the directories from *Directory= directives exist
+# (except RuntimeDirectory= in /run, which is removed when the unit is stopped)
+for path in /var/lib /var/cache /var/log /etc; do
+ [[ -e "$path/$UNIT_NAME" ]]
+done
+# Run the cleanup
+for what in "" configuration state cache logs runtime all; do
+ systemctl clean ${what:+--what="$what"} "$UNIT_NAME"
+done
+# All respective directories should be removed
+for path in /run /var/lib /var/cache /var/log /etc; do
+ [[ ! -e "$path/$UNIT_NAME" ]]
+done
+
+# --timestamp
+for value in pretty us µs utc us+utc µs+utc; do
+ systemctl show -P KernelTimestamp --timestamp="$value"
+done
+
+# set-default/get-default
+target="$(systemctl get-default)"
+systemctl set-default emergency.target
+[[ "$(systemctl get-default)" == emergency.target ]]
+systemctl set-default "$target"
+[[ "$(systemctl get-default)" == "$target" ]]
+
+# show/status
+systemctl show --property ""
+# Pick a heavily sandboxed unit for the best effect on coverage
+systemctl show systemd-logind.service
+systemctl status
+# Ignore the exit code in this case, as it might try to load non-existing units
+systemctl status -a >/dev/null || :
+systemctl status -a --state active,running,plugged >/dev/null
+systemctl status "systemd-*.timer"
+systemctl status "systemd-journald*.socket"
+systemctl status "sys-devices-*-ttyS0.device"
+systemctl status -- -.mount
+
+# --marked
+systemctl restart "$UNIT_NAME"
+systemctl set-property "$UNIT_NAME" Markers=needs-restart
+systemctl show -P Markers "$UNIT_NAME" | grep needs-restart
+systemctl reload-or-restart --marked
+(! systemctl show -P Markers "$UNIT_NAME" | grep needs-restart)
+
+# --dry-run with destructive verbs
+# kexec is skipped intentionally, as it requires a bit more involved setup
+VERBS=(
+ default
+ emergency
+ exit
+ halt
+ hibernate
+ hybrid-sleep
+ poweroff
+ reboot
+ rescue
+ suspend
+ suspend-then-hibernate
+)
+
+for verb in "${VERBS[@]}"; do
+ systemctl --dry-run "$verb"
+
+ if [[ "$verb" =~ (halt|poweroff|reboot) ]]; then
+ systemctl --dry-run --message "Hello world" "$verb"
+ systemctl --dry-run --no-wall "$verb"
+ systemctl --dry-run -f "$verb"
+ systemctl --dry-run -ff "$verb"
+ fi
+done
+
+# Aux verbs & assorted checks
+systemctl is-active "*-journald.service"
+systemctl cat "*journal*"
+systemctl cat "$UNIT_NAME"
+systemctl help "$UNIT_NAME"
+systemctl service-watchdogs
+systemctl service-watchdogs "$(systemctl service-watchdogs)"
+
+# show/set-environment
+# Make sure PATH is set
+systemctl show-environment | grep -q '^PATH='
+# Let's add an entry and override a built-in one
+systemctl set-environment PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/testaddition FOO=BAR
+# Check that both are set
+systemctl show-environment | grep -q '^PATH=.*testaddition$'
+systemctl show-environment | grep -q '^FOO=BAR$'
+systemctl daemon-reload
+# Check again after the reload
+systemctl show-environment | grep -q '^PATH=.*testaddition$'
+systemctl show-environment | grep -q '^FOO=BAR$'
+# Check that JSON output is supported
+systemctl show-environment --output=json | grep -q '^{.*"FOO":"BAR".*}$'
+# Drop both
+systemctl unset-environment FOO PATH
+# Check that one is gone and the other reverted to the built-in
+systemctl show-environment | grep '^FOO=$' && exit 1
+systemctl show-environment | grep '^PATH=.*testaddition$' && exit 1
+systemctl show-environment | grep -q '^PATH='
+# Check import-environment
+export IMPORT_THIS=hello
+export IMPORT_THIS_TOO=world
+systemctl import-environment IMPORT_THIS IMPORT_THIS_TOO
+systemctl show-environment | grep "^IMPORT_THIS=$IMPORT_THIS"
+systemctl show-environment | grep "^IMPORT_THIS_TOO=$IMPORT_THIS_TOO"
+systemctl unset-environment IMPORT_THIS IMPORT_THIS_TOO
+(! systemctl show-environment | grep "^IMPORT_THIS=")
+(! systemctl show-environment | grep "^IMPORT_THIS_TOO=")
+
+# test for sysv-generator (issue #24990)
+if [[ -x /usr/lib/systemd/system-generators/systemd-sysv-generator ]]; then
+ mkdir -p /etc/init.d
+ # invalid dependency
+ cat >/etc/init.d/issue-24990 <<\EOF
+#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides:test1 test2
+# Required-Start:test1 $remote_fs $network
+# Required-Stop:test1 $remote_fs $network
+# Description:Test
+# Short-Description: Test
+### END INIT INFO
+
+case "$1" in
+ start)
+ echo "Starting issue-24990.service"
+ sleep 1000 &
+ ;;
+ stop)
+ echo "Stopping issue-24990.service"
+ sleep 10 &
+ ;;
+ *)
+ echo "Usage: service test {start|stop|restart|status}"
+ ;;
+esac
+EOF
+
+ chmod +x /etc/init.d/issue-24990
+ systemctl daemon-reload
+ [[ -L /run/systemd/generator.late/test1.service ]]
+ [[ -L /run/systemd/generator.late/test2.service ]]
+ assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service"
+ assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service"
+ output=$(systemctl cat issue-24990)
+ assert_in "SourcePath=/etc/init.d/issue-24990" "$output"
+ assert_in "Description=LSB: Test" "$output"
+ assert_in "After=test1.service" "$output"
+ assert_in "After=remote-fs.target" "$output"
+ assert_in "After=network-online.target" "$output"
+ assert_in "Wants=network-online.target" "$output"
+ assert_in "ExecStart=/etc/init.d/issue-24990 start" "$output"
+ assert_in "ExecStop=/etc/init.d/issue-24990 stop" "$output"
+ systemctl status issue-24990 || :
+ systemctl show issue-24990
+ assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)"
+ assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)"
+
+ if ! systemctl is-active network-online.target; then
+ systemctl start network-online.target
+ fi
+
+ systemctl restart issue-24990
+ systemctl stop issue-24990
+
+ # valid dependency
+ cat >/etc/init.d/issue-24990 <<\EOF
+#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides:test1 test2
+# Required-Start:$remote_fs
+# Required-Stop:$remote_fs
+# Description:Test
+# Short-Description: Test
+### END INIT INFO
+
+case "$1" in
+ start)
+ echo "Starting issue-24990.service"
+ sleep 1000 &
+ ;;
+ stop)
+ echo "Stopping issue-24990.service"
+ sleep 10 &
+ ;;
+ *)
+ echo "Usage: service test {start|stop|restart|status}"
+ ;;
+esac
+EOF
+
+ chmod +x /etc/init.d/issue-24990
+ systemctl daemon-reload
+ [[ -L /run/systemd/generator.late/test1.service ]]
+ [[ -L /run/systemd/generator.late/test2.service ]]
+ assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service"
+ assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service"
+ output=$(systemctl cat issue-24990)
+ assert_in "SourcePath=/etc/init.d/issue-24990" "$output"
+ assert_in "Description=LSB: Test" "$output"
+ assert_in "After=remote-fs.target" "$output"
+ assert_in "ExecStart=/etc/init.d/issue-24990 start" "$output"
+ assert_in "ExecStop=/etc/init.d/issue-24990 stop" "$output"
+ systemctl status issue-24990 || :
+ systemctl show issue-24990
+ assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)"
+ assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)"
+
+ systemctl restart issue-24990
+ systemctl stop issue-24990
+fi
+
+# %J in WantedBy= causes ABRT (#26467)
+cat >/run/systemd/system/test-WantedBy.service <<EOF
+[Service]
+ExecStart=true
+
+[Install]
+WantedBy=user-%i@%J.service
+EOF
+systemctl daemon-reload
+systemctl enable --now test-WantedBy.service || :
+systemctl daemon-reload
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-27.service b/test/units/testsuite-27.service
new file mode 100644
index 0000000..454fde6
--- /dev/null
+++ b/test/units/testsuite-27.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-27-STDOUTFILE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-27.sh b/test/units/testsuite-27.sh
new file mode 100755
index 0000000..c0701f3
--- /dev/null
+++ b/test/units/testsuite-27.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+systemd-run --wait --unit=test27-one \
+ -p StandardOutput=file:/tmp/stdout \
+ -p StandardError=file:/tmp/stderr \
+ -p Type=exec \
+ sh -c 'echo x ; echo y >&2'
+cmp /tmp/stdout <<EOF
+x
+EOF
+cmp /tmp/stderr <<EOF
+y
+EOF
+
+systemd-run --wait --unit=test27-two \
+ -p StandardOutput=file:/tmp/stdout \
+ -p StandardError=file:/tmp/stderr \
+ -p Type=exec \
+ sh -c 'echo z ; echo a >&2'
+cmp /tmp/stdout <<EOF
+z
+EOF
+cmp /tmp/stderr <<EOF
+a
+EOF
+
+systemd-run --wait --unit=test27-three \
+ -p StandardOutput=append:/tmp/stdout \
+ -p StandardError=append:/tmp/stderr \
+ -p Type=exec \
+ sh -c 'echo b ; echo c >&2'
+cmp /tmp/stdout <<EOF
+z
+b
+EOF
+cmp /tmp/stderr <<EOF
+a
+c
+EOF
+
+systemd-run --wait --unit=test27-four \
+ -p StandardOutput=truncate:/tmp/stdout \
+ -p StandardError=truncate:/tmp/stderr \
+ -p Type=exec \
+ sh -c 'echo a ; echo b >&2'
+cmp /tmp/stdout <<EOF
+a
+EOF
+cmp /tmp/stderr <<EOF
+b
+EOF
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-28.service b/test/units/testsuite-28.service
new file mode 100644
index 0000000..222de00
--- /dev/null
+++ b/test/units/testsuite-28.service
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-28-PERCENTJ-WANTEDBY
+# Testsuite: Ensure %j Wants directives work
+Wants=specifier-j-wants.service
+After=specifier-j-wants.service
+Requires=testsuite-28-pre.service
+After=testsuite-28-pre.service
+
+[Service]
+ExecStart=true
+Type=oneshot
diff --git a/test/units/testsuite-29.service b/test/units/testsuite-29.service
new file mode 100644
index 0000000..035c6bf
--- /dev/null
+++ b/test/units/testsuite-29.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-29-PORTABLE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh
new file mode 100755
index 0000000..870da8b
--- /dev/null
+++ b/test/units/testsuite-29.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+set -o pipefail
+
+# Set longer timeout for slower machines, e.g. non-KVM vm.
+mkdir -p /run/systemd/system.conf.d
+cat >/run/systemd/system.conf.d/10-timeout.conf <<EOF
+[Manager]
+DefaultEnvironment=SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC=30
+ManagerEnvironment=SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC=30
+EOF
+
+systemctl daemon-reexec
+
+export SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC=30
+
+udevadm control --log-level debug
+
+ARGS=()
+STATE_DIRECTORY=/var/lib/private/
+if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
+ # If we're running under sanitizers, we need to use a less restrictive
+ # profile, otherwise LSan syscall would get blocked by seccomp
+ ARGS+=(--profile=trusted)
+ # With the trusted profile DynamicUser is disabled, so the storage is not in private/
+ STATE_DIRECTORY=/var/lib/
+fi
+# Bump the timeout if we're running with plain QEMU
+[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=90 || TIMEOUT=30
+
+systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable service'
+systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service'
+systemd-dissect --no-pager /usr/share/app0.raw | grep -q '✓ extension for portable service'
+systemd-dissect --no-pager /usr/share/app1.raw | grep -q '✓ extension for portable service'
+
+export SYSTEMD_LOG_LEVEL=debug
+mkdir -p /run/systemd/system/systemd-portabled.service.d/
+cat <<EOF >/run/systemd/system/systemd-portabled.service.d/override.conf
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+
+portablectl "${ARGS[@]}" attach --now --runtime /usr/share/minimal_0.raw minimal-app0
+
+portablectl is-attached minimal-app0
+portablectl inspect /usr/share/minimal_0.raw minimal-app0.service
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-foo.service
+systemctl is-active minimal-app0-bar.service && exit 1
+
+# Running with sanitizers may freeze the invoked service. See issue #24147.
+# Let's set timeout to improve performance.
+timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime /usr/share/minimal_1.raw minimal-app0
+
+portablectl is-attached minimal-app0
+portablectl inspect /usr/share/minimal_0.raw minimal-app0.service
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-bar.service
+systemctl is-active minimal-app0-foo.service && exit 1
+
+portablectl list | grep -q -F "minimal_1"
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1'
+
+portablectl detach --now --runtime /usr/share/minimal_1.raw minimal-app0
+
+portablectl list | grep -q -F "No images."
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1
+
+# Ensure we don't regress (again) when using --force
+
+portablectl "${ARGS[@]}" attach --force --now --runtime /usr/share/minimal_0.raw minimal-app0
+
+portablectl is-attached --force minimal-app0
+portablectl inspect --force /usr/share/minimal_0.raw minimal-app0.service
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-foo.service
+systemctl is-active minimal-app0-bar.service && exit 1
+
+portablectl "${ARGS[@]}" reattach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
+
+portablectl is-attached --force minimal-app0
+portablectl inspect --force /usr/share/minimal_0.raw minimal-app0.service
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-bar.service
+systemctl is-active minimal-app0-foo.service && exit 1
+
+portablectl list | grep -q -F "minimal_1"
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1'
+
+portablectl detach --force --now --runtime /usr/share/minimal_1.raw minimal-app0
+
+portablectl list | grep -q -F "No images."
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1
+
+# portablectl also works with directory paths rather than images
+
+unsquashfs -dest /tmp/minimal_0 /usr/share/minimal_0.raw
+unsquashfs -dest /tmp/minimal_1 /usr/share/minimal_1.raw
+
+portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/minimal_0 minimal-app0
+
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-foo.service
+systemctl is-active minimal-app0-bar.service && exit 1
+
+timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --enable --runtime /tmp/minimal_1 minimal-app0
+
+systemctl is-active minimal-app0.service
+systemctl is-active minimal-app0-bar.service
+systemctl is-active minimal-app0-foo.service && exit 1
+
+portablectl list | grep -q -F "minimal_1"
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1'
+
+portablectl detach --now --enable --runtime /tmp/minimal_1 minimal-app0
+
+portablectl list | grep -q -F "No images."
+busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1
+
+portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
+
+systemctl is-active app0.service
+status="$(portablectl is-attached --extension app0 minimal_0)"
+[[ "${status}" == "running-runtime" ]]
+
+timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
+
+systemctl is-active app0.service
+status="$(portablectl is-attached --extension app0 minimal_1)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
+
+portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1 minimal_0)"
+[[ "${status}" == "running-runtime" ]]
+
+# Ensure that adding or removing a version to the image doesn't break reattaching
+cp /usr/share/app1.raw /tmp/app1_2.raw
+timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1_2.raw /usr/share/minimal_1.raw app1
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1_2 minimal_1)"
+[[ "${status}" == "running-runtime" ]]
+
+timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1 minimal_1)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl detach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
+portablectl "${ARGS[@]}" attach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
+systemctl daemon-reload
+systemctl restart app1.service
+
+systemctl is-active app1.service
+status="$(portablectl is-attached --extension app1 minimal_0)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
+
+# Ensure that the combination of read-only images, state directory and dynamic user works, and that
+# state is retained. Check after detaching, as on slow systems (eg: sanitizers) it might take a while
+# after the service is attached before the file appears.
+grep -q -F bar "${STATE_DIRECTORY}/app0/foo"
+grep -q -F baz "${STATE_DIRECTORY}/app1/foo"
+
+# Ensure that we can override the check on extension-release.NAME
+cp /usr/share/app0.raw /tmp/app10.raw
+portablectl "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
+
+systemctl is-active app0.service
+status="$(portablectl is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)"
+[[ "${status}" == "running-runtime" ]]
+
+portablectl inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -q -F "Extension Release: /tmp/app10.raw"
+
+# Ensure that we can detach even when an image has been deleted already (stop the unit manually as
+# portablectl won't find it)
+rm -f /tmp/app10.raw
+systemctl stop app0.service
+portablectl detach --force --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0
+
+# portablectl also works with directory paths rather than images
+
+mkdir /tmp/rootdir /tmp/app0 /tmp/app1 /tmp/overlay /tmp/os-release-fix /tmp/os-release-fix/etc
+mount /usr/share/app0.raw /tmp/app0
+mount /usr/share/app1.raw /tmp/app1
+mount /usr/share/minimal_0.raw /tmp/rootdir
+
+# Fix up os-release to drop the valid PORTABLE_SERVICES field (because we are
+# bypassing the sysext logic in portabled here it will otherwise not see the
+# extensions additional valid prefix)
+grep -v "^PORTABLE_PREFIXES=" /tmp/rootdir/etc/os-release > /tmp/os-release-fix/etc/os-release
+
+mount -t overlay overlay -o lowerdir=/tmp/os-release-fix:/tmp/app1:/tmp/rootdir /tmp/overlay
+
+grep . /tmp/overlay/usr/lib/extension-release.d/*
+grep . /tmp/overlay/etc/os-release
+
+portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/overlay app1
+
+systemctl is-active app1.service
+
+portablectl detach --now --runtime overlay app1
+
+umount /tmp/overlay
+
+portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+
+systemctl is-active app0.service
+systemctl is-active app1.service
+
+portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/rootdir/usr/lib/os-release
+portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/extension-release.d/extension-release.app0
+portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/extension-release.d/extension-release.app2
+portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/systemd/system/app1.service
+portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/systemd/system/app0.service
+
+portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+
+# Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
+# Provides coverage for https://github.com/systemd/systemd/issues/23481
+portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0
+portablectl detach --now --runtime --enable /tmp/rootdir minimal-app0
+# attach and detach again to check if all drop-in configs are removed even if the main unit files are removed
+portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0
+portablectl detach --now --runtime --enable /tmp/rootdir minimal-app0
+
+umount /tmp/rootdir
+umount /tmp/app0
+umount /tmp/app1
+
+# Lack of ID field in os-release should be rejected, but it caused a crash in the past instead
+mkdir -p /tmp/emptyroot/usr/lib
+mkdir -p /tmp/emptyext/usr/lib/extension-release.d
+touch /tmp/emptyroot/usr/lib/os-release
+touch /tmp/emptyext/usr/lib/extension-release.d/extension-release.emptyext
+
+# Remote peer disconnected -> portabled crashed
+res="$(! portablectl attach --extension /tmp/emptyext /tmp/emptyroot 2> >(grep "Remote peer disconnected"))"
+test -z "${res}"
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-30.service b/test/units/testsuite-30.service
new file mode 100644
index 0000000..253f7b5
--- /dev/null
+++ b/test/units/testsuite-30.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-30-ONCLOCKCHANGE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-30.sh b/test/units/testsuite-30.sh
new file mode 100755
index 0000000..57f4666
--- /dev/null
+++ b/test/units/testsuite-30.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+systemctl disable --now systemd-timesyncd.service
+
+timedatectl set-timezone Europe/Berlin
+timedatectl set-time 1980-10-15
+
+systemd-run --on-timezone-change touch /tmp/timezone-changed
+systemd-run --on-clock-change touch /tmp/clock-changed
+
+test ! -f /tmp/timezone-changed
+test ! -f /tmp/clock-changed
+
+timedatectl set-timezone Europe/Kiev
+
+while test ! -f /tmp/timezone-changed ; do sleep .5 ; done
+
+timedatectl set-time 2018-1-1
+
+while test ! -f /tmp/clock-changed ; do sleep .5 ; done
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-31.service b/test/units/testsuite-31.service
new file mode 100644
index 0000000..f0e78a9
--- /dev/null
+++ b/test/units/testsuite-31.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-31-DEVICE-ENUMERATION
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-31.sh b/test/units/testsuite-31.sh
new file mode 100755
index 0000000..024ad36
--- /dev/null
+++ b/test/units/testsuite-31.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+if journalctl -b -t systemd --grep '\.device: Changed plugged -> dead'; then
+ exit 1
+fi
+
+echo OK >/testok
+exit 0
diff --git a/test/units/testsuite-32.service b/test/units/testsuite-32.service
new file mode 100644
index 0000000..50f5823
--- /dev/null
+++ b/test/units/testsuite-32.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-32-OOMPOLICY
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+MemoryAccounting=yes
diff --git a/test/units/testsuite-32.sh b/test/units/testsuite-32.sh
new file mode 100755
index 0000000..5c289d0
--- /dev/null
+++ b/test/units/testsuite-32.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Let's run this test only if the "memory.oom.group" cgroupfs attribute
+# exists. This test is a bit too strict, since the "memory.events"/"oom_kill"
+# logic has been around since a longer time than "memory.oom.group", but it's
+# an easier thing to test for, and also: let's not get confused by older
+# kernels where the concept was still new.
+
+if test -f /sys/fs/cgroup/system.slice/testsuite-32.service/memory.oom.group; then
+ systemd-analyze log-level debug
+
+ # Run a service that is guaranteed to be the first candidate for OOM killing
+ systemd-run --unit=oomtest.service \
+ -p Type=exec -p OOMScoreAdjust=1000 -p OOMPolicy=stop -p MemoryAccounting=yes \
+ sleep infinity
+
+ # Trigger an OOM killer run
+ echo 1 >/proc/sys/kernel/sysrq
+ echo f >/proc/sysrq-trigger
+
+ while : ; do
+ STATE="$(systemctl show -P ActiveState oomtest.service)"
+ [ "$STATE" = "failed" ] && break
+ sleep .5
+ done
+
+ RESULT="$(systemctl show -P Result oomtest.service)"
+ test "$RESULT" = "oom-kill"
+
+ systemd-analyze log-level info
+fi
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-33.service b/test/units/testsuite-33.service
new file mode 100644
index 0000000..582cdb1
--- /dev/null
+++ b/test/units/testsuite-33.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-33-CLEAN-UNIT
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-33.sh b/test/units/testsuite-33.sh
new file mode 100755
index 0000000..d48bef7
--- /dev/null
+++ b/test/units/testsuite-33.sh
@@ -0,0 +1,320 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+set -o pipefail
+
+cat >/run/systemd/system/test-service.service <<EOF
+[Service]
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
+RuntimeDirectoryPreserve=yes
+ExecStart=/bin/sleep infinity
+Type=exec
+EOF
+
+systemctl daemon-reload
+
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl start test-service
+
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
+
+(! systemctl clean test-service)
+
+systemctl stop test-service
+
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
+
+systemctl clean test-service --what=configuration
+
+test ! -e /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
+
+systemctl clean test-service
+
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test -d /var/log/test-service
+
+systemctl clean test-service --what=logs
+
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl clean test-service --what=all
+
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+cat >/run/systemd/system/test-service.service <<EOF
+[Service]
+DynamicUser=yes
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
+RuntimeDirectoryPreserve=yes
+ExecStart=/bin/sleep infinity
+Type=exec
+EOF
+
+systemctl daemon-reload
+
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl restart test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+(! systemctl clean test-service)
+
+systemctl stop test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=configuration
+
+test ! -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=logs
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
+
+systemctl clean test-service --what=all
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test ! -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test ! -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
+
+cat >/run/systemd/system/tmp-hoge.mount <<EOF
+[Mount]
+What=tmpfs
+Type=tmpfs
+ConfigurationDirectory=hoge
+RuntimeDirectory=hoge
+StateDirectory=hoge
+CacheDirectory=hoge
+LogsDirectory=hoge
+EOF
+
+systemctl daemon-reload
+
+test ! -e /etc/hoge
+test ! -e /run/hoge
+test ! -e /var/lib/hoge
+test ! -e /var/cache/hoge
+test ! -e /var/log/hoge
+
+systemctl start tmp-hoge.mount
+
+test -d /etc/hoge
+test -d /run/hoge
+test -d /var/lib/hoge
+test -d /var/cache/hoge
+test -d /var/log/hoge
+
+(! systemctl clean tmp-hoge.mount)
+
+test -d /etc/hoge
+test -d /run/hoge
+test -d /var/lib/hoge
+test -d /var/cache/hoge
+test -d /var/log/hoge
+
+systemctl stop tmp-hoge.mount
+
+test -d /etc/hoge
+test ! -d /run/hoge
+test -d /var/lib/hoge
+test -d /var/cache/hoge
+test -d /var/log/hoge
+
+systemctl clean tmp-hoge.mount --what=configuration
+
+test ! -d /etc/hoge
+test ! -d /run/hoge
+test -d /var/lib/hoge
+test -d /var/cache/hoge
+test -d /var/log/hoge
+
+systemctl clean tmp-hoge.mount
+
+test ! -d /etc/hoge
+test ! -d /run/hoge
+test -d /var/lib/hoge
+test ! -d /var/cache/hoge
+test -d /var/log/hoge
+
+systemctl clean tmp-hoge.mount --what=logs
+
+test ! -d /etc/hoge
+test ! -d /run/hoge
+test -d /var/lib/hoge
+test ! -d /var/cache/hoge
+test ! -d /var/log/hoge
+
+systemctl clean tmp-hoge.mount --what=all
+
+test ! -d /etc/hoge
+test ! -d /run/hoge
+test ! -d /var/lib/hoge
+test ! -d /var/cache/hoge
+test ! -d /var/log/hoge
+
+cat >/run/systemd/system/test-service.socket <<EOF
+[Socket]
+ListenSequentialPacket=/run/test-service.socket
+RemoveOnStop=yes
+ExecStartPre=true
+ConfigurationDirectory=test-socket
+RuntimeDirectory=test-socket
+StateDirectory=test-socket
+CacheDirectory=test-socket
+LogsDirectory=test-socket
+EOF
+
+systemctl daemon-reload
+
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
+
+systemctl start test-service.socket
+
+test -d /etc/test-socket
+test -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
+
+(! systemctl clean test-service.socket)
+
+systemctl stop test-service.socket
+
+test -d /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
+
+systemctl clean test-service.socket --what=configuration
+
+test ! -e /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
+
+systemctl clean test-service.socket
+
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test -d /var/log/test-socket
+
+systemctl clean test-service.socket --what=logs
+
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
+
+systemctl clean test-service.socket --what=all
+
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-34.service b/test/units/testsuite-34.service
new file mode 100644
index 0000000..6917afe
--- /dev/null
+++ b/test/units/testsuite-34.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-34-DYNAMICUSERMIGRATE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-34.sh b/test/units/testsuite-34.sh
new file mode 100755
index 0000000..0bc3adc
--- /dev/null
+++ b/test/units/testsuite-34.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+test_directory() {
+ local directory="$1"
+ local path="$2"
+
+ # cleanup for previous invocation
+ for i in xxx xxx2 yyy zzz x:yz x:yz2; do
+ rm -rf "${path:?}/${i}" "${path:?}/private/${i}"
+ done
+
+ # Set everything up without DynamicUser=1
+
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz touch "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+
+ test -d "${path}"/zzz
+ test ! -L "${path}"/zzz
+ test ! -e "${path}"/private/zzz
+
+ test ! -e "${path}"/xxx
+ test ! -e "${path}"/private/xxx
+ test ! -e "${path}"/xxx2
+ test ! -e "${path}"/private/xxx2
+ test -L "${path}"/yyy
+ test ! -e "${path}"/private/yyy
+
+ test -f "${path}"/zzz/test
+ test ! -e "${path}"/zzz/test-missing
+
+ # Convert to DynamicUser=1
+
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}=zzz:xxx zzz:xxx2" \
+ -p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+
+ test -L "${path}"/zzz
+ test -d "${path}"/private/zzz
+
+ test ! -e "${path}"/xxx
+ test ! -e "${path}"/private/xxx
+ test ! -e "${path}"/xxx2
+ test ! -e "${path}"/private/xxx2
+ test -L "${path}"/yyy # previous symlink is not removed
+ test ! -e "${path}"/private/yyy
+
+ test -f "${path}"/zzz/test
+ test ! -e "${path}"/zzz/test-missing
+
+ # Convert back
+
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
+
+ test -d "${path}"/zzz
+ test ! -L "${path}"/zzz
+ test ! -e "${path}"/private/zzz
+
+ test ! -e "${path}"/xxx
+ test ! -e "${path}"/private/xxx
+ test ! -e "${path}"/xxx2
+ test ! -e "${path}"/private/xxx2
+ test -L "${path}"/yyy
+ test ! -e "${path}"/private/yyy
+
+ test -f "${path}"/zzz/test
+ test ! -e "${path}"/zzz/test-missing
+
+ # Exercise the unit parsing paths too
+ cat >/run/systemd/system/testservice-34.service <<EOF
+[Service]
+Type=oneshot
+TemporaryFileSystem=${path}
+RuntimeDirectoryPreserve=yes
+${directory}=zzz:x\:yz zzz:x\:yz2
+ExecStart=test -f ${path}/x:yz2/test
+ExecStart=test -f ${path}/x:yz/test
+ExecStart=test -f ${path}/zzz/test
+EOF
+ systemctl daemon-reload
+ systemctl start --wait testservice-34.service
+
+ test -d "${path}"/zzz
+ test ! -L "${path}"/zzz
+ test ! -e "${path}"/private/zzz
+
+ test ! -L "${path}"/x:yz
+ test ! -L "${path}"/x:yz2
+}
+
+test_check_writable() {
+ # cleanup for previous invocation
+ for i in aaa quux waldo xxx; do
+ rm -rf "/var/lib/$i" "/var/lib/private/$i"
+ done
+
+ cat >/run/systemd/system/testservice-34-check-writable.service <<\EOF
+[Unit]
+Description=Check writable directories when DynamicUser= with StateDirectory=
+
+[Service]
+# Relevant only for sanitizer runs
+EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+
+Type=oneshot
+DynamicUser=yes
+StateDirectory=waldo quux/pief aaa/bbb aaa aaa/ccc xxx/yyy:aaa/111 xxx:aaa/222 xxx/zzz:aaa/333
+
+# Make sure that the state directories are really the only writable directory besides the obvious candidates
+ExecStart=bash -c ' \
+ set -eux; \
+ set -o pipefail; \
+ declare -a writable_dirs; \
+ readarray -t writable_dirs < <(find / \( -path /var/tmp -o -path /tmp -o -path /proc -o -path /dev/mqueue -o -path /dev/shm -o \
+ -path /sys/fs/bpf -o -path /dev/.lxc -o -path /sys/devices/system/cpu \) \
+ -prune -o -type d -writable -print 2>/dev/null | sort -u); \
+ [[ "$${#writable_dirs[@]}" == "8" ]]; \
+ [[ "$${writable_dirs[0]}" == "/var/lib/private/aaa" ]]; \
+ [[ "$${writable_dirs[1]}" == "/var/lib/private/aaa/bbb" ]]; \
+ [[ "$${writable_dirs[2]}" == "/var/lib/private/aaa/ccc" ]]; \
+ [[ "$${writable_dirs[3]}" == "/var/lib/private/quux/pief" ]]; \
+ [[ "$${writable_dirs[4]}" == "/var/lib/private/waldo" ]]; \
+ [[ "$${writable_dirs[5]}" == "/var/lib/private/xxx" ]]; \
+ [[ "$${writable_dirs[6]}" == "/var/lib/private/xxx/yyy" ]]; \
+ [[ "$${writable_dirs[7]}" == "/var/lib/private/xxx/zzz" ]]; \
+'
+EOF
+ systemctl daemon-reload
+ systemctl start testservice-34-check-writable.service
+}
+
+test_directory "StateDirectory" "/var/lib"
+test_directory "RuntimeDirectory" "/run"
+test_directory "CacheDirectory" "/var/cache"
+test_directory "LogsDirectory" "/var/log"
+
+test_check_writable
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-35.service b/test/units/testsuite-35.service
new file mode 100644
index 0000000..0599f61
--- /dev/null
+++ b/test/units/testsuite-35.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-35-LOGIN
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh
new file mode 100755
index 0000000..89e1562
--- /dev/null
+++ b/test/units/testsuite-35.sh
@@ -0,0 +1,592 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+cleanup_test_user() (
+ set +ex
+
+ pkill -u "$(id -u logind-test-user)"
+ sleep 1
+ pkill -KILL -u "$(id -u logind-test-user)"
+ userdel -r logind-test-user
+
+ return 0
+)
+
+setup_test_user() {
+ mkdir -p /var/spool/cron /var/spool/mail
+ useradd -m -s /bin/bash logind-test-user
+ trap cleanup_test_user EXIT
+}
+
+test_enable_debug() {
+ mkdir -p /run/systemd/system/systemd-logind.service.d
+ cat >/run/systemd/system/systemd-logind.service.d/debug.conf <<EOF
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+ systemctl daemon-reload
+ systemctl stop systemd-logind.service
+}
+
+test_properties() {
+ mkdir -p /run/systemd/logind.conf.d
+
+ cat >/run/systemd/logind.conf.d/kill-user-processes.conf <<EOF
+[Login]
+KillUserProcesses=no
+EOF
+
+ systemctl restart systemd-logind.service
+ assert_eq "$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager KillUserProcesses)" "b false"
+
+ cat >/run/systemd/logind.conf.d/kill-user-processes.conf <<EOF
+[Login]
+KillUserProcesses=yes
+EOF
+
+ systemctl restart systemd-logind.service
+ assert_eq "$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager KillUserProcesses)" "b true"
+
+ rm -rf /run/systemd/logind.conf.d
+}
+
+test_started() {
+ local pid
+
+ systemctl restart systemd-logind.service
+
+ # should start at boot, not with D-BUS activation
+ pid=$(systemctl show systemd-logind.service -p ExecMainPID --value)
+
+ # loginctl should succeed
+ loginctl
+
+ # logind should still be running
+ assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid"
+}
+
+wait_suspend() {
+ timeout "${1?}" bash -c "while [[ ! -e /run/suspend.flag ]]; do sleep 1; done"
+ rm /run/suspend.flag
+}
+
+teardown_suspend() (
+ set +eux
+
+ pkill evemu-device
+
+ rm -rf /run/systemd/system/systemd-suspend.service.d
+ systemctl daemon-reload
+
+ rm -f /run/udev/rules.d/70-logindtest-lid.rules
+ udevadm control --reload
+
+ return 0
+)
+
+test_suspend_on_lid() {
+ local pid input_name lid_dev
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping suspend test in container"
+ return
+ fi
+ if ! grep -s -q mem /sys/power/state; then
+ echo "suspend not supported on this testbed, skipping"
+ return
+ fi
+ if ! command -v evemu-device >/dev/null; then
+ echo "command evemu-device not found, skipping"
+ return
+ fi
+ if ! command -v evemu-event >/dev/null; then
+ echo "command evemu-event not found, skipping"
+ return
+ fi
+
+ trap teardown_suspend RETURN
+
+ # save pid
+ pid=$(systemctl show systemd-logind.service -p ExecMainPID --value)
+
+ # create fake suspend
+ mkdir -p /run/systemd/system/systemd-suspend.service.d
+ cat >/run/systemd/system/systemd-suspend.service.d/override.conf <<EOF
+[Service]
+ExecStart=
+ExecStart=touch /run/suspend.flag
+EOF
+ systemctl daemon-reload
+
+ # create fake lid switch
+ mkdir -p /run/udev/rules.d
+ cat >/run/udev/rules.d/70-logindtest-lid.rules <<EOF
+SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Fake Lid Switch", TAG+="power-switch"
+EOF
+ udevadm control --reload
+
+ cat >/run/lidswitch.evemu <<EOF
+# EVEMU 1.2
+# Input device name: "Lid Switch"
+# Input device ID: bus 0x19 vendor 0000 product 0x05 version 0000
+# Supported events:
+# Event type 0 (EV_SYN)
+# Event code 0 (SYN_REPORT)
+# Event code 5 (FF_STATUS_MAX)
+# Event type 5 (EV_SW)
+# Event code 0 (SW_LID)
+# Properties:
+N: Fake Lid Switch
+I: 0019 0000 0005 0000
+P: 00 00 00 00 00 00 00 00
+B: 00 21 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 00 00 00 00 00 00 00 00
+B: 04 00 00 00 00 00 00 00 00
+B: 05 01 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+EOF
+
+ evemu-device /run/lidswitch.evemu &
+
+ timeout 20 bash -c 'while ! grep "^Fake Lid Switch" /sys/class/input/*/device/name; do sleep .5; done'
+ input_name=$(grep -l '^Fake Lid Switch' /sys/class/input/*/device/name || :)
+ if [[ -z "$input_name" ]]; then
+ echo "cannot find fake lid switch." >&2
+ exit 1
+ fi
+ input_name=${input_name%/device/name}
+ lid_dev=/dev/${input_name#/sys/class/}
+ udevadm info --wait-for-initialization=10s "$lid_dev"
+ udevadm settle
+
+ # close lid
+ evemu-event "$lid_dev" --sync --type 5 --code 0 --value 1
+ # need to wait for 30s suspend inhibition after boot
+ wait_suspend 31
+ # open lid again
+ evemu-event "$lid_dev" --sync --type 5 --code 0 --value 0
+
+ # waiting for 30s inhibition time between suspends
+ sleep 30
+
+ # now closing lid should cause instant suspend
+ evemu-event "$lid_dev" --sync --type 5 --code 0 --value 1
+ wait_suspend 2
+ evemu-event "$lid_dev" --sync --type 5 --code 0 --value 0
+
+ assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid"
+}
+
+test_shutdown() {
+ local pid
+
+ # save pid
+ pid=$(systemctl show systemd-logind.service -p ExecMainPID --value)
+
+ # scheduled shutdown with wall message
+ shutdown 2>&1
+ sleep 5
+ shutdown -c || :
+ # logind should still be running
+ assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid"
+
+ # scheduled shutdown without wall message
+ shutdown --no-wall 2>&1
+ sleep 5
+ shutdown -c --no-wall || true
+ assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid"
+}
+
+cleanup_session() (
+ set +ex
+
+ local uid s
+
+ uid=$(id -u logind-test-user)
+
+ loginctl disable-linger logind-test-user
+
+ systemctl stop getty@tty2.service
+
+ for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do
+ echo "INFO: stopping session $s"
+ loginctl terminate-session "$s"
+ done
+
+ loginctl terminate-user logind-test-user
+
+ if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then
+ echo "WARNING: session for logind-test-user still active, ignoring."
+ fi
+
+ pkill -u "$uid"
+ sleep 1
+ pkill -KILL -u "$uid"
+
+ if ! timeout 30 bash -c "while systemctl is-active --quiet user@${uid}.service; do sleep 1; done"; then
+ echo "WARNING: user@${uid}.service is still active, ignoring."
+ fi
+
+ if ! timeout 30 bash -c "while systemctl is-active --quiet user-runtime-dir@${uid}.service; do sleep 1; done"; then
+ echo "WARNING: user-runtime-dir@${uid}.service is still active, ignoring."
+ fi
+
+ if ! timeout 30 bash -c "while systemctl is-active --quiet user-${uid}.slice; do sleep 1; done"; then
+ echo "WARNING: user-${uid}.slice is still active, ignoring."
+ fi
+
+ rm -rf /run/systemd/system/getty@tty2.service.d
+ systemctl daemon-reload
+
+ return 0
+)
+
+teardown_session() (
+ set +ex
+
+ cleanup_session
+
+ rm -f /run/udev/rules.d/70-logindtest-scsi_debug-user.rules
+ udevadm control --reload
+ rmmod scsi_debug
+
+ return 0
+)
+
+check_session() (
+ set +ex
+
+ local seat session leader_pid
+
+ if [[ $(loginctl --no-legend | 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 '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 | awk '$3 == "logind-test-user" { print $1 }')
+ if [[ -z "$session" ]]; then
+ echo "no session found for user logind-test-user" >&2
+ return 1
+ fi
+
+ if ! loginctl session-status "$session" | grep -q "Unit: session-${session}\.scope"; then
+ echo "cannot find scope unit for session $session" >&2
+ return 1
+ fi
+
+ leader_pid=$(loginctl session-status "$session" | awk '$1 == "Leader:" { print $2 }')
+ if [[ -z "$leader_pid" ]]; then
+ echo "cannot found leader process for session $session" >&2
+ return 1
+ fi
+
+ # cgroup v1: "1:name=systemd:/user.slice/..."; unified hierarchy: "0::/user.slice"
+ if ! grep -q -E '(name=systemd|^0:):.*session.*scope' /proc/"$leader_pid"/cgroup; then
+ echo "FAIL: process $leader_pid is not in the session cgroup" >&2
+ cat /proc/self/cgroup
+ return 1
+ fi
+)
+
+create_session() {
+ # login with the test user to start a session
+ mkdir -p /run/systemd/system/getty@tty2.service.d
+ cat >/run/systemd/system/getty@tty2.service.d/override.conf <<EOF
+[Service]
+Type=simple
+ExecStart=
+ExecStart=-/sbin/agetty --autologin logind-test-user --noclear %I $TERM
+Restart=no
+EOF
+ systemctl daemon-reload
+
+ systemctl restart getty@tty2.service
+
+ # check session
+ for i in {1..30}; do
+ (( i > 1 )) && sleep 1
+ check_session && break
+ done
+ check_session
+ assert_eq "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" "tty2"
+}
+
+test_sanity_check() {
+ # Exercise basic loginctl options
+
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ trap cleanup_session RETURN
+ create_session
+
+ # Run most of the loginctl commands from a user session to make
+ # the seat/session autodetection work-ish
+ systemd-run --user --pipe --wait -M "logind-test-user@.host" bash -eux <<\EOF
+ loginctl list-sessions
+ loginctl session-status
+ loginctl show-session
+ loginctl show-session -P DelayInhibited
+
+ # We're not in the same session scope, so in this case we need to specify
+ # the session ID explicitly
+ session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; exit; }')
+ loginctl kill-session --signal=SIGCONT "$session"
+ # FIXME(?)
+ #loginctl kill-session --signal=SIGCONT --kill-who=leader "$session"
+
+ loginctl list-users
+ loginctl user-status
+ loginctl show-user -a
+ loginctl show-user -P IdleAction
+ loginctl kill-user --signal=SIGCONT ""
+
+ loginctl list-seats
+ loginctl seat-status
+ loginctl show-seat
+ loginctl show-seat -P IdleActionUSec
+EOF
+
+ # Requires root privileges
+ loginctl lock-sessions
+ loginctl unlock-sessions
+ loginctl flush-devices
+}
+
+test_session() {
+ local dev
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping ACL tests in container"
+ return
+ fi
+
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ trap teardown_session RETURN
+
+ create_session
+
+ # scsi_debug should not be loaded yet
+ if [[ -d /sys/bus/pseudo/drivers/scsi_debug ]]; then
+ echo "scsi_debug module is already loaded." >&2
+ exit 1
+ fi
+
+ # we use scsi_debug to create new devices which we can put ACLs on
+ # tell udev about the tagging, so that logind can pick it up
+ mkdir -p /run/udev/rules.d
+ cat >/run/udev/rules.d/70-logindtest-scsi_debug-user.rules <<EOF
+SUBSYSTEM=="block", ATTRS{model}=="scsi_debug*", TAG+="uaccess"
+EOF
+ udevadm control --reload
+
+ # coldplug: logind started with existing device
+ systemctl stop systemd-logind.service
+ modprobe scsi_debug
+ timeout 30 bash -c 'while ! ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null; do sleep 1; done'
+ dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null)
+ if [[ ! -b "$dev" ]]; then
+ echo "cannot find suitable scsi block device" >&2
+ exit 1
+ fi
+ udevadm settle
+ udevadm info "$dev"
+
+ # trigger logind and activate session
+ loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')"
+
+ # check ACL
+ sleep 1
+ assert_in "user:logind-test-user:rw-" "$(getfacl -p "$dev")"
+
+ # hotplug: new device appears while logind is running
+ rmmod scsi_debug
+ modprobe scsi_debug
+ timeout 30 bash -c 'while ! ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null; do sleep 1; done'
+ dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null)
+ if [[ ! -b "$dev" ]]; then
+ echo "cannot find suitable scsi block device" >&2
+ exit 1
+ fi
+ udevadm settle
+
+ # check ACL
+ sleep 1
+ assert_in "user:logind-test-user:rw-" "$(getfacl -p "$dev")"
+}
+
+teardown_lock_idle_action() (
+ set +eux
+
+ rm -f /run/systemd/logind.conf.d/idle-action-lock.conf
+ systemctl restart systemd-logind.service
+
+ cleanup_session
+
+ return 0
+)
+
+test_lock_idle_action() {
+ local ts
+
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ if loginctl --no-legend | grep -q logind-test-user; then
+ echo >&2 "Session of the \'logind-test-user\' is already present."
+ exit 1
+ fi
+
+ trap teardown_lock_idle_action RETURN
+
+ create_session
+
+ ts="$(date '+%H:%M:%S')"
+
+ mkdir -p /run/systemd/logind.conf.d
+ cat >/run/systemd/logind.conf.d/idle-action-lock.conf <<EOF
+[Login]
+IdleAction=lock
+IdleActionSec=1s
+EOF
+ systemctl restart systemd-logind.service
+
+ # Wait for 35s, in that interval all sessions should have become idle
+ # and "Lock" signal should have been sent out. Then we wrote to tty to make
+ # session active again and next we slept for another 35s so sessions have
+ # become idle again. 'Lock' signal is sent out for each session, we have at
+ # least one session, so minimum of 2 "Lock" signals must have been sent.
+ timeout 35 bash -c "while [[ \"\$(journalctl -b -u systemd-logind.service --since=$ts | grep -c 'Sent message type=signal .* member=Lock')\" -lt 1 ]]; do sleep 1; done"
+
+ # Wakeup
+ touch /dev/tty2
+
+ # Wait again
+ timeout 35 bash -c "while [[ \"\$(journalctl -b -u systemd-logind.service --since=$ts | grep -c 'Sent message type=signal .* member=Lock')\" -lt 2 ]]; do sleep 1; done"
+
+ if [[ "$(journalctl -b -u systemd-logind.service --since="$ts" | grep -c 'System idle. Will be locked now.')" -lt 2 ]]; then
+ echo >&2 "System haven't entered idle state at least 2 times."
+ exit 1
+ fi
+}
+
+test_session_properties() {
+ local s
+
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ trap cleanup_session RETURN
+ create_session
+
+ s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ /usr/lib/systemd/tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}"
+}
+
+test_list_users() {
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ trap cleanup_session RETURN
+ create_session
+
+ assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $1 }')" "$(id -ru logind-test-user)"
+ assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" no
+
+ loginctl enable-linger logind-test-user
+
+ assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes
+}
+
+
+teardown_stop_idle_session() (
+ set +eux
+
+ rm -f /run/systemd/logind.conf.d/stop-idle-session.conf
+ systemctl restart systemd-logind.service
+
+ cleanup_session
+)
+
+test_stop_idle_session() {
+ local id ts
+
+ if [[ ! -c /dev/tty2 ]]; then
+ echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+ return
+ fi
+
+ create_session
+ trap teardown_stop_idle_session RETURN
+
+ id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')"
+ ts="$(date '+%H:%M:%S')"
+
+ mkdir -p /run/systemd/logind.conf.d
+ cat >/run/systemd/logind.conf.d/stop-idle-session.conf <<EOF
+[Login]
+StopIdleSessionSec=2s
+EOF
+ systemctl restart systemd-logind.service
+ 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 -c "logind-test-user")" 0
+}
+
+: >/failed
+
+setup_test_user
+test_enable_debug
+test_properties
+test_started
+test_suspend_on_lid
+test_shutdown
+test_sanity_check
+test_session
+test_lock_idle_action
+test_session_properties
+test_list_users
+test_stop_idle_session
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-36.service b/test/units/testsuite-36.service
new file mode 100644
index 0000000..5746dc1
--- /dev/null
+++ b/test/units/testsuite-36.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-36-NUMAPOLICY
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-36.sh b/test/units/testsuite-36.sh
new file mode 100755
index 0000000..9705f2d
--- /dev/null
+++ b/test/units/testsuite-36.sh
@@ -0,0 +1,353 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+at_exit() {
+ # shellcheck disable=SC2181
+ if [[ $? -ne 0 ]]; then
+ # We're exiting with a non-zero EC, let's dump test artifacts
+ # for easier debugging
+ [[ -v straceLog && -f "$straceLog" ]] && cat "$straceLog"
+ [[ -v journalLog && -f "$journalLog" ]] && cat "$journalLog"
+ fi
+}
+
+trap at_exit EXIT
+
+systemd-analyze log-level debug
+systemd-analyze log-target journal
+
+# Log files
+straceLog='strace.log'
+journalLog='journal.log'
+
+# Systemd config files
+testUnit='numa-test.service'
+testUnitFile="/run/systemd/system/$testUnit"
+testUnitNUMAConf="$testUnitFile.d/numa.conf"
+
+# Sleep constants (we should probably figure out something better but nothing comes to mind)
+sleepAfterStart=1
+
+# Journal cursor for easier navigation
+journalCursorFile="jounalCursorFile"
+
+startStrace() {
+ coproc strace -qq -p 1 -o "$straceLog" -e set_mempolicy -s 1024 ${1:+"$1"}
+ # Wait for strace to properly "initialize", i.e. until PID 1 has the TracerPid
+ # field set to the current strace's PID
+ while ! awk -v spid="$COPROC_PID" '/^TracerPid:/ {exit !($2 == spid);}' /proc/1/status; do sleep 0.1; done
+}
+
+stopStrace() {
+ [[ -v COPROC_PID ]] || return
+
+ local PID=$COPROC_PID
+ kill -s TERM "$PID"
+ # Make sure the strace process is indeed dead
+ while kill -0 "$PID" 2>/dev/null; do sleep 0.1; done
+}
+
+startJournalctl() {
+ : >"$journalCursorFile"
+ # Save journal's cursor for later navigation
+ journalctl --no-pager --cursor-file="$journalCursorFile" -n0 -ocat
+}
+
+stopJournalctl() {
+ local unit="${1:-init.scope}"
+ # Using journalctl --sync should be better than using SIGRTMIN+1, as
+ # the --sync wait until the synchronization is complete
+ echo "Force journald to write all queued messages"
+ journalctl --sync
+ journalctl -u "$unit" --cursor-file="$journalCursorFile" >"$journalLog"
+}
+
+checkNUMA() {
+ # NUMA enabled system should have at least NUMA node0
+ test -e /sys/devices/system/node/node0
+}
+
+writePID1NUMAPolicy() {
+ cat >"$confDir/numa.conf" <<EOF
+[Manager]
+NUMAPolicy=${1:?}
+NUMAMask=${2:-""}
+EOF
+}
+
+writeTestUnit() {
+ mkdir -p "$testUnitFile.d/"
+ printf "[Service]\nExecStart=/bin/sleep 3600\n" >"$testUnitFile"
+}
+
+writeTestUnitNUMAPolicy() {
+ cat >"$testUnitNUMAConf" <<EOF
+[Service]
+NUMAPolicy=${1:?}
+NUMAMask=${2:-""}
+EOF
+ systemctl daemon-reload
+}
+
+pid1ReloadWithStrace() {
+ startStrace
+ systemctl daemon-reload
+ sleep $sleepAfterStart
+ stopStrace
+}
+
+pid1ReloadWithJournal() {
+ startJournalctl
+ systemctl daemon-reload
+ stopJournalctl
+}
+
+pid1StartUnitWithStrace() {
+ startStrace '-f'
+ systemctl start "${1:?}"
+ sleep $sleepAfterStart
+ stopStrace
+}
+
+pid1StartUnitWithJournal() {
+ startJournalctl
+ systemctl start "${1:?}"
+ sleep $sleepAfterStart
+ stopJournalctl
+}
+
+pid1StopUnit() {
+ systemctl stop "${1:?}"
+}
+
+systemctlCheckNUMAProperties() {
+ local UNIT_NAME="${1:?}"
+ local NUMA_POLICY="${2:?}"
+ local NUMA_MASK="${3:-""}"
+ local LOGFILE
+
+ LOGFILE="$(mktemp)"
+
+ systemctl show -p NUMAPolicy "$UNIT_NAME" >"$LOGFILE"
+ grep "NUMAPolicy=$NUMA_POLICY" "$LOGFILE"
+
+ : >"$LOGFILE"
+
+ if [ -n "$NUMA_MASK" ]; then
+ systemctl show -p NUMAMask "$UNIT_NAME" >"$LOGFILE"
+ grep "NUMAMask=$NUMA_MASK" "$LOGFILE"
+ fi
+}
+
+writeTestUnit
+
+# Create systemd config drop-in directory
+confDir="/run/systemd/system.conf.d/"
+mkdir -p "$confDir"
+
+if ! checkNUMA; then
+ echo >&2 "NUMA is not supported on this machine, switching to a simple sanity check"
+
+ echo "PID1 NUMAPolicy=default && NUMAMask=0 check without NUMA support"
+ writePID1NUMAPolicy "default" "0"
+ startJournalctl
+ systemctl daemon-reload
+ stopJournalctl
+ grep "NUMA support not available, ignoring" "$journalLog"
+
+ echo "systemd-run NUMAPolicy=default && NUMAMask=0 check without NUMA support"
+ runUnit='numa-systemd-run-test.service'
+ startJournalctl
+ systemd-run -p NUMAPolicy=default -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ sleep $sleepAfterStart
+ pid1StopUnit "$runUnit"
+ stopJournalctl "$runUnit"
+ grep "NUMA support not available, ignoring" "$journalLog"
+
+else
+ echo "PID1 NUMAPolicy support - Default policy w/o mask"
+ writePID1NUMAPolicy "default"
+ pid1ReloadWithStrace
+ # Kernel requires that nodemask argument is set to NULL when setting default policy
+ grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Default policy w/ mask"
+ writePID1NUMAPolicy "default" "0"
+ pid1ReloadWithStrace
+ grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Bind policy w/o mask"
+ writePID1NUMAPolicy "bind"
+ pid1ReloadWithJournal
+ grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog"
+
+ echo "PID1 NUMAPolicy support - Bind policy w/ mask"
+ writePID1NUMAPolicy "bind" "0"
+ pid1ReloadWithStrace
+ grep -P "set_mempolicy\(MPOL_BIND, \[0x0*1\]" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Interleave policy w/o mask"
+ writePID1NUMAPolicy "interleave"
+ pid1ReloadWithJournal
+ grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog"
+
+ echo "PID1 NUMAPolicy support - Interleave policy w/ mask"
+ writePID1NUMAPolicy "interleave" "0"
+ pid1ReloadWithStrace
+ grep -P "set_mempolicy\(MPOL_INTERLEAVE, \[0x0*1\]" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Preferred policy w/o mask"
+ writePID1NUMAPolicy "preferred"
+ pid1ReloadWithJournal
+ # Preferred policy with empty node mask is actually allowed and should reset allocation policy to default
+ grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" && { echo >&2 "unexpected pass"; exit 1; }
+
+ echo "PID1 NUMAPolicy support - Preferred policy w/ mask"
+ writePID1NUMAPolicy "preferred" "0"
+ pid1ReloadWithStrace
+ grep -P "set_mempolicy\(MPOL_PREFERRED, \[0x0*1\]" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Local policy w/o mask"
+ writePID1NUMAPolicy "local"
+ pid1ReloadWithStrace
+ # Kernel requires that nodemask argument is set to NULL when setting default policy
+ # The unpatched versions of strace don't recognize the MPOL_LOCAL constant and
+ # return a numerical constant instead (with a comment):
+ # set_mempolicy(0x4 /* MPOL_??? */, NULL, 0) = 0
+ # Let's cover this scenario as well
+ grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog"
+
+ echo "PID1 NUMAPolicy support - Local policy w/ mask"
+ writePID1NUMAPolicy "local" "0"
+ pid1ReloadWithStrace
+ grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Default policy w/o mask"
+ writeTestUnitNUMAPolicy "default"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "default"
+ pid1StopUnit "$testUnit"
+ grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Default policy w/ mask"
+ writeTestUnitNUMAPolicy "default" "0"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "default" "0"
+ pid1StopUnit $testUnit
+ # Mask must be ignored
+ grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Bind policy w/o mask"
+ writeTestUnitNUMAPolicy "bind"
+ pid1StartUnitWithJournal "$testUnit"
+ pid1StopUnit "$testUnit"
+ [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]]
+
+ echo "Unit file NUMAPolicy support - Bind policy w/ mask"
+ writeTestUnitNUMAPolicy "bind" "0"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "bind" "0"
+ pid1StopUnit "$testUnit"
+ grep -P "set_mempolicy\(MPOL_BIND, \[0x0*1\]" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Interleave policy w/o mask"
+ writeTestUnitNUMAPolicy "interleave"
+ pid1StartUnitWithStrace "$testUnit"
+ pid1StopUnit "$testUnit"
+ [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]]
+
+ echo "Unit file NUMAPolicy support - Interleave policy w/ mask"
+ writeTestUnitNUMAPolicy "interleave" "0"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "interleave" "0"
+ pid1StopUnit "$testUnit"
+ grep -P "set_mempolicy\(MPOL_INTERLEAVE, \[0x0*1\]" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Preferred policy w/o mask"
+ writeTestUnitNUMAPolicy "preferred"
+ pid1StartUnitWithJournal "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "preferred"
+ pid1StopUnit "$testUnit"
+ [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] && { echo >&2 "unexpected pass"; exit 1; }
+
+ echo "Unit file NUMAPolicy support - Preferred policy w/ mask"
+ writeTestUnitNUMAPolicy "preferred" "0"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "preferred" "0"
+ pid1StopUnit "$testUnit"
+ grep -P "set_mempolicy\(MPOL_PREFERRED, \[0x0*1\]" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Local policy w/o mask"
+ writeTestUnitNUMAPolicy "local"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "local"
+ pid1StopUnit "$testUnit"
+ grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog"
+
+ echo "Unit file NUMAPolicy support - Local policy w/ mask"
+ writeTestUnitNUMAPolicy "local" "0"
+ pid1StartUnitWithStrace "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "local" "0"
+ pid1StopUnit "$testUnit"
+ # Mask must be ignored
+ grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog"
+
+ echo "Unit file CPUAffinity=NUMA support"
+ writeTestUnitNUMAPolicy "bind" "0"
+ echo "CPUAffinity=numa" >>"$testUnitNUMAConf"
+ systemctl daemon-reload
+ systemctl start "$testUnit"
+ systemctlCheckNUMAProperties "$testUnit" "bind" "0"
+ cpulist="$(cat /sys/devices/system/node/node0/cpulist)"
+ affinity_systemd="$(systemctl show --value -p CPUAffinity "$testUnit")"
+ [ "$cpulist" = "$affinity_systemd" ]
+ pid1StopUnit "$testUnit"
+
+ echo "systemd-run NUMAPolicy support"
+ runUnit='numa-systemd-run-test.service'
+
+ systemd-run -p NUMAPolicy=default --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "default"
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=default -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "default" ""
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=bind -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "bind" "0"
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=interleave -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "interleave" "0"
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=preferred -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "preferred" "0"
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=local --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "local"
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=local -p NUMAMask=0 --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "local" ""
+ pid1StopUnit "$runUnit"
+
+ systemd-run -p NUMAPolicy=local -p NUMAMask=0 -p CPUAffinity=numa --unit "$runUnit" sleep 1000
+ systemctlCheckNUMAProperties "$runUnit" "local" ""
+ systemctl cat "$runUnit" | grep -q 'CPUAffinity=numa'
+ pid1StopUnit "$runUnit"
+fi
+
+# Cleanup
+rm -rf "$confDir"
+systemctl daemon-reload
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-37.service b/test/units/testsuite-37.service
new file mode 100644
index 0000000..ccad5e2
--- /dev/null
+++ b/test/units/testsuite-37.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-37-RUNTIMEDIRECTORYPRESERVE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-37.sh b/test/units/testsuite-37.sh
new file mode 100755
index 0000000..1aec383
--- /dev/null
+++ b/test/units/testsuite-37.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+set -o pipefail
+
+systemd-mount -p RuntimeDirectory=hoge -p RuntimeDirectoryPreserve=yes -t tmpfs tmpfs /tmp/aaa
+
+touch /run/hoge/foo
+touch /tmp/aaa/bbb
+
+systemctl restart tmp-aaa.mount
+
+test -e /run/hoge/foo
+test ! -e /tmp/aaa/bbb
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-38-sleep.service b/test/units/testsuite-38-sleep.service
new file mode 100644
index 0000000..c116c80
--- /dev/null
+++ b/test/units/testsuite-38-sleep.service
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+ExecStart=/bin/sleep 3600
diff --git a/test/units/testsuite-38.service b/test/units/testsuite-38.service
new file mode 100644
index 0000000..ac77836
--- /dev/null
+++ b/test/units/testsuite-38.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-38-FREEZER
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-38.sh b/test/units/testsuite-38.sh
new file mode 100755
index 0000000..c5f9bcc
--- /dev/null
+++ b/test/units/testsuite-38.sh
@@ -0,0 +1,304 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+unit=testsuite-38-sleep.service
+
+start_test_service() {
+ systemctl daemon-reload
+ systemctl start "${unit}"
+}
+
+dbus_freeze() {
+ local name object_path suffix
+
+ suffix="${1##*.}"
+ name="${1%".$suffix"}"
+ object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
+
+ busctl call \
+ org.freedesktop.systemd1 \
+ "${object_path}" \
+ org.freedesktop.systemd1.Unit \
+ Freeze
+}
+
+dbus_thaw() {
+ local name object_path suffix
+
+ suffix="${1##*.}"
+ name="${1%".$suffix"}"
+ object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
+
+ busctl call \
+ org.freedesktop.systemd1 \
+ "${object_path}" \
+ org.freedesktop.systemd1.Unit \
+ Thaw
+}
+
+dbus_freeze_unit() {
+ busctl call \
+ org.freedesktop.systemd1 \
+ /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager \
+ FreezeUnit \
+ s \
+ "$1"
+}
+
+dbus_thaw_unit() {
+ busctl call \
+ org.freedesktop.systemd1 \
+ /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager \
+ ThawUnit \
+ s \
+ "$1"
+}
+
+dbus_can_freeze() {
+ local name object_path suffix
+
+ suffix="${1##*.}"
+ name="${1%".$suffix"}"
+ object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
+
+ busctl get-property \
+ org.freedesktop.systemd1 \
+ "${object_path}" \
+ org.freedesktop.systemd1.Unit \
+ CanFreeze
+}
+
+check_freezer_state() {
+ local name object_path suffix
+
+ suffix="${1##*.}"
+ name="${1%".$suffix"}"
+ object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
+
+ for _ in {0..10}; do
+ state=$(busctl get-property \
+ org.freedesktop.systemd1 \
+ "${object_path}" \
+ org.freedesktop.systemd1.Unit \
+ FreezerState | cut -d " " -f2 | tr -d '"')
+
+ # Ignore the intermediate freezing & thawing states in case we check
+ # the unit state too quickly
+ [[ "$state" =~ ^(freezing|thawing)$ ]] || break
+ sleep .5
+ done
+
+ [ "$state" = "$2" ] || {
+ echo "error: unexpected freezer state, expected: $2, actual: $state" >&2
+ exit 1
+ }
+}
+
+check_cgroup_state() {
+ grep -q "frozen $2" /sys/fs/cgroup/system.slice/"$1"/cgroup.events
+}
+
+test_dbus_api() {
+ echo "Test that DBus API works:"
+ echo -n " - Freeze(): "
+ dbus_freeze "${unit}"
+ check_freezer_state "${unit}" "frozen"
+ check_cgroup_state "$unit" 1
+ echo "[ OK ]"
+
+ echo -n " - Thaw(): "
+ dbus_thaw "${unit}"
+ check_freezer_state "${unit}" "running"
+ check_cgroup_state "$unit" 0
+ echo "[ OK ]"
+
+ echo -n " - FreezeUnit(): "
+ dbus_freeze_unit "${unit}"
+ check_freezer_state "${unit}" "frozen"
+ check_cgroup_state "$unit" 1
+ echo "[ OK ]"
+
+ echo -n " - ThawUnit(): "
+ dbus_thaw_unit "${unit}"
+ check_freezer_state "${unit}" "running"
+ check_cgroup_state "$unit" 0
+ echo "[ OK ]"
+
+ echo -n " - CanFreeze(): "
+ output=$(dbus_can_freeze "${unit}")
+ [ "$output" = "b true" ]
+ echo "[ OK ]"
+
+ echo
+}
+
+test_jobs() {
+ local pid_before=
+ local pid_after=
+ echo "Test that it is possible to apply jobs on frozen units:"
+
+ systemctl start "${unit}"
+ dbus_freeze "${unit}"
+ check_freezer_state "${unit}" "frozen"
+
+ echo -n " - restart: "
+ pid_before=$(systemctl show -p MainPID "${unit}" --value)
+ systemctl restart "${unit}"
+ pid_after=$(systemctl show -p MainPID "${unit}" --value)
+ [ "$pid_before" != "$pid_after" ] && echo "[ OK ]"
+
+ dbus_freeze "${unit}"
+ check_freezer_state "${unit}" "frozen"
+
+ echo -n " - stop: "
+ timeout 5s systemctl stop "${unit}"
+ echo "[ OK ]"
+
+ echo
+}
+
+test_systemctl() {
+ echo "Test that systemctl freeze/thaw verbs:"
+
+ systemctl start "$unit"
+
+ echo -n " - freeze: "
+ systemctl freeze "$unit"
+ check_freezer_state "${unit}" "frozen"
+ check_cgroup_state "$unit" 1
+ # Freezing already frozen unit should be NOP and return quickly
+ timeout 3s systemctl freeze "$unit"
+ echo "[ OK ]"
+
+ echo -n " - thaw: "
+ systemctl thaw "$unit"
+ check_freezer_state "${unit}" "running"
+ check_cgroup_state "$unit" 0
+ # Likewise thawing already running unit shouldn't block
+ timeout 3s systemctl thaw "$unit"
+ echo "[ OK ]"
+
+ systemctl stop "$unit"
+
+ echo
+}
+
+test_systemctl_show() {
+ echo "Test systemctl show integration:"
+
+ systemctl start "$unit"
+
+ echo -n " - FreezerState property: "
+ state=$(systemctl show -p FreezerState --value "$unit")
+ [ "$state" = "running" ]
+ systemctl freeze "$unit"
+ state=$(systemctl show -p FreezerState --value "$unit")
+ [ "$state" = "frozen" ]
+ systemctl thaw "$unit"
+ echo "[ OK ]"
+
+ echo -n " - CanFreeze property: "
+ state=$(systemctl show -p CanFreeze --value "$unit")
+ [ "$state" = "yes" ]
+ echo "[ OK ]"
+
+ systemctl stop "$unit"
+ echo
+}
+
+test_recursive() {
+ local slice="bar.slice"
+ local unit="baz.service"
+
+ systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1
+
+ echo "Test recursive freezing:"
+
+ echo -n " - freeze: "
+ systemctl freeze "$slice"
+ check_freezer_state "${slice}" "frozen"
+ check_freezer_state "${unit}" "frozen"
+ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events
+ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
+ echo "[ OK ]"
+
+ echo -n " - thaw: "
+ systemctl thaw "$slice"
+ check_freezer_state "${unit}" "running"
+ check_freezer_state "${slice}" "running"
+ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events
+ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
+ echo "[ OK ]"
+
+ systemctl stop "$unit"
+ systemctl stop "$slice"
+
+ echo
+}
+
+test_preserve_state() {
+ local slice="bar.slice"
+ local unit="baz.service"
+
+ systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1
+
+ echo "Test that freezer state is preserved when recursive freezing is initiated from outside (e.g. by manager up the tree):"
+
+ echo -n " - freeze from outside: "
+ echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
+ # Give kernel some time to freeze the slice
+ sleep 1
+
+ # Our state should not be affected
+ check_freezer_state "${slice}" "running"
+ check_freezer_state "${unit}" "running"
+
+ # However actual kernel state should be frozen
+ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events
+ grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
+ echo "[ OK ]"
+
+ echo -n " - thaw from outside: "
+ echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
+ sleep 1
+
+ check_freezer_state "${unit}" "running"
+ check_freezer_state "${slice}" "running"
+ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events
+ grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
+ echo "[ OK ]"
+
+ echo -n " - thaw from outside while inner service is frozen: "
+ systemctl freeze "$unit"
+ check_freezer_state "${unit}" "frozen"
+ echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
+ echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
+ check_freezer_state "${slice}" "running"
+ check_freezer_state "${unit}" "frozen"
+ echo "[ OK ]"
+
+ systemctl stop "$unit"
+ systemctl stop "$slice"
+
+ echo
+}
+
+test -e /sys/fs/cgroup/system.slice/cgroup.freeze && {
+ start_test_service
+ test_dbus_api
+ test_systemctl
+ test_systemctl_show
+ test_jobs
+ test_recursive
+ test_preserve_state
+}
+
+echo OK >/testok
+exit 0
diff --git a/test/units/testsuite-39.service b/test/units/testsuite-39.service
new file mode 100644
index 0000000..1567dfa
--- /dev/null
+++ b/test/units/testsuite-39.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-39-EXECRELOAD
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-39.sh b/test/units/testsuite-39.sh
new file mode 100755
index 0000000..dbeb1df
--- /dev/null
+++ b/test/units/testsuite-39.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+export SYSTEMD_PAGER=
+SERVICE_PATH="$(mktemp /etc/systemd/system/execreloadXXX.service)"
+SERVICE_NAME="${SERVICE_PATH##*/}"
+
+echo "[#1] Failing ExecReload= should not kill the service"
+cat >"$SERVICE_PATH" <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+ExecReload=/bin/false
+EOF
+
+systemctl daemon-reload
+systemctl start "$SERVICE_NAME"
+systemctl status "$SERVICE_NAME"
+# The reload SHOULD fail but SHOULD NOT affect the service state
+(! systemctl reload "$SERVICE_NAME")
+systemctl status "$SERVICE_NAME"
+systemctl stop "$SERVICE_NAME"
+
+
+echo "[#2] Failing ExecReload= should not kill the service (multiple ExecReload=)"
+cat >"$SERVICE_PATH" <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+ExecReload=/bin/true
+ExecReload=/bin/false
+ExecReload=/bin/true
+EOF
+
+systemctl daemon-reload
+systemctl start "$SERVICE_NAME"
+systemctl status "$SERVICE_NAME"
+# The reload SHOULD fail but SHOULD NOT affect the service state
+(! systemctl reload "$SERVICE_NAME")
+systemctl status "$SERVICE_NAME"
+systemctl stop "$SERVICE_NAME"
+
+echo "[#3] Failing ExecReload=- should not affect reload's exit code"
+cat >"$SERVICE_PATH" <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+ExecReload=-/bin/false
+EOF
+
+systemctl daemon-reload
+systemctl start "$SERVICE_NAME"
+systemctl status "$SERVICE_NAME"
+systemctl reload "$SERVICE_NAME"
+systemctl status "$SERVICE_NAME"
+systemctl stop "$SERVICE_NAME"
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-40.service b/test/units/testsuite-40.service
new file mode 100644
index 0000000..eec4ddc
--- /dev/null
+++ b/test/units/testsuite-40.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-40-EXEC-COMMAND-EX
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-40.sh b/test/units/testsuite-40.sh
new file mode 100755
index 0000000..cec1fd3
--- /dev/null
+++ b/test/units/testsuite-40.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+declare -A property
+
+property[1_one]=ExecCondition
+property[2_two]=ExecStartPre
+property[3_three]=ExecStart
+property[4_four]=ExecStartPost
+property[5_five]=ExecReload
+property[6_six]=ExecStop
+property[7_seven]=ExecStopPost
+
+# These should all get upgraded to the corresponding Ex property as the non-Ex variant
+# does not support the ":" prefix (no-env-expand).
+for c in "${!property[@]}"; do
+ systemd-run --unit="$c" -r -p "Type=oneshot" -p "${property[$c]}=:/bin/echo \${$c}" /bin/true
+ systemctl show -p "${property[$c]}" "$c" | grep -F "path=/bin/echo ; argv[]=/bin/echo \${$c} ; ignore_errors=no"
+ systemctl show -p "${property[$c]}Ex" "$c" | grep -F "path=/bin/echo ; argv[]=/bin/echo \${$c} ; flags=no-env-expand"
+done
+
+declare -A property_ex
+
+property_ex[1_one_ex]=ExecConditionEx
+property_ex[2_two_ex]=ExecStartPreEx
+property_ex[3_three_ex]=ExecStartEx
+property_ex[4_four_ex]=ExecStartPostEx
+property_ex[5_five_ex]=ExecReloadEx
+property_ex[6_six_ex]=ExecStopEx
+property_ex[7_seven_ex]=ExecStopPostEx
+
+for c in "${!property_ex[@]}"; do
+ systemd-run --unit="$c" -r -p "Type=oneshot" -p "${property_ex[$c]}=:/bin/echo \${$c}" /bin/true
+ systemctl show -p "${property_ex[$c]%??}" "$c" | grep -F "path=/bin/echo ; argv[]=/bin/echo \${$c} ; ignore_errors=no"
+ systemctl show -p "${property_ex[$c]}" "$c" | grep -F "path=/bin/echo ; argv[]=/bin/echo \${$c} ; flags=no-env-expand"
+done
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-41.service b/test/units/testsuite-41.service
new file mode 100644
index 0000000..bbd8a72
--- /dev/null
+++ b/test/units/testsuite-41.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-41-ONESHOT-RESTART
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-41.sh b/test/units/testsuite-41.sh
new file mode 100755
index 0000000..96b38c5
--- /dev/null
+++ b/test/units/testsuite-41.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# wait this many secs for each test service to succeed in what is being tested
+MAX_SECS=60
+
+systemd-analyze log-level debug
+
+# test one: Restart=on-failure should restart the service
+(! systemd-run --unit=one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1")
+
+for ((secs = 0; secs < MAX_SECS; secs++)); do
+ [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]] || break
+ sleep 1
+done
+if [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]]; then
+ exit 1
+fi
+
+TMP_FILE="/tmp/test-41-oneshot-restart-test"
+
+: >$TMP_FILE
+
+# test two: make sure StartLimitBurst correctly limits the number of restarts
+# and restarts execution of the unit from the first ExecStart=
+(! systemd-run --unit=two \
+ -p StartLimitIntervalSec=120 \
+ -p StartLimitBurst=3 \
+ -p Type=oneshot \
+ -p Restart=on-failure \
+ -p ExecStart="/bin/bash -c \"printf a >> $TMP_FILE\"" /bin/bash -c "exit 1")
+
+# wait for at least 3 restarts
+for ((secs = 0; secs < MAX_SECS; secs++)); do
+ [[ $(cat $TMP_FILE) != "aaa" ]] || break
+ sleep 1
+done
+if [[ $(cat $TMP_FILE) != "aaa" ]]; then
+ exit 1
+fi
+
+# wait for 5 more seconds to make sure there aren't excess restarts
+sleep 5
+if [[ $(cat $TMP_FILE) != "aaa" ]]; then
+ exit 1
+fi
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-42.service b/test/units/testsuite-42.service
new file mode 100644
index 0000000..f57e616
--- /dev/null
+++ b/test/units/testsuite-42.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-42-EXECSTOPPOST
+Before=getty-pre.target
+Wants=getty-pre.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-42.sh b/test/units/testsuite-42.sh
new file mode 100755
index 0000000..b78d5b7
--- /dev/null
+++ b/test/units/testsuite-42.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+
+systemd-analyze log-level debug
+
+systemd-run --unit=simple1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+ -p ExecStopPost='/bin/touch /run/simple1' true
+test -f /run/simple1
+
+(! systemd-run --unit=simple2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+ -p ExecStopPost='/bin/touch /run/simple2' false)
+test -f /run/simple2
+
+systemd-run --unit=exec1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+ -p ExecStopPost='/bin/touch /run/exec1' sleep 1
+test -f /run/exec1
+
+(! systemd-run --unit=exec2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+ -p ExecStopPost='/bin/touch /run/exec2' sh -c 'sleep 1; false')
+test -f /run/exec2
+
+cat >/tmp/forking1.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+
+sleep 4 &
+MAINPID=\$!
+disown
+
+systemd-notify MAINPID=\$MAINPID
+EOF
+chmod +x /tmp/forking1.sh
+
+systemd-run --unit=forking1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+ -p ExecStopPost='/bin/touch /run/forking1' /tmp/forking1.sh
+test -f /run/forking1
+
+cat >/tmp/forking2.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+
+(sleep 4; exit 1) &
+MAINPID=\$!
+disown
+
+systemd-notify MAINPID=\$MAINPID
+EOF
+chmod +x /tmp/forking2.sh
+
+(! systemd-run --unit=forking2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+ -p ExecStopPost='/bin/touch /run/forking2' /tmp/forking2.sh)
+test -f /run/forking2
+
+systemd-run --unit=oneshot1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+ -p ExecStopPost='/bin/touch /run/oneshot1' true
+test -f /run/oneshot1
+
+(! systemd-run --unit=oneshot2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+ -p ExecStopPost='/bin/touch /run/oneshot2' false)
+test -f /run/oneshot2
+
+systemd-run --unit=dbus1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+ -p ExecStopPost='/bin/touch /run/dbus1' \
+ busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus RequestName su systemd.test.ExecStopPost 4 || :
+test -f /run/dbus1
+
+systemd-run --unit=dbus2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+ -p ExecStopPost='/bin/touch /run/dbus2' true
+test -f /run/dbus2
+
+# https://github.com/systemd/systemd/issues/19920
+(! systemd-run --unit=dbus3.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus \
+ -p ExecStopPost='/bin/touch /run/dbus3' true)
+
+cat >/tmp/notify1.sh <<EOF
+#!/usr/bin/env bash
+
+set -eux
+
+systemd-notify --ready
+EOF
+chmod +x /tmp/notify1.sh
+
+systemd-run --unit=notify1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+ -p ExecStopPost='/bin/touch /run/notify1' /tmp/notify1.sh
+test -f /run/notify1
+
+(! systemd-run --unit=notify2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+ -p ExecStopPost='/bin/touch /run/notify2' true)
+test -f /run/notify2
+
+systemd-run --unit=idle1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle -p ExecStopPost='/bin/touch /run/idle1' true
+test -f /run/idle1
+
+(! systemd-run --unit=idle2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle \
+ -p ExecStopPost='/bin/touch /run/idle2' false)
+test -f /run/idle2
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-43.service b/test/units/testsuite-43.service
new file mode 100644
index 0000000..e36afea
--- /dev/null
+++ b/test/units/testsuite-43.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-43-PRIVATEUSER-UNPRIV
+After=systemd-logind.service user@4711.service
+Wants=user@4711.service
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-43.sh b/test/units/testsuite-43.sh
new file mode 100755
index 0000000..014c174
--- /dev/null
+++ b/test/units/testsuite-43.sh
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+runas() {
+ declare userid=$1
+ shift
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
+}
+
+runas testuser systemd-run --wait --user --unit=test-private-users \
+ -p PrivateUsers=yes -P echo hello
+
+runas testuser systemctl --user log-level debug
+
+runas testuser systemd-run --wait --user --unit=test-private-tmp-innerfile \
+ -p PrivateUsers=yes -p PrivateTmp=yes \
+ -P touch /tmp/innerfile.txt
+# File should not exist outside the job's tmp directory.
+test ! -e /tmp/innerfile.txt
+
+touch /tmp/outerfile.txt
+# File should not appear in unit's private tmp.
+runas testuser systemd-run --wait --user --unit=test-private-tmp-outerfile \
+ -p PrivateUsers=yes -p PrivateTmp=yes \
+ -P test ! -e /tmp/outerfile.txt
+
+# Confirm that creating a file in home works
+runas testuser systemd-run --wait --user --unit=test-unprotected-home \
+ -P touch /home/testuser/works.txt
+test -e /home/testuser/works.txt
+
+# Confirm that creating a file in home is blocked under read-only
+(! runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \
+ -p PrivateUsers=yes -p ProtectHome=read-only \
+ -P bash -c '
+ test -e /home/testuser/works.txt || exit 10
+ touch /home/testuser/blocked.txt && exit 11
+ ')
+test ! -e /home/testuser/blocked.txt
+
+# Check that tmpfs hides the whole directory
+runas testuser systemd-run --wait --user --unit=test-protect-home-tmpfs \
+ -p PrivateUsers=yes -p ProtectHome=tmpfs \
+ -P test ! -e /home/testuser
+
+# Confirm that home, /root, and /run/user are inaccessible under "yes"
+# shellcheck disable=SC2016
+runas testuser systemd-run --wait --user --unit=test-protect-home-yes \
+ -p PrivateUsers=yes -p ProtectHome=yes \
+ -P bash -c '
+ test "$(stat -c %a /home)" = "0"
+ test "$(stat -c %a /root)" = "0"
+ test "$(stat -c %a /run/user)" = "0"
+ '
+
+# Confirm we cannot change groups because we only have one mapping in the user
+# namespace (no CAP_SETGID in the parent namespace to write the additional
+# mapping of the user supplied group and thus cannot change groups to an
+# unmapped group ID)
+(! runas testuser systemd-run --wait --user --unit=test-group-fail \
+ -p PrivateUsers=yes -p Group=daemon \
+ -P true)
+
+# Check that with a new user namespace we can bind mount
+# files and use a different root directory
+runas testuser systemd-run --wait --user --unit=test-bind-mount \
+ -p PrivateUsers=yes -p BindPaths=/dev/null:/etc/os-release \
+ test ! -s /etc/os-release
+
+runas testuser systemd-run --wait --user --unit=test-read-write \
+ -p PrivateUsers=yes -p ReadOnlyPaths=/ \
+ -p ReadWritePaths="/var /run /tmp" \
+ -p NoExecPaths=/ -p ExecPaths=/usr \
+ test ! -w /etc/os-release
+
+runas testuser systemd-run --wait --user --unit=test-caps \
+ -p PrivateUsers=yes -p AmbientCapabilities=CAP_SYS_ADMIN \
+ -p CapabilityBoundingSet=CAP_SYS_ADMIN \
+ test -s /etc/os-release
+
+runas testuser systemd-run --wait --user --unit=test-devices \
+ -p PrivateUsers=yes -p PrivateDevices=yes -p PrivateIPC=yes \
+ sh -c "ls -1 /dev/ | wc -l | grep -q -F 18"
+
+# Same check as test/test-execute/exec-privatenetwork-yes.service
+runas testuser systemd-run --wait --user --unit=test-network \
+ -p PrivateUsers=yes -p PrivateNetwork=yes \
+ /bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -Ev ": (lo|(erspan|gre|gretap|ip_vti|ip6_vti|ip6gre|ip6tnl|sit|tunl)0@.*):"'
+
+(! runas testuser systemd-run --wait --user --unit=test-hostname \
+ -p PrivateUsers=yes -p ProtectHostname=yes \
+ hostnamectl hostname foo)
+
+(! runas testuser systemd-run --wait --user --unit=test-clock \
+ -p PrivateUsers=yes -p ProtectClock=yes \
+ timedatectl set-time "2012-10-30 18:17:16")
+
+(! runas testuser systemd-run --wait --user --unit=test-kernel-tunable \
+ -p PrivateUsers=yes -p ProtectKernelTunables=yes \
+ sh -c "echo 0 > /proc/sys/user/max_user_namespaces")
+
+(! runas testuser systemd-run --wait --user --unit=test-kernel-mod \
+ -p PrivateUsers=yes -p ProtectKernelModules=yes \
+ sh -c "modprobe -r overlay && modprobe overlay")
+
+if sysctl kernel.dmesg_restrict=0; then
+ (! runas testuser systemd-run --wait --user --unit=test-kernel-log \
+ -p PrivateUsers=yes -p ProtectKernelLogs=yes -p LogNamespace=yes \
+ dmesg)
+fi
+
+unsquashfs -no-xattrs -d /tmp/img /usr/share/minimal_0.raw
+runas testuser systemd-run --wait --user --unit=test-root-dir \
+ -p PrivateUsers=yes -p RootDirectory=/tmp/img \
+ grep MARKER=1 /etc/os-release
+
+mkdir /tmp/img_bind
+mount --bind /tmp/img /tmp/img_bind
+runas testuser systemd-run --wait --user --unit=test-root-dir-bind \
+ -p PrivateUsers=yes -p RootDirectory=/tmp/img_bind -p MountFlags=private \
+ grep MARKER=1 /etc/os-release
+umount /tmp/img_bind
+
+# Unprivileged overlayfs was added to Linux 5.11, so try to detect it first
+mkdir -p /tmp/a /tmp/b /tmp/c
+if unshare --mount --user --map-root-user mount -t overlay overlay /tmp/c -o lowerdir=/tmp/a:/tmp/b; then
+ unsquashfs -no-xattrs -d /tmp/app2 /usr/share/app1.raw
+ runas testuser systemd-run --wait --user --unit=test-extension-dir \
+ -p PrivateUsers=yes -p ExtensionDirectories=/tmp/app2 \
+ -p TemporaryFileSystem=/run -p RootDirectory=/tmp/img \
+ -p MountAPIVFS=yes \
+ grep PORTABLE_PREFIXES=app1 /usr/lib/extension-release.d/extension-release.app2
+fi
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-44.service b/test/units/testsuite-44.service
new file mode 100644
index 0000000..4dffdea
--- /dev/null
+++ b/test/units/testsuite-44.service
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TESTSUITE-44-LOG-NAMESPACE
+Before=getty-pre.target
+Wants=getty-pre.target
+Wants=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket
+After=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-44.sh b/test/units/testsuite-44.sh
new file mode 100755
index 0000000..da779a6
--- /dev/null
+++ b/test/units/testsuite-44.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+
+systemd-analyze log-level debug
+
+systemd-run --wait -p LogNamespace=foobar echo "hello world"
+
+journalctl --namespace=foobar --sync
+journalctl -o cat --namespace=foobar >/tmp/hello-world
+journalctl -o cat >/tmp/no-hello-world
+
+grep "^hello world$" /tmp/hello-world
+(! grep "^hello world$" /tmp/no-hello-world)
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-45.service b/test/units/testsuite-45.service
new file mode 100644
index 0000000..b16ce99
--- /dev/null
+++ b/test/units/testsuite-45.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-45-TIMEDATE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-45.sh b/test/units/testsuite-45.sh
new file mode 100755
index 0000000..ba01daa
--- /dev/null
+++ b/test/units/testsuite-45.sh
@@ -0,0 +1,293 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+test_timedatectl() {
+ timedatectl --no-pager --help
+ timedatectl --version
+
+ timedatectl
+ timedatectl --no-ask-password
+ timedatectl status --machine=testuser@.host
+ timedatectl status
+ timedatectl show
+ timedatectl show --all
+ timedatectl show -p NTP
+ timedatectl show -p NTP --value
+ timedatectl list-timezones
+
+ if ! systemd-detect-virt -qc; then
+ systemctl enable --runtime --now systemd-timesyncd
+ timedatectl timesync-status
+ timedatectl show-timesync
+ fi
+}
+
+restore_timezone() {
+ if [[ -f /tmp/timezone.bak ]]; then
+ mv /tmp/timezone.bak /etc/timezone
+ else
+ rm -f /etc/timezone
+ fi
+}
+
+test_timezone() {
+ local ORIG_TZ=
+
+ # Debian/Ubuntu specific file
+ if [[ -f /etc/timezone ]]; then
+ mv /etc/timezone /tmp/timezone.bak
+ fi
+
+ trap restore_timezone RETURN
+
+ if [[ -L /etc/localtime ]]; then
+ ORIG_TZ=$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')
+ echo "original tz: $ORIG_TZ"
+ fi
+
+ echo 'timedatectl works'
+ assert_in "Local time:" "$(timedatectl --no-pager)"
+
+ echo 'change timezone'
+ assert_eq "$(timedatectl --no-pager set-timezone Europe/Kiev 2>&1)" ""
+ assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "Europe/Kiev"
+ if [[ -f /etc/timezone ]]; then
+ assert_eq "$(cat /etc/timezone)" "Europe/Kiev"
+ fi
+ assert_in "Time zone: Europe/Kiev \(EES*T, \+0[0-9]00\)" "$(timedatectl)"
+
+ if [[ -n "$ORIG_TZ" ]]; then
+ echo 'reset timezone to original'
+ assert_eq "$(timedatectl set-timezone "$ORIG_TZ" 2>&1)" ""
+ assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "$ORIG_TZ"
+ if [[ -f /etc/timezone ]]; then
+ assert_eq "$(cat /etc/timezone)" "$ORIG_TZ"
+ fi
+ fi
+}
+
+restore_adjtime() {
+ if [[ -e /etc/adjtime.bak ]]; then
+ mv /etc/adjtime.bak /etc/adjtime
+ else
+ rm /etc/adjtime
+ fi
+}
+
+check_adjtime_not_exist() {
+ if [[ -e /etc/adjtime ]]; then
+ echo "/etc/adjtime unexpectedly exists." >&2
+ exit 1
+ fi
+}
+
+test_adjtime() {
+ # test setting UTC vs. LOCAL in /etc/adjtime
+ if [[ -e /etc/adjtime ]]; then
+ mv /etc/adjtime /etc/adjtime.bak
+ fi
+
+ trap restore_adjtime RETURN
+
+ echo 'no adjtime file'
+ rm -f /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+
+ echo 'UTC set in adjtime file'
+ printf '0.0 0 0\n0\nUTC\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+UTC"
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'non-zero values in adjtime file'
+ printf '0.1 123 0\n0\nLOCAL\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ assert_eq "$(cat /etc/adjtime)" "0.1 123 0
+0
+UTC"
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.1 123 0
+0
+LOCAL"
+
+ echo 'fourth line adjtime file'
+ printf '0.0 0 0\n0\nLOCAL\nsomethingelse\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+UTC
+somethingelse"
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL
+somethingelse"
+
+ echo 'no final newline in adjtime file'
+ printf '0.0 0 0\n0\nUTC' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0\n0\nUTC' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'only one line in adjtime file'
+ printf '0.0 0 0\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0\n' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'only one line in adjtime file, no final newline'
+ printf '0.0 0 0' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'only two lines in adjtime file'
+ printf '0.0 0 0\n0\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0\n0\n' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'only two lines in adjtime file, no final newline'
+ printf '0.0 0 0\n0' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0\n0' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+
+ echo 'unknown value in 3rd line of adjtime file'
+ printf '0.0 0 0\n0\nFOO\n' > /etc/adjtime
+ timedatectl set-local-rtc 0
+ check_adjtime_not_exist
+ printf '0.0 0 0\n0\nFOO\n' > /etc/adjtime
+ timedatectl set-local-rtc 1
+ assert_eq "$(cat /etc/adjtime)" "0.0 0 0
+0
+LOCAL"
+}
+
+assert_ntp() {
+ local value="${1:?}"
+
+ for _ in {0..9}; do
+ [[ "$(busctl get-property org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1 NTP)" == "b $value" ]] && return 0
+ sleep .5
+ done
+
+ return 1
+}
+
+start_mon() {
+ busctl monitor --match="type='signal',sender=org.freedesktop.timedate1,member='PropertiesChanged',path=/org/freedesktop/timedate1" >"$mon" &
+ MONPID=$!
+}
+
+wait_mon() {
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
+ if grep -q "$1" "$mon"; then break; fi
+ done
+ assert_in "$2" "$(cat "$mon")"
+ kill "$MONPID"
+ wait "$MONPID" 2>/dev/null || true
+}
+
+test_ntp() {
+ # timesyncd has ConditionVirtualization=!container by default; drop/mock that for testing
+ if systemd-detect-virt --container --quiet; then
+ systemctl disable --quiet --now systemd-timesyncd
+ mkdir -p /run/systemd/system/systemd-timesyncd.service.d
+ cat >/run/systemd/system/systemd-timesyncd.service.d/container.conf <<EOF
+[Unit]
+ConditionVirtualization=
+
+[Service]
+Type=simple
+AmbientCapabilities=
+ExecStart=
+ExecStart=/bin/sleep infinity
+EOF
+ systemctl daemon-reload
+ fi
+
+ mon=$(mktemp -t dbusmon.XXXXXX)
+
+ echo 'disable NTP'
+ timedatectl set-ntp false
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
+ if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=inactive" ]]; then
+ break;
+ fi
+ done
+ assert_eq "$(systemctl show systemd-timesyncd --property ActiveState)" "ActiveState=inactive"
+ assert_ntp "false"
+ assert_rc 3 systemctl is-active --quiet systemd-timesyncd
+
+ echo 'enable NTP'
+ start_mon
+ timedatectl set-ntp true
+ wait_mon "NTP" "BOOLEAN true"
+ assert_ntp "true"
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
+ if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=active" ]]; then
+ break;
+ fi
+ done
+ assert_eq "$(systemctl show systemd-timesyncd --property ActiveState)" "ActiveState=active"
+ assert_rc 0 systemctl is-active --quiet systemd-timesyncd
+
+ echo 're-disable NTP'
+ start_mon
+ timedatectl set-ntp false
+ wait_mon "NTP" "BOOLEAN false"
+ assert_ntp "false"
+ assert_rc 3 systemctl is-active --quiet systemd-timesyncd
+}
+
+: >/failed
+
+test_timedatectl
+test_timezone
+test_adjtime
+test_ntp
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-46.service b/test/units/testsuite-46.service
new file mode 100644
index 0000000..26b3350
--- /dev/null
+++ b/test/units/testsuite-46.service
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-46-HOMED
+Wants=getty-pre.target
+Before=getty-pre.target
+Wants=systemd-homed.service
+After=systemd-homed.service
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+NotifyAccess=all
diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh
new file mode 100755
index 0000000..ec80b71
--- /dev/null
+++ b/test/units/testsuite-46.sh
@@ -0,0 +1,313 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Check if homectl is installed, and if it isn't bail out early instead of failing
+if ! test -x /usr/bin/homectl ; then
+ echo "no homed" >/skipped
+ exit 0
+fi
+
+inspect() {
+ # As updating disk-size-related attributes can take some time on some
+ # filesystems, let's drop these fields before comparing the outputs to
+ # avoid unexpected fails. To see the full outputs of both homectl &
+ # userdbctl (for debugging purposes) drop the fields just before the
+ # comparison.
+ local USERNAME="${1:?}"
+ homectl inspect "$USERNAME" | tee /tmp/a
+ userdbctl user "$USERNAME" | tee /tmp/b
+
+ # diff uses the grep BREs for pattern matching
+ diff -I '^\s*Disk \(Size\|Free\|Floor\|Ceiling\):' /tmp/{a,b}
+ rm /tmp/{a,b}
+
+ homectl inspect --json=pretty "$USERNAME"
+}
+
+wait_for_state() {
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 0.5
+ homectl inspect "$1" | grep -qF "State: $2" && break
+ done
+}
+
+systemd-analyze log-level debug
+systemctl service-log-level systemd-homed debug
+
+# Create a tmpfs to use as backing store for the home dir. That way we can enforce a size limit nicely.
+mkdir -p /home
+mount -t tmpfs tmpfs /home -o size=290M
+
+# we enable --luks-discard= since we run our tests in a tight VM, hence don't
+# needlessly pressure for storage. We also set the cheapest KDF, since we don't
+# want to waste CI CPU cycles on it.
+NEWPASSWORD=xEhErW0ndafV4s homectl create test-user \
+ --disk-size=min \
+ --luks-discard=yes \
+ --image-path=/home/test-user.home \
+ --luks-pbkdf-type=pbkdf2 \
+ --luks-pbkdf-time-cost=1ms
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl authenticate test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test"
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s NEWPASSWORD=yPN4N0fYNKUkOq homectl passwd test-user
+inspect test-user
+
+PASSWORD=yPN4N0fYNKUkOq homectl activate test-user
+inspect test-user
+
+SYSTEMD_LOG_LEVEL=debug PASSWORD=yPN4N0fYNKUkOq NEWPASSWORD=xEhErW0ndafV4s homectl passwd test-user
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+# Do some resize tests, but only if we run on real kernels, as quota inside of containers will fail
+if ! systemd-detect-virt -cq ; then
+ # grow while inactive
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M
+ inspect test-user
+
+ # minimize while inactive
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user min
+ inspect test-user
+
+ PASSWORD=xEhErW0ndafV4s homectl activate test-user
+ inspect test-user
+
+ # grow while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user max
+ inspect test-user
+
+ # minimize while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 0
+ inspect test-user
+
+ # grow while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M
+ inspect test-user
+
+ # shrink to original size while active
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user 256M
+ inspect test-user
+
+ # minimize again
+ PASSWORD=xEhErW0ndafV4s homectl resize test-user min
+ inspect test-user
+
+ # Increase space, so that we can reasonably rebalance free space between to home dirs
+ mount /home -o remount,size=800M
+
+ # create second user
+ NEWPASSWORD=uuXoo8ei homectl create test-user2 \
+ --disk-size=min \
+ --luks-discard=yes \
+ --image-path=/home/test-user2.home \
+ --luks-pbkdf-type=pbkdf2 \
+ --luks-pbkdf-time-cost=1ms
+ inspect test-user2
+
+ # activate second user
+ PASSWORD=uuXoo8ei homectl activate test-user2
+ inspect test-user2
+
+ # set second user's rebalance weight to 100
+ PASSWORD=uuXoo8ei homectl update test-user2 --rebalance-weight=100
+ inspect test-user2
+
+ # set first user's rebalance weight to quarter of that of the second
+ PASSWORD=xEhErW0ndafV4s homectl update test-user --rebalance-weight=25
+ inspect test-user
+
+ # synchronously rebalance
+ homectl rebalance
+ inspect test-user
+ inspect test-user2
+fi
+
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
+
+wait_for_state test-user inactive
+homectl remove test-user
+
+if ! systemd-detect-virt -cq ; then
+ wait_for_state test-user2 active
+ homectl deactivate test-user2
+ wait_for_state test-user2 inactive
+ homectl remove test-user2
+fi
+
+# userdbctl tests
+export PAGER=
+
+# Create a couple of user/group records to test io.systemd.DropIn
+# See docs/USER_RECORD.md and docs/GROUP_RECORD.md
+mkdir -p /run/userdb/
+cat >"/run/userdb/dropingroup.group" <<\EOF
+{
+ "groupName" : "dropingroup",
+ "gid" : 1000000
+}
+EOF
+cat >"/run/userdb/dropinuser.user" <<\EOF
+{
+ "userName" : "dropinuser",
+ "uid" : 2000000,
+ "realName" : "🐱",
+ "memberOf" : [
+ "dropingroup"
+ ]
+}
+EOF
+cat >"/run/userdb/dropinuser.user-privileged" <<\EOF
+{
+ "privileged" : {
+ "hashedPassword" : [
+ "$6$WHBKvAFFT9jKPA4k$OPY4D4TczKN/jOnJzy54DDuOOagCcvxxybrwMbe1SVdm.Bbr.zOmBdATp.QrwZmvqyr8/SafbbQu.QZ2rRvDs/"
+ ],
+ "sshAuthorizedKeys" : [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA//dxI2xLg4MgxIKKZv1nqwTEIlE/fdakii2Fb75pG+ foo@bar.tld",
+ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMlaqG2rTMje5CQnfjXJKmoSpEVJ2gWtx4jBvsQbmee2XbU/Qdq5+SRisssR9zVuxgg5NA5fv08MgjwJQMm+csc= hello@world.tld"
+ ]
+ }
+}
+EOF
+# Set permissions and create necessary symlinks as described in nss-systemd(8)
+chmod 0600 "/run/userdb/dropinuser.user-privileged"
+ln -svrf "/run/userdb/dropingroup.group" "/run/userdb/1000000.group"
+ln -svrf "/run/userdb/dropinuser.user" "/run/userdb/2000000.user"
+ln -svrf "/run/userdb/dropinuser.user-privileged" "/run/userdb/2000000.user-privileged"
+
+userdbctl
+userdbctl --version
+userdbctl --help --no-pager
+userdbctl --no-legend
+userdbctl --output=classic
+userdbctl --output=friendly
+userdbctl --output=table
+userdbctl --output=json | jq
+userdbctl -j --json=pretty | jq
+userdbctl -j --json=short | jq
+userdbctl --with-varlink=no
+
+userdbctl user
+userdbctl user testuser
+userdbctl user root
+userdbctl user testuser root
+userdbctl user -j testuser root | jq
+# Check only UID for the nobody user, since the name is build-configurable
+userdbctl user --with-nss=no --synthesize=yes
+userdbctl user --with-nss=no --synthesize=yes 0 root 65534
+userdbctl user dropinuser
+userdbctl user 2000000
+userdbctl user --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropinuser
+userdbctl user --with-nss=no 2000000
+(! userdbctl user '')
+(! userdbctl user 🐱)
+(! userdbctl user 🐱 '' bar)
+(! userdbctl user i-do-not-exist)
+(! userdbctl user root i-do-not-exist testuser)
+(! userdbctl user --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl user -N root nobody)
+(! userdbctl user --with-dropin=no dropinuser)
+(! userdbctl user --with-dropin=no 2000000)
+
+userdbctl group
+userdbctl group testuser
+userdbctl group root
+userdbctl group testuser root
+userdbctl group -j testuser root | jq
+# Check only GID for the nobody group, since the name is build-configurable
+userdbctl group --with-nss=no --synthesize=yes
+userdbctl group --with-nss=no --synthesize=yes 0 root 65534
+userdbctl group dropingroup
+userdbctl group 1000000
+userdbctl group --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropingroup
+userdbctl group --with-nss=no 1000000
+(! userdbctl group '')
+(! userdbctl group 🐱)
+(! userdbctl group 🐱 '' bar)
+(! userdbctl group i-do-not-exist)
+(! userdbctl group root i-do-not-exist testuser)
+(! userdbctl group --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl group --with-dropin=no dropingroup)
+(! userdbctl group --with-dropin=no 1000000)
+
+userdbctl users-in-group
+userdbctl users-in-group testuser
+userdbctl users-in-group testuser root
+userdbctl users-in-group -j testuser root | jq
+userdbctl users-in-group 🐱
+(! userdbctl users-in-group '')
+(! userdbctl users-in-group foo '' bar)
+
+userdbctl groups-of-user
+userdbctl groups-of-user testuser
+userdbctl groups-of-user testuser root
+userdbctl groups-of-user -j testuser root | jq
+userdbctl groups-of-user 🐱
+(! userdbctl groups-of-user '')
+(! userdbctl groups-of-user foo '' bar)
+
+userdbctl services
+userdbctl services -j | jq
+
+userdbctl ssh-authorized-keys dropinuser | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+echo "my-top-secret-key 🐱" >/tmp/my-top-secret-key
+userdbctl ssh-authorized-keys dropinuser --chain /bin/cat /tmp/my-top-secret-key | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+grep "my-top-secret-key 🐱" /tmp/authorized-keys
+(! userdbctl ssh-authorized-keys 🐱)
+(! userdbctl ssh-authorized-keys dropin-user --chain)
+(! userdbctl ssh-authorized-keys dropin-user --chain '')
+(! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /bin/false)
+
+(! userdbctl '')
+for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do
+ (! userdbctl "--$opt=''")
+ (! userdbctl "--$opt='🐱'")
+ (! userdbctl "--$opt=foo")
+ (! userdbctl "--$opt=foo" "--$opt=''" "--$opt=🐱")
+done
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-47-repro.service b/test/units/testsuite-47-repro.service
new file mode 100644
index 0000000..1508ac6
--- /dev/null
+++ b/test/units/testsuite-47-repro.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Issue 14566 Repro
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+ExecStopPost=/bin/true
+KillMode=mixed
diff --git a/test/units/testsuite-47-repro.sh b/test/units/testsuite-47-repro.sh
new file mode 100755
index 0000000..74fa760
--- /dev/null
+++ b/test/units/testsuite-47-repro.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+sleep infinity &
+echo $! >/leakedtestpid
+wait $!
diff --git a/test/units/testsuite-47.service b/test/units/testsuite-47.service
new file mode 100644
index 0000000..d5ad480
--- /dev/null
+++ b/test/units/testsuite-47.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-47-ISSUE-14566
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-47.sh b/test/units/testsuite-47.sh
new file mode 100755
index 0000000..529e961
--- /dev/null
+++ b/test/units/testsuite-47.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+systemctl start testsuite-47-repro
+sleep 4
+systemctl status testsuite-47-repro
+
+leaked_pid=$(cat /leakedtestpid)
+
+systemctl stop testsuite-47-repro
+sleep 4
+
+# Leaked PID will still be around if we're buggy.
+# I personally prefer to see 42.
+ps -p "$leaked_pid" && exit 42
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-48.service b/test/units/testsuite-48.service
new file mode 100644
index 0000000..7476956
--- /dev/null
+++ b/test/units/testsuite-48.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-48-START-STOP-NO-RELOAD
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-48.sh b/test/units/testsuite-48.sh
new file mode 100755
index 0000000..2b5b86f
--- /dev/null
+++ b/test/units/testsuite-48.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+
+cat >/run/systemd/system/testservice-48.target <<EOF
+[Unit]
+Wants=testservice-48.service
+EOF
+
+systemctl daemon-reload
+
+systemctl start testservice-48.target
+
+# The filesystem on the test image, despite being ext4, seems to have a mtime
+# granularity of one second, which means the manager's unit cache won't be
+# marked as dirty when writing the unit file, unless we wait at least a full
+# second after the previous daemon-reload.
+# May 07 23:12:20 H testsuite-48.sh[30]: + cat
+# May 07 23:12:20 H testsuite-48.sh[30]: + ls -l --full-time /etc/systemd/system/testservice-48.service
+# May 07 23:12:20 H testsuite-48.sh[52]: -rw-r--r-- 1 root root 50 2020-05-07 23:12:20.000000000 +0100 /
+# May 07 23:12:20 H testsuite-48.sh[30]: + stat -f --format=%t /etc/systemd/system/testservice-48.servic
+# May 07 23:12:20 H testsuite-48.sh[53]: ef53
+sleep 3.1
+
+cat >/run/systemd/system/testservice-48.service <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+EOF
+
+systemctl start testservice-48.service
+
+systemctl is-active testservice-48.service
+
+# Stop and remove, and try again to exercise https://github.com/systemd/systemd/issues/15992
+systemctl stop testservice-48.service
+rm -f /run/systemd/system/testservice-48.service
+systemctl daemon-reload
+
+sleep 3.1
+
+cat >/run/systemd/system/testservice-48.service <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+EOF
+
+# Start a non-existing unit first, so that the cache is reloaded for an unrelated
+# reason. Starting the existing unit later should still work thanks to the check
+# for the last load attempt vs cache timestamp.
+systemctl start testservice-48-nonexistent.service || true
+
+systemctl start testservice-48.service
+
+systemctl is-active testservice-48.service
+
+# Stop and remove, and try again to exercise the transaction setup code path by
+# having the target pull in the unloaded but available unit
+systemctl stop testservice-48.service testservice-48.target
+rm -f /run/systemd/system/testservice-48.service /run/systemd/system/testservice-48.target
+systemctl daemon-reload
+
+sleep 3.1
+
+cat >/run/systemd/system/testservice-48.target <<EOF
+[Unit]
+Conflicts=shutdown.target
+Wants=testservice-48.service
+EOF
+
+systemctl daemon-reload
+
+systemctl start testservice-48.target
+
+cat >/run/systemd/system/testservice-48.service <<EOF
+[Service]
+ExecStart=/bin/sleep infinity
+EOF
+
+systemctl restart testservice-48.target
+
+systemctl is-active testservice-48.service
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-49-namespaced.service b/test/units/testsuite-49-namespaced.service
new file mode 100644
index 0000000..93abc31
--- /dev/null
+++ b/test/units/testsuite-49-namespaced.service
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+RuntimeMaxSec=300
+# Adding a new mounts at runtime works if the unit is in the active state,
+# so use Type=notify to make sure there's no race condition in the test
+Type=notify
+RemainAfterExit=yes
+MountAPIVFS=yes
+PrivateTmp=yes
+BindPaths=/run/testservice-49-fixed:/tmp/testfile_fixed
+InaccessiblePaths=/run/inaccessible
+ExecStartPre=grep -q -F MARKER_FIXED /tmp/testfile_fixed
+ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER_RUNTIME /tmp/testfile_runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile_fixed'
diff --git a/test/units/testsuite-49-non-namespaced.service b/test/units/testsuite-49-non-namespaced.service
new file mode 100644
index 0000000..db4e8d9
--- /dev/null
+++ b/test/units/testsuite-49-non-namespaced.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Service]
+RuntimeMaxSec=10
+Type=notify
+RemainAfterExit=yes
+ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER_RUNTIME /tmp/testfile_runtime; do sleep 0.1; done; exit 0'
diff --git a/test/units/testsuite-49.service b/test/units/testsuite-49.service
new file mode 100644
index 0000000..bd4e155
--- /dev/null
+++ b/test/units/testsuite-49.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-49-RUNTIME-BIND-PATHS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-49.sh b/test/units/testsuite-49.sh
new file mode 100755
index 0000000..1fa9725
--- /dev/null
+++ b/test/units/testsuite-49.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+
+echo "MARKER_FIXED" >/run/testservice-49-fixed
+mkdir -p /run/inaccessible
+
+systemctl start testsuite-49-namespaced.service
+
+# Ensure that inaccessible paths aren't bypassed by the runtime setup
+set +e
+systemctl bind --mkdir testsuite-49-namespaced.service /run/testservice-49-fixed /run/inaccessible/testfile_fixed && exit 1
+set -e
+
+echo "MARKER_RUNTIME" >/run/testservice-49-runtime
+
+systemctl bind --mkdir testsuite-49-namespaced.service /run/testservice-49-runtime /tmp/testfile_runtime
+
+while systemctl show -P SubState testsuite-49-namespaced.service | grep -q running
+do
+ sleep 0.1
+done
+
+systemctl is-active testsuite-49-namespaced.service
+
+# Now test that systemctl bind fails when attempted on a non-namespaced unit
+systemctl start testsuite-49-non-namespaced.service
+
+set +e
+systemctl bind --mkdir testsuite-49-non-namespaced.service /run/testservice-49-runtime /tmp/testfile_runtime && exit 1
+set -e
+
+while systemctl show -P SubState testsuite-49-non-namespaced.service | grep -q running
+do
+ sleep 0.1
+done
+
+set +e
+systemctl is-active testsuite-49-non-namespaced.service && exit 1
+set -e
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-50.service b/test/units/testsuite-50.service
new file mode 100644
index 0000000..bcafe6e
--- /dev/null
+++ b/test/units/testsuite-50.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-50-DISSECT
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
new file mode 100755
index 0000000..7589202
--- /dev/null
+++ b/test/units/testsuite-50.sh
@@ -0,0 +1,405 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+set -o pipefail
+
+export SYSTEMD_LOG_LEVEL=debug
+
+cleanup() {(
+ set +ex
+
+ if [ -z "${image_dir}" ]; then
+ return
+ fi
+ umount "${image_dir}/app0"
+ umount "${image_dir}/app1"
+ umount "${image_dir}/app-nodistro"
+ rm -rf "${image_dir}"
+)}
+
+udevadm control --log-level=debug
+
+cd /tmp
+
+image_dir="$(mktemp -d -t -p /tmp tmp.XXXXXX)"
+if [ -z "${image_dir}" ] || [ ! -d "${image_dir}" ]; then
+ echo "mktemp under /tmp failed"
+ exit 1
+fi
+
+trap cleanup EXIT
+
+cp /usr/share/minimal* "${image_dir}/"
+image="${image_dir}/minimal_0"
+roothash="$(cat "${image}.roothash")"
+
+os_release="$(test -e /etc/os-release && echo /etc/os-release || echo /usr/lib/os-release)"
+
+systemd-dissect --json=short "${image}.raw" | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"partition_label":null,"fstype":"squashfs","architecture":null,"verity":"external"'
+systemd-dissect "${image}.raw" | grep -q -F "MARKER=1"
+systemd-dissect "${image}.raw" | grep -q -F -f <(sed 's/"//g' "$os_release")
+
+mv "${image}.verity" "${image}.fooverity"
+mv "${image}.roothash" "${image}.foohash"
+systemd-dissect --json=short "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"partition_label":null,"fstype":"squashfs","architecture":null,"verity":"external"'
+systemd-dissect "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F "MARKER=1"
+systemd-dissect "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F -f <(sed 's/"//g' "$os_release")
+mv "${image}.fooverity" "${image}.verity"
+mv "${image}.foohash" "${image}.roothash"
+
+mkdir -p "${image_dir}/mount" "${image_dir}/mount2"
+systemd-dissect --mount "${image}.raw" "${image_dir}/mount"
+grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
+grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"
+grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release"
+# Verity volume should be shared (opened only once)
+systemd-dissect --mount "${image}.raw" "${image_dir}/mount2"
+verity_count=$(find /dev/mapper/ -name "*verity*" | wc -l)
+# In theory we should check that count is exactly one. In practice, libdevmapper
+# randomly and unpredictably fails with an unhelpful EINVAL when a device is open
+# (and even mounted and in use), so best-effort is the most we can do for now
+if [ "${verity_count}" -lt 1 ]; then
+ echo "Verity device ${image}.raw not found in /dev/mapper/"
+ exit 1
+fi
+systemd-dissect --umount "${image_dir}/mount"
+systemd-dissect --umount "${image_dir}/mount2"
+
+systemd-run -P -p RootImage="${image}.raw" cat /usr/lib/os-release | grep -q -F "MARKER=1"
+mv "${image}.verity" "${image}.fooverity"
+mv "${image}.roothash" "${image}.foohash"
+systemd-run -P -p RootImage="${image}.raw" -p RootHash="${image}.foohash" -p RootVerity="${image}.fooverity" cat /usr/lib/os-release | grep -q -F "MARKER=1"
+# Let's use the long option name just here as a test
+systemd-run -P --property RootImage="${image}.raw" --property RootHash="${roothash}" --property RootVerity="${image}.fooverity" cat /usr/lib/os-release | grep -q -F "MARKER=1"
+mv "${image}.fooverity" "${image}.verity"
+mv "${image}.foohash" "${image}.roothash"
+
+# Make a GPT disk on the fly, with the squashfs as partition 1 and the verity hash tree as partition 2
+machine="$(uname -m)"
+if [ "${machine}" = "x86_64" ]; then
+ root_guid=4f68bce3-e8cd-4db1-96e7-fbcaf984b709
+ verity_guid=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5
+ signature_guid=41092b05-9fc8-4523-994f-2def0408b176
+ architecture="x86-64"
+elif [ "${machine}" = "i386" ] || [ "${machine}" = "i686" ] || [ "${machine}" = "x86" ]; then
+ root_guid=44479540-f297-41b2-9af7-d131d5f0458a
+ verity_guid=d13c5d3b-b5d1-422a-b29f-9454fdc89d76
+ signature_guid=5996fc05-109c-48de-808b-23fa0830b676
+ architecture="x86"
+elif [ "${machine}" = "aarch64" ] || [ "${machine}" = "aarch64_be" ] || [ "${machine}" = "armv8b" ] || [ "${machine}" = "armv8l" ]; then
+ root_guid=b921b045-1df0-41c3-af44-4c6f280d3fae
+ verity_guid=df3300ce-d69f-4c92-978c-9bfb0f38d820
+ signature_guid=6db69de6-29f4-4758-a7a5-962190f00ce3
+ architecture="arm64"
+elif [ "${machine}" = "arm" ]; then
+ root_guid=69dad710-2ce4-4e3c-b16c-21a1d49abed3
+ verity_guid=7386cdf2-203c-47a9-a498-f2ecce45a2d6
+ signature_guid=42b0455f-eb11-491d-98d3-56145ba9d037
+ architecture="arm"
+elif [ "${machine}" = "loongarch64" ]; then
+ root_guid=77055800-792c-4f94-b39a-98c91b762bb6
+ verity_guid=f3393b22-e9af-4613-a948-9d3bfbd0c535
+ signature_guid=5afb67eb-ecc8-4f85-ae8e-ac1e7c50e7d0
+ architecture="loongarch64"
+elif [ "${machine}" = "ia64" ]; then
+ root_guid=993d8d3d-f80e-4225-855a-9daf8ed7ea97
+ verity_guid=86ed10d5-b607-45bb-8957-d350f23d0571
+ signature_guid=e98b36ee-32ba-4882-9b12-0ce14655f46a
+ architecture="ia64"
+elif [ "${machine}" = "s390x" ]; then
+ root_guid=5eead9a9-fe09-4a1e-a1d7-520d00531306
+ verity_guid=b325bfbe-c7be-4ab8-8357-139e652d2f6b
+ signature_guid=c80187a5-73a3-491a-901a-017c3fa953e9
+ architecture="s390x"
+elif [ "${machine}" = "ppc64le" ]; then
+ root_guid=c31c45e6-3f39-412e-80fb-4809c4980599
+ verity_guid=906bd944-4589-4aae-a4e4-dd983917446a
+ signature_guid=d4a236e7-e873-4c07-bf1d-bf6cf7f1c3c6
+ architecture="ppc64-le"
+else
+ echo "Unexpected uname -m: ${machine} in testsuite-50.sh, please fix me"
+ exit 1
+fi
+# du rounds up to block size, which is more helpful for partitioning
+root_size="$(du -k "${image}.raw" | cut -f1)"
+verity_size="$(du -k "${image}.verity" | cut -f1)"
+signature_size=4
+# 4MB seems to be the minimum size blkid will accept, below that probing fails
+dd if=/dev/zero of="${image}.gpt" bs=512 count=$((8192+root_size*2+verity_size*2+signature_size*2))
+# sfdisk seems unhappy if the size overflows into the next unit, eg: 1580KiB will be interpreted as 1MiB
+# so do some basic rounding up if the minimal image is more than 1 MB
+if [ "${root_size}" -ge 1024 ]; then
+ root_size="$((root_size/1024 + 1))MiB"
+else
+ root_size="${root_size}KiB"
+fi
+verity_size="$((verity_size * 2))KiB"
+signature_size="$((signature_size * 2))KiB"
+
+HAVE_OPENSSL=0
+if systemctl --version | grep -q -- +OPENSSL ; then
+ # The openssl binary is installed conditionally.
+ # If we have OpenSSL support enabled and openssl is missing, fail early
+ # with a proper error message.
+ if ! command -v openssl >/dev/null 2>&1; then
+ echo "openssl missing" >/failed
+ exit 1
+ fi
+ HAVE_OPENSSL=1
+ # Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
+ cat >> "${image}.openssl.cnf" <<EOF
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+C = DE
+ST = Test State
+L = Test Locality
+O = Org Name
+OU = Org Unit Name
+CN = Common Name
+emailAddress = test@email.com
+EOF
+
+ # Create key pair
+ openssl req -config "${image}.openssl.cnf" -new -x509 -newkey rsa:1024 -keyout "${image}.key" -out "${image}.crt" -days 365 -nodes
+ # Sign Verity root hash with it
+ openssl smime -sign -nocerts -noattr -binary -in "${image}.roothash" -inkey "${image}.key" -signer "${image}.crt" -outform der -out "${image}.roothash.p7s"
+ # Generate signature partition JSON data
+ echo '{"rootHash":"'"${roothash}"'","signature":"'"$(base64 -w 0 < "${image}.roothash.p7s")"'"}' > "${image}.verity-sig"
+ # Pad it
+ truncate -s "${signature_size}" "${image}.verity-sig"
+ # Register certificate in the (userspace) verity key ring
+ mkdir -p /run/verity.d
+ ln -s "${image}.crt" /run/verity.d/ok.crt
+fi
+
+# Construct a UUID from hash
+# input: 11111111222233334444555566667777
+# output: 11111111-2222-3333-4444-555566667777
+uuid="$(head -c 32 "${image}.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')"
+echo -e "label: gpt\nsize=${root_size}, type=${root_guid}, uuid=${uuid}" | sfdisk "${image}.gpt"
+uuid="$(tail -c 32 "${image}.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')"
+echo -e "size=${verity_size}, type=${verity_guid}, uuid=${uuid}" | sfdisk "${image}.gpt" --append
+if [ "${HAVE_OPENSSL}" -eq 1 ]; then
+ echo -e "size=${signature_size}, type=${signature_guid}" | sfdisk "${image}.gpt" --append
+fi
+sfdisk --part-label "${image}.gpt" 1 "Root Partition"
+sfdisk --part-label "${image}.gpt" 2 "Verity Partition"
+if [ "${HAVE_OPENSSL}" -eq 1 ]; then
+ sfdisk --part-label "${image}.gpt" 3 "Signature Partition"
+fi
+loop="$(losetup --show -P -f "${image}.gpt")"
+partitions=(
+ "${loop:?}p1"
+ "${loop:?}p2"
+)
+if [ "${HAVE_OPENSSL}" -eq 1 ]; then
+ partitions+=( "${loop:?}p3" )
+fi
+# The kernel sometimes(?) does not emit "add" uevent for loop block partition devices.
+# Let's not expect the devices to be initialized.
+udevadm wait --timeout 60 --settle --initialized=no "${partitions[@]}"
+udevadm lock --device="${loop}p1" dd if="${image}.raw" of="${loop}p1"
+udevadm lock --device="${loop}p2" dd if="${image}.verity" of="${loop}p2"
+if [ "${HAVE_OPENSSL}" -eq 1 ]; then
+ udevadm lock --device="${loop}p3" dd if="${image}.verity-sig" of="${loop}p3"
+fi
+losetup -d "${loop}"
+
+# Derive partition UUIDs from root hash, in UUID syntax
+ROOT_UUID="$(systemd-id128 -u show "$(head -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)"
+VERITY_UUID="$(systemd-id128 -u show "$(tail -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)"
+
+systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'"$ROOT_UUID"'","partition_label":"Root Partition","fstype":"squashfs","architecture":"'"$architecture"'","verity":"signed",'
+systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root-verity","partition_uuid":"'"$VERITY_UUID"'","partition_label":"Verity Partition","fstype":"DM_verity_hash","architecture":"'"$architecture"'","verity":null,'
+if [ "${HAVE_OPENSSL}" -eq 1 ]; then
+ systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q -E '{"rw":"ro","designator":"root-verity-sig","partition_uuid":"'".*"'","partition_label":"Signature Partition","fstype":"verity_hash_signature","architecture":"'"$architecture"'","verity":null,'
+fi
+systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1"
+systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release")
+
+systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount"
+grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
+grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"
+grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release"
+systemd-dissect --umount "${image_dir}/mount"
+
+# add explicit -p MountAPIVFS=yes once to test the parser
+systemd-run -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+
+systemd-run -P -p RootImage="${image}.raw" -p RootImageOptions="root:nosuid,dev home:ro,dev ro,noatime" mount | grep -F "squashfs" | grep -q -F "nosuid"
+systemd-run -P -p RootImage="${image}.gpt" -p RootImageOptions="root:ro,noatime root:ro,dev" mount | grep -F "squashfs" | grep -q -F "noatime"
+
+mkdir -p "${image_dir}/result"
+cat >/run/systemd/system/testservice-50a.service <<EOF
+[Service]
+Type=oneshot
+ExecStart=bash -c "mount >/run/result/a"
+BindPaths=${image_dir}/result:/run/result
+TemporaryFileSystem=/run
+RootImage=${image}.raw
+RootImageOptions=root:ro,noatime home:ro,dev relatime,dev
+RootImageOptions=nosuid,dev
+EOF
+systemctl start testservice-50a.service
+grep -F "squashfs" "${image_dir}/result/a" | grep -q -F "noatime"
+grep -F "squashfs" "${image_dir}/result/a" | grep -q -F -v "nosuid"
+
+cat >/run/systemd/system/testservice-50b.service <<EOF
+[Service]
+Type=oneshot
+ExecStart=bash -c "mount >/run/result/b"
+BindPaths=${image_dir}/result:/run/result
+TemporaryFileSystem=/run
+RootImage=${image}.gpt
+RootImageOptions=root:ro,noatime,nosuid home:ro,dev nosuid,dev
+RootImageOptions=home:ro,dev nosuid,dev,%%foo
+# this is the default, but let's specify once to test the parser
+MountAPIVFS=yes
+EOF
+systemctl start testservice-50b.service
+grep -F "squashfs" "${image_dir}/result/b" | grep -q -F "noatime"
+
+# Check that specifier escape is applied %%foo → %foo
+busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/testservice_2d50b_2eservice org.freedesktop.systemd1.Service RootImageOptions | grep -F "nosuid,dev,%foo"
+
+# Now do some checks with MountImages, both by itself, with options and in combination with RootImage, and as single FS or GPT image
+systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2:nosuid,dev" mount | grep -F "squashfs" | grep -q -F "nosuid"
+systemd-run -P -p MountImages="${image}.gpt:/run/img1:root:nosuid ${image}.raw:/run/img2:home:suid" mount | grep -F "squashfs" | grep -q -F "nosuid"
+systemd-run -P -p MountImages="${image}.raw:/run/img2\:3" cat /run/img2:3/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -P -p MountImages="${image}.raw:/run/img2\:3:nosuid" mount | grep -F "squashfs" | grep -q -F "nosuid"
+systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.raw" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.raw" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50c.service <<EOF
+[Service]
+MountAPIVFS=yes
+TemporaryFileSystem=/run
+RootImage=${image}.raw
+MountImages=${image}.gpt:/run/img1:root:noatime:home:relatime
+MountImages=${image}.raw:/run/img2\:3:nosuid
+ExecStart=bash -c "cat /run/img1/usr/lib/os-release >/run/result/c"
+ExecStart=bash -c "cat /run/img2:3/usr/lib/os-release >>/run/result/c"
+ExecStart=bash -c "mount >>/run/result/c"
+BindPaths=${image_dir}/result:/run/result
+Type=oneshot
+EOF
+systemctl start testservice-50c.service
+grep -q -F "MARKER=1" "${image_dir}/result/c"
+grep -F "squashfs" "${image_dir}/result/c" | grep -q -F "noatime"
+grep -F "squashfs" "${image_dir}/result/c" | grep -q -F -v "nosuid"
+
+# Adding a new mounts at runtime works if the unit is in the active state,
+# so use Type=notify to make sure there's no race condition in the test
+cat >/run/systemd/system/testservice-50d.service <<EOF
+[Service]
+RuntimeMaxSec=300
+Type=notify
+RemainAfterExit=yes
+MountAPIVFS=yes
+PrivateTmp=yes
+ExecStart=/bin/sh -c ' \\
+ systemd-notify --ready; \\
+ while [ ! -f /tmp/img/usr/lib/os-release ] || ! grep -q -F MARKER /tmp/img/usr/lib/os-release; do \\
+ sleep 0.1; \\
+ done; \\
+ mount; \\
+ mount | grep -F "on /tmp/img type squashfs" | grep -q -F "nosuid"; \\
+'
+EOF
+systemctl start testservice-50d.service
+
+systemctl mount-image --mkdir testservice-50d.service "${image}.raw" /tmp/img root:nosuid
+
+while systemctl show -P SubState testservice-50d.service | grep -q running
+do
+ sleep 0.1
+done
+
+systemctl is-active testservice-50d.service
+
+# ExtensionImages will set up an overlay
+systemd-run -P --property ExtensionImages=/usr/share/app0.raw --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionImages=/usr/share/app0.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
+systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionImages=/usr/share/app-nodistro.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+# Check that using a symlink to NAME-VERSION.raw works as long as the symlink has the correct name NAME.raw
+mkdir -p /usr/share/symlink-test/
+cp /usr/share/app-nodistro.raw /usr/share/symlink-test/app-nodistro-v1.raw
+ln -fs /usr/share/symlink-test/app-nodistro-v1.raw /usr/share/symlink-test/app-nodistro.raw
+systemd-run -P --property ExtensionImages=/usr/share/symlink-test/app-nodistro.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50e.service <<EOF
+[Service]
+MountAPIVFS=yes
+TemporaryFileSystem=/run /var/lib
+StateDirectory=app0
+RootImage=${image}.raw
+ExtensionImages=/usr/share/app0.raw /usr/share/app1.raw:nosuid
+# Relevant only for sanitizer runs
+UnsetEnvironment=LD_PRELOAD
+ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
+ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+Type=oneshot
+RemainAfterExit=yes
+EOF
+systemctl start testservice-50e.service
+systemctl is-active testservice-50e.service
+
+# ExtensionDirectories will set up an overlay
+mkdir -p "${image_dir}/app0" "${image_dir}/app1" "${image_dir}/app-nodistro"
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh)
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh)
+systemd-dissect --mount /usr/share/app0.raw "${image_dir}/app0"
+systemd-dissect --mount /usr/share/app1.raw "${image_dir}/app1"
+systemd-dissect --mount /usr/share/app-nodistro.raw "${image_dir}/app-nodistro"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionDirectories="${image_dir}/app-nodistro" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50f.service <<EOF
+[Service]
+MountAPIVFS=yes
+TemporaryFileSystem=/run /var/lib
+StateDirectory=app0
+RootImage=${image}.raw
+ExtensionDirectories=${image_dir}/app0 ${image_dir}/app1
+# Relevant only for sanitizer runs
+UnsetEnvironment=LD_PRELOAD
+ExecStart=/bin/bash -c '/opt/script0.sh | grep ID'
+ExecStart=/bin/bash -c '/opt/script1.sh | grep ID'
+Type=oneshot
+RemainAfterExit=yes
+EOF
+systemctl start testservice-50f.service
+systemctl is-active testservice-50f.service
+systemd-dissect --umount "${image_dir}/app0"
+systemd-dissect --umount "${image_dir}/app1"
+
+# Test that an extension consisting of an empty directory under /etc/extensions/ takes precedence
+mkdir -p /var/lib/extensions/
+ln -s /usr/share/app-nodistro.raw /var/lib/extensions/app-nodistro.raw
+systemd-sysext merge
+grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
+systemd-sysext unmerge
+mkdir -p /etc/extensions/app-nodistro
+systemd-sysext merge
+test ! -e /usr/lib/systemd/system/some_file
+systemd-sysext unmerge
+rmdir /etc/extensions/app-nodistro
+rm /var/lib/extensions/app-nodistro.raw
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-51-repro-1.service b/test/units/testsuite-51-repro-1.service
new file mode 100644
index 0000000..90252b3
--- /dev/null
+++ b/test/units/testsuite-51-repro-1.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Issue 16115 Repro with on-abnormal
+
+[Service]
+Type=simple
+Restart=on-abnormal
+ExecCondition=/bin/false
+ExecStart=sleep 100
+RestartSec=1
diff --git a/test/units/testsuite-51-repro-2.service b/test/units/testsuite-51-repro-2.service
new file mode 100644
index 0000000..7c65691
--- /dev/null
+++ b/test/units/testsuite-51-repro-2.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Issue 16115 Repro with on-failure
+
+[Service]
+Type=simple
+Restart=on-failure
+ExecCondition=/bin/false
+ExecStart=sleep 100
+RestartSec=1
diff --git a/test/units/testsuite-51-repro-3.service b/test/units/testsuite-51-repro-3.service
new file mode 100644
index 0000000..c68f93d
--- /dev/null
+++ b/test/units/testsuite-51-repro-3.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Issue 22257 Repro with Restart=always
+
+[Service]
+Type=simple
+Restart=always
+ExecCondition=/bin/false
+ExecStart=sleep 100
+RestartSec=1
diff --git a/test/units/testsuite-51.service b/test/units/testsuite-51.service
new file mode 100644
index 0000000..c241262
--- /dev/null
+++ b/test/units/testsuite-51.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-51-ISSUE-16115
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-51.sh b/test/units/testsuite-51.sh
new file mode 100755
index 0000000..e603d95
--- /dev/null
+++ b/test/units/testsuite-51.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemctl start testsuite-51-repro-1
+systemctl start testsuite-51-repro-2
+systemctl start testsuite-51-repro-3
+sleep 5 # wait a bit in case there are restarts so we can count them below
+
+[[ "$(systemctl show testsuite-51-repro-1 -P NRestarts)" == "0" ]]
+[[ "$(systemctl show testsuite-51-repro-2 -P NRestarts)" == "0" ]]
+[[ "$(systemctl show testsuite-51-repro-3 -P NRestarts)" == "0" ]]
+
+touch /testok
diff --git a/test/units/testsuite-52.service b/test/units/testsuite-52.service
new file mode 100644
index 0000000..b9f2909
--- /dev/null
+++ b/test/units/testsuite-52.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Testsuite service
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-52.sh b/test/units/testsuite-52.sh
new file mode 100755
index 0000000..d78fdd5
--- /dev/null
+++ b/test/units/testsuite-52.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+systemd-analyze log-level debug
+
+systemctl enable test-honor-first-shutdown.service
+systemctl start test-honor-first-shutdown.service
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-53.service b/test/units/testsuite-53.service
new file mode 100644
index 0000000..cf3adbb
--- /dev/null
+++ b/test/units/testsuite-53.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-53-ISSUE-16347
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-53.sh b/test/units/testsuite-53.sh
new file mode 100755
index 0000000..84cd661
--- /dev/null
+++ b/test/units/testsuite-53.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+# Reset host date to current time, 3 days in the past.
+date -s "-3 days"
+
+# Run a timer for every 15 minutes.
+systemd-run --unit test-timer --on-calendar "*:0/15:0" true
+
+next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
+next_elapsed=$(date -d "${next_elapsed}" +%s)
+now=$(date +%s)
+time_delta=$((next_elapsed - now))
+
+# Check that the timer will elapse in less than 20 minutes.
+((0 < time_delta && time_delta < 1200)) || {
+ echo 'Timer elapse outside of the expected 20 minute window.'
+ echo " next_elapsed=${next_elapsed}"
+ echo " now=${now}"
+ echo " time_delta=${time_delta}"
+ echo ''
+} >>/failed
+
+if test ! -s /failed ; then
+ rm -f /failed
+ touch /testok
+fi
diff --git a/test/units/testsuite-54.service b/test/units/testsuite-54.service
new file mode 100644
index 0000000..ba8cdad
--- /dev/null
+++ b/test/units/testsuite-54.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TESTSUITE-54-CREDS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh
new file mode 100755
index 0000000..3be5076
--- /dev/null
+++ b/test/units/testsuite-54.sh
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+
+systemd-analyze log-level debug
+
+# Verify that the creds are properly loaded and we can read them from the service's unpriv user
+systemd-run -p LoadCredential=passwd:/etc/passwd \
+ -p LoadCredential=shadow:/etc/shadow \
+ -p SetCredential=dog:wuff \
+ -p DynamicUser=1 \
+ --unit=test-54-unpriv.service \
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' \
+ >/tmp/ts54-concat
+(cat /etc/passwd /etc/shadow && echo -n wuff) | cmp /tmp/ts54-concat
+rm /tmp/ts54-concat
+
+# Test that SetCredential= acts as fallback for LoadCredential=
+echo piff > /tmp/ts54-fallback
+[ "$(systemd-run -p LoadCredential=paff:/tmp/ts54-fallback -p SetCredential=paff:poff --pipe --wait systemd-creds cat paff)" = "piff" ]
+rm /tmp/ts54-fallback
+[ "$(systemd-run -p LoadCredential=paff:/tmp/ts54-fallback -p SetCredential=paff:poff --pipe --wait systemd-creds cat paff)" = "poff" ]
+
+if systemd-detect-virt -q -c ; then
+ expected_credential=mynspawncredential
+ expected_value=strangevalue
+elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then
+ # Verify that passing creds through kernel cmdline works
+ [ "$(systemd-creds --system cat kernelcmdlinecred)" = "uff" ]
+
+ # And that it also works via SMBIOS
+ [ "$(systemd-creds --system cat smbioscredential)" = "magicdata" ]
+ [ "$(systemd-creds --system cat binarysmbioscredential)" = "magicbinarydata" ]
+
+ # If we aren't run in nspawn, we are run in qemu
+ systemd-detect-virt -q -v
+ expected_credential=myqemucredential
+ expected_value=othervalue
+
+ # Verify that writing a sysctl via the kernel cmdline worked
+ [ "$(cat /proc/sys/kernel/domainname)" = "sysctltest" ]
+
+ # Verify that creating a user via sysusers via the kernel cmdline worked
+ grep -q ^credtestuser: /etc/passwd
+
+ # Verify that writing a file via tmpfiles worked
+ [ "$(cat /tmp/sourcedfromcredential)" = "tmpfilessecret" ]
+ [ "$(cat /etc/motd.d/50-provision.conf)" = "hello" ]
+ [ "$(cat /etc/issue.d/50-provision.conf)" = "welcome" ]
+else
+ echo "qemu_fw_cfg support missing in kernel. Sniff!"
+ expected_credential=""
+ expected_value=""
+fi
+
+if [ "$expected_credential" != "" ] ; then
+ # If this test is run in nspawn a credential should have been passed to us. See test/TEST-54-CREDS/test.sh
+ [ "$(systemd-creds --system cat "$expected_credential")" = "$expected_value" ]
+
+ # Test that propagation from system credential to service credential works
+ [ "$(systemd-run -p LoadCredential="$expected_credential" --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ]
+
+ # Check it also works, if we rename it while propagating it
+ [ "$(systemd-run -p LoadCredential=miau:"$expected_credential" --pipe --wait systemd-creds cat miau)" = "$expected_value" ]
+
+ # Combine it with a fallback (which should have no effect, given the cred should be passed down)
+ [ "$(systemd-run -p LoadCredential="$expected_credential" -p SetCredential="$expected_credential":zzz --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ]
+
+ # This should succeed
+ systemd-run -p AssertCredential="$expected_credential" -p Type=oneshot true
+
+ # And this should fail
+ (! systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true)
+fi
+
+# Verify that the creds are immutable
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+ -p DynamicUser=1 \
+ --unit=test-54-immutable-touch.service \
+ --wait \
+ touch '${CREDENTIALS_DIRECTORY}/passwd')
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+ -p DynamicUser=1 \
+ --unit=test-54-immutable-rm.service \
+ --wait \
+ rm '${CREDENTIALS_DIRECTORY}/passwd')
+
+# Check directory-based loading
+mkdir -p /tmp/ts54-creds/sub
+echo -n a >/tmp/ts54-creds/foo
+echo -n b >/tmp/ts54-creds/bar
+echo -n c >/tmp/ts54-creds/baz
+echo -n d >/tmp/ts54-creds/sub/qux
+systemd-run -p LoadCredential=cred:/tmp/ts54-creds \
+ -p DynamicUser=1 \
+ --unit=test-54-dir.service \
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
+ '${CREDENTIALS_DIRECTORY}/cred_bar' \
+ '${CREDENTIALS_DIRECTORY}/cred_baz' \
+ '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
+cmp /tmp/ts54-concat <(echo -n abcd)
+rm /tmp/ts54-concat
+rm -rf /tmp/ts54-creds
+
+# Now test encrypted credentials (only supported when built with OpenSSL though)
+if systemctl --version | grep -q -- +OPENSSL ; then
+ echo -n $RANDOM >/tmp/test-54-plaintext
+ systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
+ systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
+
+ systemd-run -p LoadCredentialEncrypted=test-54:/tmp/test-54-ciphertext \
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+
+ echo -n $RANDOM >/tmp/test-54-plaintext
+ systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
+ systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
+
+ systemd-run -p SetCredentialEncrypted=test-54:"$(cat /tmp/test-54-ciphertext)" \
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+
+ rm /tmp/test-54-plaintext /tmp/test-54-ciphertext
+fi
+
+# https://github.com/systemd/systemd/issues/27275
+systemd-run -p DynamicUser=yes -p 'LoadCredential=os:/etc/os-release' \
+ -p 'ExecStartPre=true' \
+ -p 'ExecStartPre=systemd-creds cat os' \
+ --unit=test-54-exec-start.service \
+ --wait \
+ --pipe \
+ true | cmp /etc/os-release
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-55-testbloat.service b/test/units/testsuite-55-testbloat.service
new file mode 100644
index 0000000..6c8e3c9
--- /dev/null
+++ b/test/units/testsuite-55-testbloat.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Create a lot of memory pressure
+
+[Service]
+# A VERY small memory.high will cause the 'stress' (trying to use a lot of memory)
+# to throttle and be put under heavy pressure.
+MemoryHigh=3M
+Slice=testsuite-55-workload.slice
+ExecStart=stress --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-stride 1
diff --git a/test/units/testsuite-55-testchill.service b/test/units/testsuite-55-testchill.service
new file mode 100644
index 0000000..369b802
--- /dev/null
+++ b/test/units/testsuite-55-testchill.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=No memory pressure
+
+[Service]
+MemoryHigh=3M
+Slice=testsuite-55-workload.slice
+ExecStart=sleep infinity
diff --git a/test/units/testsuite-55-testmunch.service b/test/units/testsuite-55-testmunch.service
new file mode 100644
index 0000000..3730059
--- /dev/null
+++ b/test/units/testsuite-55-testmunch.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Create some memory pressure
+
+[Service]
+MemoryHigh=12M
+Slice=testsuite-55-workload.slice
+ExecStart=stress --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-stride 1
diff --git a/test/units/testsuite-55-workload.slice b/test/units/testsuite-55-workload.slice
new file mode 100644
index 0000000..d117b75
--- /dev/null
+++ b/test/units/testsuite-55-workload.slice
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test slice for memory pressure kills
+
+[Slice]
+CPUAccounting=true
+MemoryAccounting=true
+IOAccounting=true
+TasksAccounting=true
+ManagedOOMMemoryPressure=kill
+ManagedOOMMemoryPressureLimit=20%
diff --git a/test/units/testsuite-55.service b/test/units/testsuite-55.service
new file mode 100644
index 0000000..00fb499
--- /dev/null
+++ b/test/units/testsuite-55.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TESTSUITE-55-OOMD
+After=user@4711.service
+Wants=user@4711.service
+
+[Service]
+ExecStartPre=rm -f /failed /skipped /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-55.sh b/test/units/testsuite-55.sh
new file mode 100755
index 0000000..838ba3a
--- /dev/null
+++ b/test/units/testsuite-55.sh
@@ -0,0 +1,179 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+
+# Ensure that the init.scope.d drop-in is applied on boot
+test "$(cat /sys/fs/cgroup/init.scope/memory.high)" != "max"
+
+# Loose checks to ensure the environment has the necessary features for systemd-oomd
+[[ -e /proc/pressure ]] || echo "no PSI" >>/skipped
+cgroup_type="$(stat -fc %T /sys/fs/cgroup/)"
+if [[ "$cgroup_type" != *"cgroup2"* ]] && [[ "$cgroup_type" != *"0x63677270"* ]]; then
+ echo "no cgroup2" >>/skipped
+fi
+if [ ! -f /usr/lib/systemd/systemd-oomd ] && [ ! -f /lib/systemd/systemd-oomd ]; then
+ echo "no oomd" >>/skipped
+fi
+
+if [[ -e /skipped ]]; then
+ exit 0
+fi
+
+rm -rf /run/systemd/system/testsuite-55-testbloat.service.d
+
+# Activate swap file if we are in a VM
+if systemd-detect-virt --vm --quiet; then
+ mkswap /swapfile
+ swapon /swapfile
+ swapon --show
+fi
+
+# Configure oomd explicitly to avoid conflicts with distro dropins
+mkdir -p /run/systemd/oomd.conf.d/
+cat >/run/systemd/oomd.conf.d/99-oomd-test.conf <<EOF
+[OOM]
+DefaultMemoryPressureDurationSec=2s
+EOF
+
+mkdir -p /run/systemd/system/-.slice.d/
+cat >/run/systemd/system/-.slice.d/99-oomd-test.conf <<EOF
+[Slice]
+ManagedOOMSwap=auto
+EOF
+
+mkdir -p /run/systemd/system/user@.service.d/
+cat >/run/systemd/system/user@.service.d/99-oomd-test.conf <<EOF
+[Service]
+ManagedOOMMemoryPressure=auto
+ManagedOOMMemoryPressureLimit=0%
+EOF
+
+mkdir -p /run/systemd/system/systemd-oomd.service.d/
+cat >/run/systemd/system/systemd-oomd.service.d/debug.conf <<EOF
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+
+systemctl daemon-reload
+
+# enable the service to ensure dbus-org.freedesktop.oom1.service exists
+# and D-Bus activation works
+systemctl enable systemd-oomd.service
+
+# if oomd is already running for some reasons, then restart it to make sure the above settings to be applied
+if systemctl is-active systemd-oomd.service; then
+ systemctl restart systemd-oomd.service
+fi
+
+systemctl start testsuite-55-testchill.service
+systemctl start testsuite-55-testbloat.service
+
+# Verify systemd-oomd is monitoring the expected units
+# Try to avoid racing the oomctl output check by checking in a loop with a timeout
+oomctl_output=$(oomctl)
+timeout="$(date -ud "1 minutes" +%s)"
+while [[ $(date -u +%s) -le $timeout ]]; do
+ if grep "/testsuite-55-workload.slice" <<< "$oomctl_output"; then
+ break
+ fi
+ oomctl_output=$(oomctl)
+ sleep 1
+done
+
+grep "/testsuite-55-workload.slice" <<< "$oomctl_output"
+grep "20.00%" <<< "$oomctl_output"
+grep "Default Memory Pressure Duration: 2s" <<< "$oomctl_output"
+
+systemctl status testsuite-55-testchill.service
+
+# systemd-oomd watches for elevated pressure for 2 seconds before acting.
+# It can take time to build up pressure so either wait 2 minutes or for the service to fail.
+timeout="$(date -ud "2 minutes" +%s)"
+while [[ $(date -u +%s) -le $timeout ]]; do
+ if ! systemctl status testsuite-55-testbloat.service; then
+ break
+ fi
+ oomctl
+ sleep 2
+done
+
+# testbloat should be killed and testchill should be fine
+if systemctl status testsuite-55-testbloat.service; then exit 42; fi
+if ! systemctl status testsuite-55-testchill.service; then exit 24; fi
+
+# Make sure we also work correctly on user units.
+
+systemctl start --machine "testuser@.host" --user testsuite-55-testchill.service
+systemctl start --machine "testuser@.host" --user testsuite-55-testbloat.service
+
+# Verify systemd-oomd is monitoring the expected units
+# Try to avoid racing the oomctl output check by checking in a loop with a timeout
+oomctl_output=$(oomctl)
+timeout="$(date -ud "1 minutes" +%s)"
+while [[ $(date -u +%s) -le $timeout ]]; do
+ if grep -E "/user.slice.*/testsuite-55-workload.slice" <<< "$oomctl_output"; then
+ break
+ fi
+ oomctl_output=$(oomctl)
+ sleep 1
+done
+
+grep -E "/user.slice.*/testsuite-55-workload.slice" <<< "$oomctl_output"
+grep "20.00%" <<< "$oomctl_output"
+grep "Default Memory Pressure Duration: 2s" <<< "$oomctl_output"
+
+systemctl --machine "testuser@.host" --user status testsuite-55-testchill.service
+
+# systemd-oomd watches for elevated pressure for 2 seconds before acting.
+# It can take time to build up pressure so either wait 2 minutes or for the service to fail.
+timeout="$(date -ud "2 minutes" +%s)"
+while [[ $(date -u +%s) -le $timeout ]]; do
+ if ! systemctl --machine "testuser@.host" --user status testsuite-55-testbloat.service; then
+ break
+ fi
+ oomctl
+ sleep 2
+done
+
+# testbloat should be killed and testchill should be fine
+if systemctl --machine "testuser@.host" --user status testsuite-55-testbloat.service; then exit 42; fi
+if ! systemctl --machine "testuser@.host" --user status testsuite-55-testchill.service; then exit 24; fi
+
+# only run this portion of the test if we can set xattrs
+if setfattr -n user.xattr_test -v 1 /sys/fs/cgroup/; then
+ sleep 120 # wait for systemd-oomd kill cool down and elevated memory pressure to come down
+
+ mkdir -p /run/systemd/system/testsuite-55-testbloat.service.d/
+ cat >/run/systemd/system/testsuite-55-testbloat.service.d/override.conf <<EOF
+[Service]
+ManagedOOMPreference=avoid
+EOF
+
+ systemctl daemon-reload
+ systemctl start testsuite-55-testchill.service
+ systemctl start testsuite-55-testmunch.service
+ systemctl start testsuite-55-testbloat.service
+
+ timeout="$(date -ud "2 minutes" +%s)"
+ while [[ "$(date -u +%s)" -le "$timeout" ]]; do
+ if ! systemctl status testsuite-55-testmunch.service; then
+ break
+ fi
+ oomctl
+ sleep 2
+ done
+
+ # testmunch should be killed since testbloat had the avoid xattr on it
+ if ! systemctl status testsuite-55-testbloat.service; then exit 25; fi
+ if systemctl status testsuite-55-testmunch.service; then exit 43; fi
+ if ! systemctl status testsuite-55-testchill.service; then exit 24; fi
+fi
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-56.service b/test/units/testsuite-56.service
new file mode 100644
index 0000000..d8ad589
--- /dev/null
+++ b/test/units/testsuite-56.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=TEST-56-EXIT-TYPE
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-56.sh b/test/units/testsuite-56.sh
new file mode 100755
index 0000000..f81c6dd
--- /dev/null
+++ b/test/units/testsuite-56.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+set -eux
+
+systemd-analyze log-level debug
+
+# Multiple level process tree, parent process stays up
+cat >/tmp/test56-exit-cgroup.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> sleep
+sleep infinity &
+disown
+
+# process tree: systemd -> bash -> bash -> sleep
+((sleep infinity); true) &
+
+systemd-notify --ready
+
+# Run the stop/kill command
+\$1 &
+
+# process tree: systemd -> bash -> sleep
+sleep infinity
+EOF
+chmod +x /tmp/test56-exit-cgroup.sh
+
+# service should be stopped cleanly
+systemd-run --wait --unit=one -p Type=notify -p ExitType=cgroup \
+ /tmp/test56-exit-cgroup.sh 'systemctl stop one'
+
+# same thing with a truthy exec condition
+systemd-run --wait --unit=two -p Type=notify -p ExitType=cgroup \
+ -p ExecCondition=true \
+ /tmp/test56-exit-cgroup.sh 'systemctl stop two'
+
+# false exec condition: systemd-run should exit immediately with status code: 1
+(! systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \
+ -p ExecCondition=false \
+ /tmp/test56-exit-cgroup.sh)
+
+# service should exit uncleanly (main process exits with SIGKILL)
+(! systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \
+ /tmp/test56-exit-cgroup.sh 'systemctl kill --signal 9 four')
+
+
+# Multiple level process tree, parent process exits quickly
+cat >/tmp/test56-exit-cgroup-parentless.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> sleep
+sleep infinity &
+
+# process tree: systemd -> bash -> sleep
+((sleep infinity); true) &
+
+systemd-notify --ready
+
+# Run the stop/kill command after this bash process exits
+(sleep 1; \$1) &
+EOF
+chmod +x /tmp/test56-exit-cgroup-parentless.sh
+
+# service should be stopped cleanly
+systemd-run --wait --unit=five -p Type=notify -p ExitType=cgroup \
+ /tmp/test56-exit-cgroup-parentless.sh 'systemctl stop five'
+
+# service should still exit cleanly despite SIGKILL (the main process already exited cleanly)
+systemd-run --wait --unit=six -p Type=notify -p ExitType=cgroup \
+ /tmp/test56-exit-cgroup-parentless.sh 'systemctl kill --signal 9 six'
+
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-57-binds-to.service b/test/units/testsuite-57-binds-to.service
new file mode 100644
index 0000000..c542896
--- /dev/null
+++ b/test/units/testsuite-57-binds-to.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Unit with BindsTo=
+BindsTo=testsuite-57-bound-by.service
+After=testsuite-57-bound-by.service
+
+[Service]
+ExecStart=/bin/sleep infinity
+# --kill-who= (no 'm') to check that the short form is accepted
+ExecStopPost=systemctl kill --kill-who=main -sRTMIN+1 testsuite-57.service
diff --git a/test/units/testsuite-57-bound-by.service b/test/units/testsuite-57-bound-by.service
new file mode 100644
index 0000000..a2df5a1
--- /dev/null
+++ b/test/units/testsuite-57-bound-by.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Unit with BoundBy=
+
+[Service]
+ExecStart=/bin/sleep 0.7
diff --git a/test/units/testsuite-57-fail.service b/test/units/testsuite-57-fail.service
new file mode 100644
index 0000000..54d2330
--- /dev/null
+++ b/test/units/testsuite-57-fail.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Failing unit
+OnFailure=testsuite-57-uphold.service
+
+[Service]
+ExecStart=/bin/false
diff --git a/test/units/testsuite-57-prop-stop-one.service b/test/units/testsuite-57-prop-stop-one.service
new file mode 100644
index 0000000..a942b52
--- /dev/null
+++ b/test/units/testsuite-57-prop-stop-one.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Stop Propagation Receiver
+Wants=testsuite-57-prop-stop-two.service
+After=testsuite-57-prop-stop-two.service
+StopPropagatedFrom=testsuite-57-prop-stop-two.service
+
+[Service]
+ExecStart=/bin/sleep infinity
+ExecStopPost=systemctl kill --kill-whom=main -sUSR2 testsuite-57.service
diff --git a/test/units/testsuite-57-prop-stop-two.service b/test/units/testsuite-57-prop-stop-two.service
new file mode 100644
index 0000000..2bcd209
--- /dev/null
+++ b/test/units/testsuite-57-prop-stop-two.service
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Stop Propagation Sender
+
+[Service]
+ExecStart=/bin/sleep 1.5
diff --git a/test/units/testsuite-57-retry-fail.service b/test/units/testsuite-57-retry-fail.service
new file mode 100644
index 0000000..67f3407
--- /dev/null
+++ b/test/units/testsuite-57-retry-fail.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Failed Dependency Unit
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/sh -c "if [ -f /tmp/testsuite-57-retry-fail ]; then exit 0; else exit 1; fi"
+Restart=no
diff --git a/test/units/testsuite-57-retry-upheld.service b/test/units/testsuite-57-retry-upheld.service
new file mode 100644
index 0000000..2f718a6
--- /dev/null
+++ b/test/units/testsuite-57-retry-upheld.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upheld Unit
+Requires=testsuite-57-retry-fail.service
+After=testsuite-57-retry-fail.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/echo ok
diff --git a/test/units/testsuite-57-retry-uphold.service b/test/units/testsuite-57-retry-uphold.service
new file mode 100644
index 0000000..a01b131
--- /dev/null
+++ b/test/units/testsuite-57-retry-uphold.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upholding Unit
+Upholds=testsuite-57-retry-upheld.service
+
+[Service]
+ExecStart=/bin/sleep infinity
diff --git a/test/units/testsuite-57-short-lived.service b/test/units/testsuite-57-short-lived.service
new file mode 100644
index 0000000..cd8b514
--- /dev/null
+++ b/test/units/testsuite-57-short-lived.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Shortlived Unit
+StopWhenUnneeded=yes
+
+# Bump up the start limit logic, so that we can be restarted frequently enough
+StartLimitBurst=15
+StartLimitIntervalSec=1h
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/testsuite-57-short-lived.sh
diff --git a/test/units/testsuite-57-short-lived.sh b/test/units/testsuite-57-short-lived.sh
new file mode 100755
index 0000000..cd797a1
--- /dev/null
+++ b/test/units/testsuite-57-short-lived.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+
+if [ -f /tmp/testsuite-57.counter ] ; then
+ read -r counter < /tmp/testsuite-57.counter
+ counter=$(("$counter" + 1))
+else
+ counter=0
+fi
+
+echo "$counter" > /tmp/testsuite-57.counter
+
+if [ "$counter" -eq 5 ] ; then
+ systemctl kill --kill-whom=main -sUSR1 testsuite-57.service
+fi
+
+exec sleep 1.5
diff --git a/test/units/testsuite-57-success.service b/test/units/testsuite-57-success.service
new file mode 100644
index 0000000..ae1f46f
--- /dev/null
+++ b/test/units/testsuite-57-success.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Succeeding unit
+OnSuccess=testsuite-57-fail.service
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/testsuite-57-uphold.service b/test/units/testsuite-57-uphold.service
new file mode 100644
index 0000000..eba97f5
--- /dev/null
+++ b/test/units/testsuite-57-uphold.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upholding Unit
+Upholds=testsuite-57-short-lived.service
+
+[Service]
+ExecStart=/bin/sleep infinity
diff --git a/test/units/testsuite-57.service b/test/units/testsuite-57.service
new file mode 100644
index 0000000..d3ec955
--- /dev/null
+++ b/test/units/testsuite-57.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-57-ONSUCCESS-UPHOLD
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-57.sh b/test/units/testsuite-57.sh
new file mode 100755
index 0000000..24040c3
--- /dev/null
+++ b/test/units/testsuite-57.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+set -eux
+set -o pipefail
+
+systemd-analyze log-level debug
+systemd-analyze log-target journal
+
+# Idea is this:
+# 1. we start testsuite-57-success.service
+# 2. which through OnSuccess= starts testsuite-57-fail.service,
+# 3. which through OnFailure= starts testsuite-57-uphold.service,
+# 4. which through Uphold= starts/keeps testsuite-57-short-lived.service running,
+# 5. which will sleep 1s when invoked, and on the 5th invocation send us a SIGUSR1
+# 6. once we got that we finish cleanly
+
+sigusr1=0
+trap sigusr1=1 SIGUSR1
+
+systemctl start testsuite-57-success.service
+
+while [ "$sigusr1" -eq 0 ] ; do
+ sleep .5
+done
+
+systemctl stop testsuite-57-uphold.service
+
+# Idea is this:
+# 1. we start testsuite-57-retry-uphold.service
+# 2. which through Uphold= starts testsuite-57-retry-upheld.service
+# 3. which through Requires= starts testsuite-57-retry-fail.service
+# 4. which fails as /tmp/testsuite-57-retry-fail does not exist, so testsuite-57-retry-upheld.service
+# is no longer restarted
+# 5. we create /tmp/testsuite-57-retry-fail
+# 6. now testsuite-57-retry-upheld.service will be restarted since upheld, and its dependency will
+# be satisfied
+
+rm -f /tmp/testsuite-57-retry-fail
+systemctl start testsuite-57-retry-uphold.service
+
+while ! systemctl is-failed testsuite-57-retry-fail.service ; do
+ sleep .5
+done
+
+systemctl is-active testsuite-57-retry-upheld.service && { echo 'unexpected success'; exit 1; }
+
+touch /tmp/testsuite-57-retry-fail
+
+while ! systemctl is-active testsuite-57-retry-upheld.service ; do
+ sleep .5
+done
+
+systemctl stop testsuite-57-retry-uphold.service testsuite-57-retry-fail.service testsuite-57-retry-upheld.service
+
+# Idea is this:
+# 1. we start testsuite-57-prop-stop-one.service
+# 2. which through Wants=/After= pulls in testsuite-57-prop-stop-two.service as well
+# 3. testsuite-57-prop-stop-one.service then sleeps indefinitely
+# 4. testsuite-57-prop-stop-two.service sleeps a short time and exits
+# 5. the StopPropagatedFrom= dependency between the two should ensure *both* will exit as result
+# 6. an ExecStopPost= line on testsuite-57-prop-stop-one.service will send us a SIGUSR2
+# 7. once we got that we finish cleanly
+
+sigusr2=0
+trap sigusr2=1 SIGUSR2
+
+systemctl start testsuite-57-prop-stop-one.service
+
+while [ "$sigusr2" -eq 0 ] ; do
+ sleep .5
+done
+
+
+# Idea is this:
+# 1. we start testsuite-57-binds-to.service
+# 2. which through BindsTo=/After= pulls in testsuite-57-bound-by.service as well
+# 3. testsuite-57-bound-by.service suddenly dies
+# 4. testsuite-57-binds-to.service should then also be pulled down (it otherwise just hangs)
+# 6. an ExecStopPost= line on testsuite-57-binds-to.service will send us a SIGRTMIN1+1
+# 7. once we got that we finish cleanly
+
+sigrtmin1=0
+trap sigrtmin1=1 SIGRTMIN+1
+
+systemctl start testsuite-57-binds-to.service
+
+while [ "$sigrtmin1" -eq 0 ] ; do
+ sleep .5
+done
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-58.service b/test/units/testsuite-58.service
new file mode 100644
index 0000000..f843527
--- /dev/null
+++ b/test/units/testsuite-58.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-58-REPART
+
+[Service]
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-58.sh b/test/units/testsuite-58.sh
new file mode 100755
index 0000000..e369e58
--- /dev/null
+++ b/test/units/testsuite-58.sh
@@ -0,0 +1,937 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+if ! command -v systemd-repart >/dev/null; then
+ echo "no systemd-repart" >/skipped
+ exit 0
+fi
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+export SYSTEMD_LOG_LEVEL=debug
+export PAGER=cat
+
+seed=750b6cd5c4ae4012a15e7be3c29e6a47
+
+if ! systemd-detect-virt --quiet --container; then
+ udevadm control --log-level debug
+fi
+
+machine="$(uname -m)"
+if [ "${machine}" = "x86_64" ]; then
+ root_guid=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
+ root_uuid=60F33797-1D71-4DCB-AA6F-20564F036CD0
+ root_uuid2=73A4CCD2-EAF5-44DA-A366-F99188210FDC
+ usr_guid=8484680C-9521-48C6-9C11-B0720656F69E
+ usr_uuid=7E3369DD-D653-4513-ADF5-B993A9F20C16
+ architecture="x86-64"
+elif [ "${machine}" = "i386" ] || [ "${machine}" = "i686" ] || [ "${machine}" = "x86" ]; then
+ root_guid=44479540-F297-41B2-9AF7-D131D5F0458A
+ root_uuid=02B4253F-29A4-404E-8972-1669D3B03C87
+ root_uuid2=268E0FD3-B468-4806-A823-E533FE9BB9CC
+ usr_guid=75250D76-8CC6-458E-BD66-BD47CC81A812
+ usr_uuid=7B42FFB0-B0E1-4395-B20B-C78F4A571648
+ architecture="x86"
+elif [ "${machine}" = "aarch64" ] || [ "${machine}" = "aarch64_be" ] || [ "${machine}" = "armv8b" ] || [ "${machine}" = "armv8l" ]; then
+ root_guid=B921B045-1DF0-41C3-AF44-4C6F280D3FAE
+ root_uuid=055D0227-53A6-4033-85C3-9A5973EFF483
+ root_uuid2=F7DBBE48-8FD0-4833-8411-AA34E7C8E60A
+ usr_guid=B0E01050-EE5F-4390-949A-9101B17104E9
+ usr_uuid=FCE3C75E-D6A4-44C0-87F0-4C105183FB1F
+ architecture="arm64"
+elif [ "${machine}" = "arm" ]; then
+ root_guid=69DAD710-2CE4-4E3C-B16C-21A1D49ABED3
+ root_uuid=567DA89E-8DE2-4499-8D10-18F212DFF034
+ root_uuid2=813ECFE5-4C89-4193-8A52-437493F2F96E
+ usr_guid=7D0359A3-02B3-4F0A-865C-654403E70625
+ usr_uuid=71E93DC2-5073-42CB-8A84-A354E64D8966
+ architecture="arm"
+elif [ "${machine}" = "loongarch64" ]; then
+ root_guid=77055800-792C-4F94-B39A-98C91B762BB6
+ root_uuid=D8EFC2D2-0133-41E4-BDCB-3B9F4CFDDDE8
+ root_uuid2=36499F9E-0688-40C1-A746-EA8FD9543C56
+ usr_guid=E611C702-575C-4CBE-9A46-434FA0BF7E3F
+ usr_uuid=031FFA75-00BB-49B6-A70D-911D2D82A5B7
+ architecture="loongarch64"
+elif [ "${machine}" = "ia64" ]; then
+ root_guid=993D8D3D-F80E-4225-855A-9DAF8ED7EA97
+ root_uuid=DCF33449-0896-4EA9-BC24-7D58AEEF522D
+ root_uuid2=C2A6CAB7-ABEA-4FBA-8C48-CB4C52E6CA38
+ usr_guid=4301D2A6-4E3B-4B2A-BB94-9E0B2C4225EA
+ usr_uuid=BC2BCCE7-80D6-449A-85CC-637424CE5241
+ architecture="ia64"
+elif [ "${machine}" = "s390x" ]; then
+ root_guid=5EEAD9A9-FE09-4A1E-A1D7-520D00531306
+ root_uuid=7EBE0C85-E27E-48EC-B164-F4807606232E
+ root_uuid2=2A074E1C-2A19-4094-A0C2-24B1A5D52FCB
+ usr_guid=8A4F5770-50AA-4ED3-874A-99B710DB6FEA
+ usr_uuid=51171D30-35CF-4A49-B8B5-9478B9B796A5
+ architecture="s390x"
+elif [ "${machine}" = "ppc64le" ]; then
+ root_guid=C31C45E6-3F39-412E-80FB-4809C4980599
+ root_uuid=061E67A1-092F-482F-8150-B525D50D6654
+ root_uuid2=A6687CEF-4E4F-44E7-90B3-CDA52EA81739
+ usr_guid=15BB03AF-77E7-4D4A-B12B-C0D084F7491C
+ usr_uuid=C0D0823B-8040-4C7C-A629-026248E297FB
+ architecture="ppc64-le"
+else
+ echo "Unexpected uname -m: ${machine} in testsuite-58.sh, please fix me"
+ exit 1
+fi
+
+test_basic() {
+ local defs imgs output
+ local loop volume
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ # 1. create an empty image
+
+ systemd-repart --empty=create \
+ --size=1G \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118"
+
+ # 2. Testing with root, root2, home, and swap
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root
+EOF
+
+ ln -s root.conf "$defs/root2.conf"
+
+ cat >"$defs/home.conf" <<EOF
+[Partition]
+Type=home
+Label=home-first
+Label=home-always-too-long-xxxxxxxxxxxxxx-%v
+EOF
+
+ cat >"$defs/swap.conf" <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=64M
+PaddingMinBytes=92M
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"
+$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
+
+ # 3. Testing with root, root2, home, swap, and another partition
+
+ cat >"$defs/swap.conf" <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=64M
+EOF
+
+ cat >"$defs/extra.conf" <<EOF
+[Partition]
+Type=linux-generic
+Label=custom_label
+UUID=a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
+EOF
+
+ echo "Label=ignored_label" >>"$defs/home.conf"
+ echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf"
+
+ systemd-repart --definitions="$defs" \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"
+$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"
+$imgs/zzz5 : start= 1908696, size= 188416, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\""
+
+ # 4. Resizing to 2G
+
+ systemd-repart --definitions="$defs" \
+ --size=2G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 4194270
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"
+$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"
+$imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\""
+
+ # 5. Testing with root, root2, home, swap, another partition, and partition copy
+
+ dd if=/dev/urandom of="$imgs/block-copy" bs=4096 count=10240
+
+ cat >"$defs/extra2.conf" <<EOF
+[Partition]
+Type=linux-generic
+Label=block-copy
+UUID=2a1d97e1d0a346cca26eadc643926617
+CopyBlocks=$imgs/block-copy
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --size=3G \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 6291422
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"
+$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"
+$imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\"
+$imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=2A1D97E1-D0A3-46CC-A26E-ADC643926617, name=\"block-copy\""
+
+ cmp --bytes=$((4096*10240)) --ignore-initial=0:$((512*4194264)) "$imgs/block-copy" "$imgs/zzz"
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping encrypt tests in container."
+ return
+ fi
+
+ # 6. Testing Format=/Encrypt=/CopyFiles=
+
+ cat >"$defs/extra3.conf" <<EOF
+[Partition]
+Type=linux-generic
+Label=luks-format-copy
+UUID=7b93d1f2-595d-4ce3-b0b9-837fbd9e63b0
+Format=ext4
+Encrypt=yes
+CopyFiles=$defs:/def
+SizeMinBytes=48M
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --size=auto \
+ --dry-run=no \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$')
+
+ assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 6389726
+$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"
+$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\"
+$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"
+$imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\"
+$imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=2A1D97E1-D0A3-46CC-A26E-ADC643926617, name=\"block-copy\"
+$imgs/zzz7 : start= 6291416, size= 98304, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7B93D1F2-595D-4CE3-B0B9-837FBD9E63B0, name=\"luks-format-copy\""
+
+ loop="$(losetup -P --show --find "$imgs/zzz")"
+ udevadm wait --timeout 60 --settle "${loop:?}"
+
+ volume="test-repart-$RANDOM"
+
+ touch "$imgs/empty-password"
+ cryptsetup open --type=luks2 --key-file="$imgs/empty-password" "${loop}p7" "$volume"
+ mkdir -p "$imgs/mount"
+ mount -t ext4 "/dev/mapper/$volume" "$imgs/mount"
+ # Use deferred closing on the mapper and autoclear on the loop, so they are cleaned up on umount
+ cryptsetup close --deferred "$volume"
+ losetup -d "$loop"
+ diff -r "$imgs/mount/def" "$defs" >/dev/null
+ umount "$imgs/mount"
+}
+
+test_dropin() {
+ local defs imgs output
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=64M
+UUID=837c3d67-21b3-478e-be82-7e7f83bf96d3
+EOF
+
+ mkdir -p "$defs/root.conf.d"
+ cat >"$defs/root.conf.d/override1.conf" <<EOF
+[Partition]
+Label=label1
+SizeMaxBytes=32M
+EOF
+
+ cat >"$defs/root.conf.d/override2.conf" <<EOF
+[Partition]
+Label=label2
+EOF
+
+ output=$(systemd-repart --definitions="$defs" --empty=create --size=100M --json=pretty "$imgs/zzz")
+
+ diff <(echo "$output") - <<EOF
+[
+ {
+ "type" : "swap",
+ "label" : "label2",
+ "uuid" : "837c3d67-21b3-478e-be82-7e7f83bf96d3",
+ "file" : "root.conf",
+ "node" : "$imgs/zzz1",
+ "offset" : 1048576,
+ "old_size" : 0,
+ "raw_size" : 33554432,
+ "size" : "→ 32.0M",
+ "old_padding" : 0,
+ "raw_padding" : 0,
+ "padding" : "→ 0B",
+ "activity" : "create",
+ "drop-in_files" : [
+ "$defs/root.conf.d/override1.conf",
+ "$defs/root.conf.d/override2.conf"
+ ]
+ }
+]
+EOF
+}
+
+test_multiple_definitions() {
+ local defs imgs output
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ mkdir -p "$defs/1"
+
+ cat >"$defs/1/root1.conf" <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=32M
+UUID=7b93d1f2-595d-4ce3-b0b9-837fbd9e63b0
+Label=label1
+EOF
+
+ mkdir -p "$defs/2"
+
+ cat >"$defs/2/root2.conf" <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=32M
+UUID=837c3d67-21b3-478e-be82-7e7f83bf96d3
+Label=label2
+EOF
+
+ output=$(systemd-repart --definitions="$defs/1" --definitions="$defs/2" --empty=create --size=100M --json=pretty "$imgs/zzz")
+
+ diff <(echo "$output") - <<EOF
+[
+ {
+ "type" : "swap",
+ "label" : "label1",
+ "uuid" : "7b93d1f2-595d-4ce3-b0b9-837fbd9e63b0",
+ "file" : "root1.conf",
+ "node" : "$imgs/zzz1",
+ "offset" : 1048576,
+ "old_size" : 0,
+ "raw_size" : 33554432,
+ "size" : "→ 32.0M",
+ "old_padding" : 0,
+ "raw_padding" : 0,
+ "padding" : "→ 0B",
+ "activity" : "create"
+ },
+ {
+ "type" : "swap",
+ "label" : "label2",
+ "uuid" : "837c3d67-21b3-478e-be82-7e7f83bf96d3",
+ "file" : "root2.conf",
+ "node" : "$imgs/zzz2",
+ "offset" : 34603008,
+ "old_size" : 0,
+ "raw_size" : 33554432,
+ "size" : "→ 32.0M",
+ "old_padding" : 0,
+ "raw_padding" : 0,
+ "padding" : "→ 0B",
+ "activity" : "create"
+ }
+]
+EOF
+}
+
+test_copy_blocks() {
+ local defs imgs output
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping copy blocks tests in container."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ # First, create a disk image and verify its in order
+
+ cat >"$defs/esp.conf" <<EOF
+[Partition]
+Type=esp
+SizeMinBytes=10M
+Format=vfat
+EOF
+
+ cat >"$defs/usr.conf" <<EOF
+[Partition]
+Type=usr-${architecture}
+SizeMinBytes=10M
+Format=ext4
+ReadOnly=yes
+EOF
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+SizeMinBytes=10M
+Format=ext4
+MakeDirectories=/usr /efi
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --empty=create \
+ --size=auto \
+ --seed="$seed" \
+ "$imgs/zzz"
+
+ output=$(sfdisk --dump "$imgs/zzz")
+
+ assert_in "$imgs/zzz1 : start= 2048, size= 20480, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=39107B09-615D-48FB-BA37-C663885FCE67, name=\"esp\"" "$output"
+ assert_in "$imgs/zzz2 : start= 22528, size= 20480, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output"
+ assert_in "$imgs/zzz3 : start= 43008, size= 20480, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:60\"" "$output"
+
+ # Then, create another image with CopyBlocks=auto
+
+ cat >"$defs/esp.conf" <<EOF
+[Partition]
+Type=esp
+CopyBlocks=auto
+EOF
+
+ cat >"$defs/usr.conf" <<EOF
+[Partition]
+Type=usr-${architecture}
+ReadOnly=yes
+CopyBlocks=auto
+EOF
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+CopyBlocks=auto
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --empty=create \
+ --size=auto \
+ --seed="$seed" \
+ --image="$imgs/zzz" \
+ "$imgs/yyy"
+
+ cmp "$imgs/zzz" "$imgs/yyy"
+}
+
+test_unaligned_partition() {
+ local defs imgs output
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ # Operate on an image with unaligned partition.
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+EOF
+
+ truncate -s 10g "$imgs/unaligned"
+ sfdisk "$imgs/unaligned" <<EOF
+label: gpt
+
+start=2048, size=69044
+start=71092, size=3591848
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/unaligned"
+
+ output=$(sfdisk --dump "$imgs/unaligned")
+
+ assert_in "$imgs/unaligned1 : start= 2048, size= 69044," "$output"
+ assert_in "$imgs/unaligned2 : start= 71092, size= 3591848," "$output"
+ assert_in "$imgs/unaligned3 : start= 3662944, size= 17308536, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output"
+}
+
+test_issue_21817() {
+ local defs imgs output
+
+ # testcase for #21817
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ cat >"$defs/test.conf" <<EOF
+[Partition]
+Type=root
+EOF
+
+ truncate -s 100m "$imgs/21817.img"
+ sfdisk "$imgs/21817.img" <<EOF
+label: gpt
+
+size=50M, type=${root_guid}
+,
+EOF
+
+ systemd-repart --pretty=yes \
+ --definitions "$imgs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/21817.img"
+
+ output=$(sfdisk --dump "$imgs/21817.img")
+
+ assert_in "$imgs/21817.img1 : start= 2048, size= 102400, type=${root_guid}," "$output"
+ # Accept both unpadded (pre-v2.38 util-linux) and padded (v2.38+ util-linux) sizes
+ assert_in "$imgs/21817.img2 : start= 104448, size= (100319| 98304)," "$output"
+}
+
+test_issue_24553() {
+ local defs imgs output
+
+ # testcase for #24553
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root
+SizeMinBytes=10G
+SizeMaxBytes=120G
+EOF
+
+ cat >"$imgs/partscript" <<EOF
+label: gpt
+label-id: C9FFE979-A415-C449-B729-78C7AA664B10
+unit: sectors
+first-lba: 40
+
+start=40, size=524288, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=F2E89C8A-DC5D-4C4C-A29C-6CFB643B74FD, name="ESP System Partition"
+start=524328, size=14848000, type=${root_guid}, uuid=${root_uuid}, name="root-${architecture}"
+EOF
+
+ # 1. Operate on a small image compared with SizeMinBytes=.
+ truncate -s 8g "$imgs/zzz"
+ sfdisk "$imgs/zzz" <"$imgs/partscript"
+
+ # This should fail, but not trigger assertions.
+ assert_rc 1 systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
+
+ output=$(sfdisk --dump "$imgs/zzz")
+ assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
+
+ # 2. Operate on an larger image compared with SizeMinBytes=.
+ rm -f "$imgs/zzz"
+ truncate -s 12g "$imgs/zzz"
+ sfdisk "$imgs/zzz" <"$imgs/partscript"
+
+ # This should succeed.
+ systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
+
+ output=$(sfdisk --dump "$imgs/zzz")
+ assert_in "$imgs/zzz2 : start= 524328, size= 24641456, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
+
+ # 3. Multiple partitions with Priority= (small disk)
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root
+SizeMinBytes=10G
+SizeMaxBytes=120G
+Priority=100
+EOF
+
+ cat >"$defs/usr.conf" <<EOF
+[Partition]
+Type=usr
+SizeMinBytes=10M
+Priority=10
+EOF
+
+ rm -f "$imgs/zzz"
+ truncate -s 8g "$imgs/zzz"
+ sfdisk "$imgs/zzz" <"$imgs/partscript"
+
+ # This should also succeed, but root is not extended.
+ systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
+
+ output=$(sfdisk --dump "$imgs/zzz")
+ assert_in "$imgs/zzz2 : start= 524328, size= 14848000, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
+ assert_in "$imgs/zzz3 : start= 15372328, size= 1404848, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:59\"" "$output"
+
+ # 4. Multiple partitions with Priority= (large disk)
+ rm -f "$imgs/zzz"
+ truncate -s 12g "$imgs/zzz"
+ sfdisk "$imgs/zzz" <"$imgs/partscript"
+
+ # This should also succeed, and root is extended.
+ systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ "$imgs/zzz"
+
+ output=$(sfdisk --dump "$imgs/zzz")
+ assert_in "$imgs/zzz2 : start= 524328, size= 20971520, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\"" "$output"
+ assert_in "$imgs/zzz3 : start= 21495848, size= 3669936, type=${usr_guid}, uuid=${usr_uuid}, name=\"usr-${architecture}\", attrs=\"GUID:59\"" "$output"
+}
+
+test_zero_uuid() {
+ local defs imgs output
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ # Test image with zero UUID.
+
+ cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+UUID=null
+EOF
+
+ systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ "$imgs/zero"
+
+ output=$(sfdisk --dump "$imgs/zero")
+
+ assert_in "$imgs/zero1 : start= 2048, size= 20480, type=${root_guid}, uuid=00000000-0000-0000-0000-000000000000" "$output"
+}
+
+test_verity() {
+ local defs imgs output
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping verity test in container."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ cat >"$defs/verity-data.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+CopyFiles=${defs}
+Verity=data
+VerityMatchKey=root
+EOF
+
+ cat >"$defs/verity-hash.conf" <<EOF
+[Partition]
+Type=root-${architecture}-verity
+Verity=hash
+VerityMatchKey=root
+EOF
+
+ cat >"$defs/verity-sig.conf" <<EOF
+[Partition]
+Type=root-${architecture}-verity-sig
+Verity=signature
+VerityMatchKey=root
+EOF
+
+ # Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
+ cat >> "$defs/verity.openssl.cnf" <<EOF
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+C = DE
+ST = Test State
+L = Test Locality
+O = Org Name
+OU = Org Unit Name
+CN = Common Name
+emailAddress = test@email.com
+EOF
+
+ openssl req -config "$defs/verity.openssl.cnf" -new -x509 -newkey rsa:1024 -keyout "$defs/verity.key" -out "$defs/verity.crt" -days 365 -nodes
+
+ mkdir -p /run/verity.d
+ ln -s "$defs/verity.crt" /run/verity.d/ok.crt
+
+ output=$(systemd-repart --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ --json=pretty \
+ --private-key="$defs/verity.key" \
+ --certificate="$defs/verity.crt" \
+ "$imgs/verity")
+
+ roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
+
+ # Check that we can dissect, mount and unmount a repart verity image.
+
+ systemd-dissect "$imgs/verity" --root-hash "$roothash"
+ systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
+ systemd-dissect -U "$imgs/mnt"
+}
+
+testcase_free_area_calculation() {
+ local defs imgs output
+
+ if ! command -v mksquashfs >/dev/null; then
+ echo "Skipping free area calculation test without squashfs."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+ chmod a+rx "$defs"
+
+ # https://github.com/systemd/systemd/issues/28225
+ echo "*** free area calculation ***"
+
+ tee "$defs/00-ESP.conf" <<EOF
+[Partition]
+Type = esp
+Label = ESP
+Format = vfat
+
+SizeMinBytes = 128M
+SizeMaxBytes = 128M
+
+# Sufficient for testing
+CopyFiles = /etc:/
+EOF
+
+ tee "$defs/10-os.conf" <<EOF
+[Partition]
+Type = root-${architecture}
+Label = test
+Format = squashfs
+
+Minimize = best
+# Sufficient for testing
+CopyFiles = /etc/:/
+
+VerityMatchKey = os
+Verity = data
+EOF
+
+ tee "$defs/11-os-verity.conf" <<EOF
+[Partition]
+Type = root-${architecture}-verity
+Label = test
+
+Minimize = best
+
+VerityMatchKey = os
+Verity = hash
+EOF
+
+ # Set sector size for VFAT to 512 bytes because there will not be enough FAT clusters otherwise
+ output1=$(SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512" systemd-repart \
+ --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ --sector-size=4096 \
+ --defer-partitions=esp \
+ --json=pretty \
+ "$imgs/zzz")
+
+ # The second invocation
+ output2=$(SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512" systemd-repart \
+ --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=allow \
+ --size=auto \
+ --sector-size=4096 \
+ --defer-partitions=esp \
+ --json=pretty \
+ "$imgs/zzz")
+
+ diff -u <(echo "$output1" | grep -E "(offset|raw_size|raw_padding)") <(echo "$output2" | grep -E "(offset|raw_size|raw_padding)")
+}
+
+test_sector() {
+ local defs imgs output loop
+ local start size ratio
+ local sector="${1?}"
+
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping sector size tests in container."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+
+ cat > "$defs/a.conf" <<EOF
+[Partition]
+Type=root
+SizeMaxBytes=15M
+SizeMinBytes=15M
+EOF
+ cat > "$defs/b.conf" <<EOF
+[Partition]
+Type=linux-generic
+Weight=250
+EOF
+
+ cat > "$defs/c.conf" <<EOF
+[Partition]
+Type=linux-generic
+Weight=750
+EOF
+
+ truncate -s 100m "$imgs/$sector.img"
+ loop=$(losetup -b "$sector" -P --show -f "$imgs/$sector.img" )
+ udevadm wait --timeout 60 --settle "${loop:?}"
+ systemd-repart --pretty=yes \
+ --definitions="$defs" \
+ --seed="$seed" \
+ --empty=require \
+ --dry-run=no \
+ "$loop"
+
+ sfdisk --verify "$loop"
+ output=$(sfdisk --dump "$loop")
+ losetup -d "$loop"
+
+ ratio=$(( sector / 512 ))
+ start=$(( 2048 / ratio ))
+ size=$(( 30720 / ratio ))
+ assert_in "${loop}p1 : start= *${start}, size= *${size}, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output"
+ start=$(( start + size ))
+ size=$(( 42992 / ratio ))
+ assert_in "${loop}p2 : start= *${start}, size= *${size}, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=DF71F5E3-080A-4D16-824B-18591B881380, name=\"linux-generic\"" "$output"
+ start=$(( start + size ))
+ size=$(( 129000 / ratio ))
+ assert_in "${loop}p3 : start= *${start}, size= *${size}, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=DB081670-07AE-48CA-9F5E-813D5E40B976, name=\"linux-generic-2\"" "$output"
+}
+
+test_basic
+test_dropin
+test_multiple_definitions
+test_copy_blocks
+test_unaligned_partition
+test_issue_21817
+test_issue_24553
+test_zero_uuid
+test_verity
+
+# Valid block sizes on the Linux block layer are >= 512 and <= PAGE_SIZE, and
+# must be powers of 2. Which leaves exactly four different ones to test on
+# typical hardware
+test_sector 512
+test_sector 1024
+test_sector 2048
+test_sector 4096
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-59.service b/test/units/testsuite-59.service
new file mode 100644
index 0000000..f85cfab
--- /dev/null
+++ b/test/units/testsuite-59.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-59-RELOADING-RESTART
+
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
diff --git a/test/units/testsuite-59.sh b/test/units/testsuite-59.sh
new file mode 100755
index 0000000..83db053
--- /dev/null
+++ b/test/units/testsuite-59.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+fail () {
+ systemd-analyze log-level info
+ exit 1
+}
+
+# Wait for a service to enter a state within a timeout period, if it doesn't
+# enter the desired state within the timeout period then this function will
+# exit the test case with a non zero exit code.
+wait_on_state_or_fail () {
+ service=$1
+ expected_state=$2
+ timeout=$3
+
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ while [ "$state" != "$expected_state" ]; do
+ if [ "$timeout" = "0" ]; then
+ fail
+ fi
+ timeout=$((timeout - 1))
+ sleep 1
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ done
+}
+
+systemd-analyze log-level debug
+
+
+cat >/run/systemd/system/testservice-fail-59.service <<EOF
+[Unit]
+Description=TEST-59-RELOADING-RESTART Normal exit
+
+[Service]
+Type=notify
+ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+EOF
+
+cat >/run/systemd/system/testservice-fail-restart-59.service <<EOF
+[Unit]
+Description=TEST-59-RELOADING-RESTART Restart=on-failure
+
+[Service]
+Type=notify
+ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 1; exit 1"
+Restart=on-failure
+StartLimitBurst=1
+EOF
+
+
+cat >/run/systemd/system/testservice-abort-restart-59.service <<EOF
+[Unit]
+Description=TEST-59-RELOADING-RESTART Restart=on-abort
+
+[Service]
+Type=notify
+ExecStart=/bin/bash -c "systemd-notify --ready; systemd-notify RELOADING=1; sleep 5; exit 1"
+Restart=on-abort
+EOF
+
+systemctl daemon-reload
+
+# This service sends a RELOADING=1 message then exits before it sends a
+# READY=1. Ensure it enters failed state and does not linger in reloading
+# state.
+systemctl start testservice-fail-59.service
+wait_on_state_or_fail "testservice-fail-59.service" "failed" "30"
+
+# This service sends a RELOADING=1 message then exits before it sends a
+# READY=1. It should automatically restart on failure. Ensure it enters failed
+# state and does not linger in reloading state.
+systemctl start testservice-fail-restart-59.service
+wait_on_state_or_fail "testservice-fail-restart-59.service" "failed" "30"
+
+# This service sends a RELOADING=1 message then exits before it sends a
+# READY=1. It should automatically restart on abort. It will sleep for 5s
+# to allow us to send it a SIGABRT. Ensure the service enters the failed state
+# and does not linger in reloading state.
+systemctl start testservice-abort-restart-59.service
+systemctl --signal=SIGABRT kill testservice-abort-restart-59.service
+wait_on_state_or_fail "testservice-abort-restart-59.service" "failed" "30"
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-60.service b/test/units/testsuite-60.service
new file mode 100644
index 0000000..1a929e4
--- /dev/null
+++ b/test/units/testsuite-60.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-60-MOUNT-RATELIMIT
+
+[Service]
+Type=oneshot
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
diff --git a/test/units/testsuite-60.sh b/test/units/testsuite-60.sh
new file mode 100755
index 0000000..f390863
--- /dev/null
+++ b/test/units/testsuite-60.sh
@@ -0,0 +1,311 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+teardown_test_dependencies() (
+ set +eux
+
+ if mountpoint /tmp/deptest; then
+ umount /tmp/deptest
+ fi
+
+ if [[ -n "${LOOP}" ]]; then
+ losetup -d "${LOOP}" || :
+ fi
+ if [[ -n "${LOOP_0}" ]]; then
+ losetup -d "${LOOP_0}" || :
+ fi
+ if [[ -n "${LOOP_1}" ]]; then
+ losetup -d "${LOOP_1}" || :
+ fi
+
+ rm -f /tmp/testsuite-60-dependencies-0.img
+ rm -f /tmp/testsuite-60-dependencies-1.img
+
+ rm -f /run/systemd/system/tmp-deptest.mount
+ systemctl daemon-reload
+
+ return 0
+)
+
+setup_loop() {
+ truncate -s 30m "/tmp/testsuite-60-dependencies-${1?}.img"
+ sfdisk --wipe=always "/tmp/testsuite-60-dependencies-${1?}.img" <<EOF
+label:gpt
+
+name="loop${1?}-part1"
+EOF
+ LOOP=$(losetup -P --show -f "/tmp/testsuite-60-dependencies-${1?}.img")
+ udevadm wait --settle --timeout=10 "${LOOP}"
+ udevadm lock --device="${LOOP}" mkfs.ext4 -L "partname${1?}-1" "${LOOP}p1"
+}
+
+check_dependencies() {
+ local escaped_0 escaped_1 after
+
+ escaped_0=$(systemd-escape -p "${LOOP_0}p1")
+ escaped_1=$(systemd-escape -p "${LOOP_1}p1")
+
+ if [[ -f /run/systemd/system/tmp-deptest.mount ]]; then
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_not_in "local-fs-pre.target" "$after"
+ assert_in "remote-fs-pre.target" "$after"
+ assert_in "network.target" "$after"
+ fi
+
+ # mount LOOP_0
+ mount -t ext4 "${LOOP_0}p1" /tmp/deptest
+ sleep 1
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_in "local-fs-pre.target" "$after"
+ assert_not_in "remote-fs-pre.target" "$after"
+ assert_not_in "network.target" "$after"
+ assert_in "${escaped_0}.device" "$after"
+ assert_in "blockdev@${escaped_0}.target" "$after"
+ assert_not_in "${escaped_1}.device" "$after"
+ assert_not_in "blockdev@${escaped_1}.target" "$after"
+ umount /tmp/deptest
+
+ if [[ -f /run/systemd/system/tmp-deptest.mount ]]; then
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_not_in "local-fs-pre.target" "$after"
+ assert_in "remote-fs-pre.target" "$after"
+ assert_in "network.target" "$after"
+ fi
+
+ # mount LOOP_1 (using fake _netdev option)
+ mount -t ext4 -o _netdev "${LOOP_1}p1" /tmp/deptest
+ sleep 1
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_not_in "local-fs-pre.target" "$after"
+ assert_in "remote-fs-pre.target" "$after"
+ assert_in "network.target" "$after"
+ assert_not_in "${escaped_0}.device" "$after"
+ assert_not_in "blockdev@${escaped_0}.target" "$after"
+ assert_in "${escaped_1}.device" "$after"
+ assert_in "blockdev@${escaped_1}.target" "$after"
+ umount /tmp/deptest
+
+ if [[ -f /run/systemd/system/tmp-deptest.mount ]]; then
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_not_in "local-fs-pre.target" "$after"
+ assert_in "remote-fs-pre.target" "$after"
+ assert_in "network.target" "$after"
+ fi
+
+ # mount tmpfs
+ mount -t tmpfs tmpfs /tmp/deptest
+ sleep 1
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_in "local-fs-pre.target" "$after"
+ assert_not_in "remote-fs-pre.target" "$after"
+ assert_not_in "network.target" "$after"
+ assert_not_in "${escaped_0}.device" "$after"
+ assert_not_in "blockdev@${escaped_0}.target" "$after"
+ assert_not_in "${escaped_1}.device" "$after"
+ assert_not_in "blockdev@${escaped_1}.target" "$after"
+ umount /tmp/deptest
+
+ if [[ -f /run/systemd/system/tmp-deptest.mount ]]; then
+ after=$(systemctl show --property=After --value tmp-deptest.mount)
+ assert_not_in "local-fs-pre.target" "$after"
+ assert_in "remote-fs-pre.target" "$after"
+ assert_in "network.target" "$after"
+ fi
+}
+
+test_dependencies() {
+ if systemd-detect-virt --quiet --container; then
+ echo "Skipping test_dependencies in container"
+ return
+ fi
+
+ trap teardown_test_dependencies RETURN
+
+ setup_loop 0
+ LOOP_0="${LOOP}"
+ LOOP=
+ setup_loop 1
+ LOOP_1="${LOOP}"
+ LOOP=
+
+ mkdir -p /tmp/deptest
+
+ # without .mount file
+ check_dependencies
+
+ # create .mount file
+ mkdir -p /run/systemd/system
+ cat >/run/systemd/system/tmp-deptest.mount <<EOF
+[Mount]
+Where=/tmp/deptest
+What=192.168.0.1:/tmp/mnt
+Type=nfs
+EOF
+ systemctl daemon-reload
+
+ # with .mount file
+ check_dependencies
+}
+
+test_issue_20329() {
+ local tmpdir unit
+ tmpdir="$(mktemp -d)"
+ unit=$(systemd-escape --suffix mount --path "$tmpdir")
+
+ # Set up test mount unit
+ cat > /run/systemd/system/"$unit" <<EOF
+[Mount]
+What=tmpfs
+Where=$tmpdir
+Type=tmpfs
+Options=defaults,nofail
+EOF
+
+ # Start the unit
+ systemctl daemon-reload
+ systemctl start "$unit"
+
+ [[ "$(systemctl show --property SubState --value "$unit")" = "mounted" ]] || {
+ echo >&2 "Test mount \"$unit\" unit isn't mounted"
+ return 1
+ }
+ mountpoint -q "$tmpdir"
+
+ trap 'systemctl stop $unit' RETURN
+
+ # Trigger the mount ratelimiting
+ cd "$(mktemp -d)"
+ mkdir foo
+ for _ in {1..50}; do
+ mount --bind foo foo
+ umount foo
+ done
+
+ # Unmount the test mount and start it immediately again via systemd
+ umount "$tmpdir"
+ systemctl start "$unit"
+
+ # Make sure it is seen as mounted by systemd and it actually is mounted
+ [[ "$(systemctl show --property SubState --value "$unit")" = "mounted" ]] || {
+ echo >&2 "Test mount \"$unit\" unit isn't in \"mounted\" state"
+ return 1
+ }
+
+ mountpoint -q "$tmpdir" || {
+ echo >&2 "Test mount \"$unit\" is in \"mounted\" state, actually is not mounted"
+ return 1
+ }
+}
+
+test_issue_23796() {
+ local mount_path mount_mytmpfs
+
+ mount_path="$(command -v mount 2>/dev/null)"
+ mount_mytmpfs="${mount_path/\/bin/\/sbin}.mytmpfs"
+ cat >"$mount_mytmpfs" <<EOF
+#!/bin/bash
+sleep ".\$RANDOM"
+exec -- $mount_path -t tmpfs tmpfs "\$2"
+EOF
+ chmod +x "$mount_mytmpfs"
+
+ mkdir -p /run/systemd/system
+ cat >/run/systemd/system/tmp-hoge.mount <<EOF
+[Mount]
+What=mytmpfs
+Where=/tmp/hoge
+Type=mytmpfs
+EOF
+
+ # shellcheck disable=SC2064
+ trap "rm -f /run/systemd/system/tmp-hoge.mount '$mount_mytmpfs'" RETURN
+
+ for _ in {1..10}; do
+ systemctl --no-block start tmp-hoge.mount
+ sleep ".$RANDOM"
+ systemctl daemon-reexec
+
+ sleep 1
+
+ if [[ "$(systemctl is-failed tmp-hoge.mount)" == "failed" ]] || \
+ journalctl -u tmp-hoge.mount -q --grep "but there is no mount"; then
+ exit 1
+ fi
+
+ systemctl stop tmp-hoge.mount
+ done
+}
+
+: >/failed
+
+systemd-analyze log-level debug
+systemd-analyze log-target journal
+
+NUM_DIRS=20
+
+# make sure we can handle mounts at very long paths such that mount unit name must be hashed to fall within our unit name limit
+LONGPATH="$(printf "/$(printf "x%0.s" {1..255})%0.s" {1..7})"
+LONGMNT="$(systemd-escape --suffix=mount --path "$LONGPATH")"
+TS="$(date '+%H:%M:%S')"
+
+mkdir -p "$LONGPATH"
+mount -t tmpfs tmpfs "$LONGPATH"
+systemctl daemon-reload
+
+# check that unit is active(mounted)
+systemctl --no-pager show -p SubState --value "$LONGPATH" | grep -q mounted
+
+# check that relevant part of journal doesn't contain any errors related to unit
+[ "$(journalctl -b --since="$TS" --priority=err | grep -c "$LONGMNT")" = "0" ]
+
+# check that we can successfully stop the mount unit
+systemctl stop "$LONGPATH"
+rm -rf "$LONGPATH"
+
+# mount/unmount enough times to trigger the /proc/self/mountinfo parsing rate limiting
+
+for ((i = 0; i < NUM_DIRS; i++)); do
+ mkdir "/tmp/meow${i}"
+done
+
+TS="$(date '+%H:%M:%S')"
+
+for ((i = 0; i < NUM_DIRS; i++)); do
+ mount -t tmpfs tmpfs "/tmp/meow${i}"
+done
+
+systemctl daemon-reload
+systemctl list-units -t mount tmp-meow* | grep -q tmp-meow
+
+for ((i = 0; i < NUM_DIRS; i++)); do
+ umount "/tmp/meow${i}"
+done
+
+# Figure out if we have entered the rate limit state.
+# If the infra is slow we might not enter the rate limit state; in that case skip the exit check.
+if timeout 2m bash -c "while ! journalctl -u init.scope --since=$TS | grep -q '(mount-monitor-dispatch) entered rate limit'; do sleep 1; done"; then
+ timeout 2m bash -c "while ! journalctl -u init.scope --since=$TS | grep -q '(mount-monitor-dispatch) left rate limit'; do sleep 1; done"
+fi
+
+# Verify that the mount units are always cleaned up at the end.
+# Give some time for units to settle so we don't race between exiting the rate limit state and cleaning up the units.
+timeout 2m bash -c 'while systemctl list-units -t mount tmp-meow* | grep -q tmp-meow; do systemctl daemon-reload; sleep 10; done'
+
+# test for issue #19983 and #23552.
+test_dependencies
+
+# test that handling of mount start jobs is delayed when /proc/self/mouninfo monitor is rate limited
+test_issue_20329
+
+# test for reexecuting with background mount job
+test_issue_23796
+
+systemd-analyze log-level info
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-61.service b/test/units/testsuite-61.service
new file mode 100644
index 0000000..568960c
--- /dev/null
+++ b/test/units/testsuite-61.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-61-UNITTESTS-QEMU
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-61.sh b/test/units/testsuite-61.sh
new file mode 100755
index 0000000..748e24a
--- /dev/null
+++ b/test/units/testsuite-61.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+TESTS_GLOB="test-loop-block"
+# shellcheck source=test/units/testsuite-02.sh
+. "$(dirname "$0")/testsuite-02.sh"
+
+exit 0
diff --git a/test/units/testsuite-62-1.service b/test/units/testsuite-62-1.service
new file mode 100644
index 0000000..fa3a7e7
--- /dev/null
+++ b/test/units/testsuite-62-1.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES-all-pings-work
+[Service]
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+RestrictNetworkInterfaces=
+Type=oneshot
diff --git a/test/units/testsuite-62-2.service b/test/units/testsuite-62-2.service
new file mode 100644
index 0000000..b83362d
--- /dev/null
+++ b/test/units/testsuite-62-2.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES-allow-list
+[Service]
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+RestrictNetworkInterfaces=veth0
+RestrictNetworkInterfaces=veth1
+Type=oneshot
diff --git a/test/units/testsuite-62-3.service b/test/units/testsuite-62-3.service
new file mode 100644
index 0000000..b6c8e7a
--- /dev/null
+++ b/test/units/testsuite-62-3.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES-deny-list
+[Service]
+ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+RestrictNetworkInterfaces=~veth0
+RestrictNetworkInterfaces=~veth1
+Type=oneshot
diff --git a/test/units/testsuite-62-4.service b/test/units/testsuite-62-4.service
new file mode 100644
index 0000000..053e6d2
--- /dev/null
+++ b/test/units/testsuite-62-4.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES-empty-assignment
+[Service]
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.9'
+RestrictNetworkInterfaces=veth0
+RestrictNetworkInterfaces=
+Type=oneshot
diff --git a/test/units/testsuite-62-5.service b/test/units/testsuite-62-5.service
new file mode 100644
index 0000000..a8f268d
--- /dev/null
+++ b/test/units/testsuite-62-5.service
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES-invert-assignment
+[Service]
+ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.1'
+ExecStart=/bin/sh -c 'ping -c 1 -W 0.2 192.168.113.5'
+ExecStart=/bin/sh -c '! ping -c 1 -W 0.2 192.168.113.9'
+RestrictNetworkInterfaces=veth0
+RestrictNetworkInterfaces=veth0 veth1
+RestrictNetworkInterfaces=~veth0
+Type=oneshot
diff --git a/test/units/testsuite-62.service b/test/units/testsuite-62.service
new file mode 100644
index 0000000..5c3f94d
--- /dev/null
+++ b/test/units/testsuite-62.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-62-RESTRICT-IFACES
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-62.sh b/test/units/testsuite-62.sh
new file mode 100755
index 0000000..04b79b7
--- /dev/null
+++ b/test/units/testsuite-62.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+setup() {
+ systemd-analyze log-level debug
+
+ for i in {0..3};
+ do
+ ip netns del "ns${i}" || true
+ ip link del "veth${i}" || true
+ ip netns add "ns${i}"
+ ip link add "veth${i}" type veth peer name "veth${i}_"
+ ip link set "veth${i}_" netns "ns${i}"
+ ip -n "ns${i}" link set dev "veth${i}_" up
+ ip -n "ns${i}" link set dev lo up
+ ip -n "ns${i}" addr add "192.168.113."$((4*i+1))/30 dev "veth${i}_"
+ ip link set dev "veth${i}" up
+ ip addr add "192.168.113."$((4*i+2))/30 dev "veth${i}"
+ done
+}
+
+teardown() {
+ set +e
+
+ for i in {0..3};
+ do
+ ip netns del "ns${i}"
+ ip link del "veth${i}"
+ done
+
+ systemd-analyze log-level info
+}
+
+KERNEL_VERSION="$(uname -r)"
+KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
+KERNEL_MINOR="${KERNEL_VERSION#"$KERNEL_MAJOR".}"
+KERNEL_MINOR="${KERNEL_MINOR%%.*}"
+
+MAJOR_REQUIRED=5
+MINOR_REQUIRED=7
+
+if [[ "$KERNEL_MAJOR" -lt $MAJOR_REQUIRED || ("$KERNEL_MAJOR" -eq $MAJOR_REQUIRED && "$KERNEL_MINOR" -lt $MINOR_REQUIRED) ]]; then
+ echo "kernel is not 5.7+" >>/skipped
+ exit 0
+fi
+
+if systemctl --version | grep -q -F -- "-BPF_FRAMEWORK"; then
+ echo "bpf-framework is disabled" >>/skipped
+ exit 0
+fi
+
+trap teardown EXIT
+setup
+
+systemctl start --wait testsuite-62-1.service
+systemctl start --wait testsuite-62-2.service
+systemctl start --wait testsuite-62-3.service
+systemctl start --wait testsuite-62-4.service
+systemctl start --wait testsuite-62-5.service
+
+echo OK > /testok
+
+exit 0
diff --git a/test/units/testsuite-63.service b/test/units/testsuite-63.service
new file mode 100644
index 0000000..483c6a8
--- /dev/null
+++ b/test/units/testsuite-63.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-63-PATH
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-63.sh b/test/units/testsuite-63.sh
new file mode 100755
index 0000000..59a7b32
--- /dev/null
+++ b/test/units/testsuite-63.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+systemctl log-level debug
+
+# Test that a path unit continuously triggering a service that fails condition checks eventually fails with
+# the trigger-limit-hit error.
+rm -f /tmp/nonexistent
+systemctl start test63.path
+touch /tmp/test63
+
+# Make sure systemd has sufficient time to hit the trigger limit for test63.path.
+# shellcheck disable=SC2016
+timeout 30 bash -c 'while ! test "$(systemctl show test63.path -P ActiveState)" = failed; do sleep .2; done'
+test "$(systemctl show test63.service -P ActiveState)" = inactive
+test "$(systemctl show test63.service -P Result)" = success
+test "$(systemctl show test63.path -P Result)" = trigger-limit-hit
+
+# Test that starting the service manually doesn't affect the path unit.
+rm -f /tmp/test63
+systemctl reset-failed
+systemctl start test63.path
+systemctl start test63.service
+test "$(systemctl show test63.service -P ActiveState)" = inactive
+test "$(systemctl show test63.service -P Result)" = success
+test "$(systemctl show test63.path -P ActiveState)" = active
+test "$(systemctl show test63.path -P Result)" = success
+
+# Test that glob matching works too, with $TRIGGER_PATH
+systemctl start test63-glob.path
+touch /tmp/test63-glob-foo
+timeout 60 bash -c 'while ! systemctl -q is-active test63-glob.service; do sleep .2; done'
+test "$(systemctl show test63-glob.service -P ActiveState)" = active
+test "$(systemctl show test63-glob.service -P Result)" = success
+
+test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[["trigger_unit","test63-glob.path"],["trigger_path","/tmp/test63-glob-foo"]]}'
+
+systemctl stop test63-glob.path test63-glob.service
+
+test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[]}'
+
+# tests for issue https://github.com/systemd/systemd/issues/24577#issuecomment-1522628906
+rm -f /tmp/hoge
+systemctl start test63-issue-24577.path
+systemctl status -n 0 test63-issue-24577.path
+systemctl status -n 0 test63-issue-24577.service || :
+systemctl list-jobs
+output=$(systemctl list-jobs --no-legend)
+assert_not_in "test63-issue-24577.service" "$output"
+assert_not_in "test63-issue-24577-dep.service" "$output"
+
+touch /tmp/hoge
+systemctl status -n 0 test63-issue-24577.path
+systemctl status -n 0 test63-issue-24577.service || :
+systemctl list-jobs
+output=$(systemctl list-jobs --no-legend)
+assert_in "test63-issue-24577.service" "$output"
+assert_in "test63-issue-24577-dep.service" "$output"
+
+# even if the service is stopped, it will be soon retriggered.
+systemctl stop test63-issue-24577.service
+systemctl status -n 0 test63-issue-24577.path
+systemctl status -n 0 test63-issue-24577.service || :
+systemctl list-jobs
+output=$(systemctl list-jobs --no-legend)
+assert_in "test63-issue-24577.service" "$output"
+assert_in "test63-issue-24577-dep.service" "$output"
+
+rm -f /tmp/hoge
+systemctl stop test63-issue-24577.service
+systemctl status -n 0 test63-issue-24577.path
+systemctl status -n 0 test63-issue-24577.service || :
+systemctl list-jobs
+output=$(systemctl list-jobs --no-legend)
+assert_not_in "test63-issue-24577.service" "$output"
+assert_in "test63-issue-24577-dep.service" "$output"
+
+systemctl log-level info
+
+echo OK >/testok
diff --git a/test/units/testsuite-64.service b/test/units/testsuite-64.service
new file mode 100644
index 0000000..f75a3d7
--- /dev/null
+++ b/test/units/testsuite-64.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-64-UDEV
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-64.sh b/test/units/testsuite-64.sh
new file mode 100755
index 0000000..e9f352c
--- /dev/null
+++ b/test/units/testsuite-64.sh
@@ -0,0 +1,1017 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# vi: ts=4 sw=4 tw=0 et:
+
+set -eux
+set -o pipefail
+
+# Check if all symlinks under /dev/disk/ are valid
+# shellcheck disable=SC2120
+helper_check_device_symlinks() {(
+ set +x
+
+ local dev link path paths target
+
+ [[ $# -gt 0 ]] && paths=("$@") || paths=("/dev/disk" "/dev/mapper")
+
+ # Check if all given paths are valid
+ for path in "${paths[@]}"; do
+ if ! test -e "$path"; then
+ echo >&2 "Path '$path' doesn't exist"
+ return 1
+ fi
+ done
+
+ while read -r link; do
+ target="$(readlink -f "$link")"
+ # Both checks should do virtually the same thing, but check both to be
+ # on the safe side
+ if [[ ! -e "$link" || ! -e "$target" ]]; then
+ echo >&2 "ERROR: symlink '$link' points to '$target' which doesn't exist"
+ return 1
+ fi
+
+ # Check if the symlink points to the correct device in /dev
+ dev="/dev/$(udevadm info -q name "$link")"
+ if [[ "$target" != "$dev" ]]; then
+ echo >&2 "ERROR: symlink '$link' points to '$target' but '$dev' was expected"
+ return 1
+ fi
+ done < <(find "${paths[@]}" -type l)
+)}
+
+helper_check_udev_watch() {(
+ set +x
+
+ local link target id dev
+
+ while read -r link; do
+ target="$(readlink "$link")"
+ if [[ ! -L "/run/udev/watch/$target" ]]; then
+ echo >&2 "ERROR: symlink /run/udev/watch/$target does not exist"
+ return 1
+ fi
+ if [[ "$(readlink "/run/udev/watch/$target")" != "$(basename "$link")" ]]; then
+ echo >&2 "ERROR: symlink target of /run/udev/watch/$target is inconsistent with $link"
+ return 1
+ fi
+
+ if [[ "$target" =~ ^[0-9]+$ ]]; then
+ # $link is ID -> wd
+ id="$(basename "$link")"
+ else
+ # $link is wd -> ID
+ id="$target"
+ fi
+
+ if [[ "${id:0:1}" == "b" ]]; then
+ dev="/dev/block/${id:1}"
+ elif [[ "${id:0:1}" == "c" ]]; then
+ dev="/dev/char/${id:1}"
+ else
+ echo >&2 "ERROR: unexpected device ID '$id'"
+ return 1
+ fi
+
+ if [[ ! -e "$dev" ]]; then
+ echo >&2 "ERROR: device '$dev' corresponding to symlink '$link' does not exist"
+ return 1
+ fi
+ done < <(find /run/udev/watch -type l)
+)}
+
+check_device_unit() {(
+ set +x
+
+ local log_level link links path syspath unit
+
+ log_level="${1?}"
+ path="${2?}"
+ unit=$(systemd-escape --path --suffix=device "$path")
+
+ syspath=$(systemctl show --value --property SysFSPath "$unit" 2>/dev/null)
+ if [[ -z "$syspath" ]]; then
+ [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit not found."
+ return 1
+ fi
+
+ if [[ ! -L "$path" ]]; then
+ if [[ ! -d "$syspath" ]]; then
+ [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not exist."
+ return 1
+ fi
+ return 0
+ fi
+
+ if [[ ! -b "$path" && ! -c "$path" ]]; then
+ [[ "$log_level" == 1 ]] && echo >&2 "ERROR: invalid file type $path"
+ return 1
+ fi
+
+ read -r -a links < <(udevadm info -q symlink "$syspath" 2>/dev/null)
+ for link in "${links[@]}"; do
+ if [[ "/dev/$link" == "$path" ]]; then # DEVLINKS= given by -q symlink are relative to /dev
+ return 0
+ fi
+ done
+
+ read -r -a links < <(udevadm info "$syspath" | sed -ne '/SYSTEMD_ALIAS=/ { s/^E: SYSTEMD_ALIAS=//; p }' 2>/dev/null)
+ for link in "${links[@]}"; do
+ if [[ "$link" == "$path" ]]; then # SYSTEMD_ALIAS= are absolute
+ return 0
+ fi
+ done
+
+ [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not have the corresponding DEVLINKS or SYSTEMD_ALIAS."
+ return 1
+)}
+
+check_device_units() {(
+ set +x
+
+ local log_level path paths
+
+ log_level="${1?}"
+ shift
+ paths=("$@")
+
+ for path in "${paths[@]}"; do
+ if ! check_device_unit "$log_level" "$path"; then
+ return 1
+ fi
+ done
+
+ while read -r unit _; do
+ path=$(systemd-escape --path --unescape "$unit")
+ if ! check_device_unit "$log_level" "$path"; then
+ return 1
+ fi
+ done < <(systemctl list-units --all --type=device --no-legend dev-* | awk '$1 !~ /dev-tty.+/ { print $1 }' | sed -e 's/\.device$//')
+
+ return 0
+)}
+
+helper_check_device_units() {(
+ set +x
+
+ local i
+
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
+ if check_device_units 0 "$@"; then
+ return 0
+ fi
+ done
+
+ check_device_units 1 "$@"
+)}
+
+testcase_megasas2_basic() {
+ lsblk -S
+ [[ "$(lsblk --scsi --noheadings | wc -l)" -ge 128 ]]
+}
+
+testcase_nvme_basic() {
+ lsblk --noheadings | grep "^nvme"
+ [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 28 ]]
+}
+
+testcase_nvme_subsystem() {
+ local expected_symlinks=(
+ # Controller(s)
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17
+ # Shared namespaces
+ /dev/disk/by-path/pci-*-nvme-16
+ /dev/disk/by-path/pci-*-nvme-17
+ )
+
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+}
+
+testcase_virtio_scsi_identically_named_partitions() {
+ local num
+
+ if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then
+ num=$((4 * 4))
+ else
+ num=$((16 * 8))
+ fi
+
+ lsblk --noheadings -a -o NAME,PARTLABEL
+ [[ "$(lsblk --noheadings -a -o NAME,PARTLABEL | grep -c "Hello world")" -eq "$num" ]]
+}
+
+testcase_multipath_basic_failover() {
+ local dmpath i path wwid
+
+ # Configure multipath
+ cat >/etc/multipath.conf <<\EOF
+defaults {
+ # Use /dev/mapper/$WWN paths instead of /dev/mapper/mpathX
+ user_friendly_names no
+ find_multipaths yes
+ enable_foreign "^$"
+}
+
+blacklist_exceptions {
+ property "(SCSI_IDENT_|ID_WWN)"
+}
+
+blacklist {
+}
+EOF
+ modprobe -v dm_multipath
+ systemctl start multipathd.service
+ systemctl status multipathd.service
+ multipath -ll
+ udevadm settle
+ ls -l /dev/disk/by-id/
+
+ for i in {0..15}; do
+ wwid="deaddeadbeef$(printf "%.4d" "$i")"
+ path="/dev/disk/by-id/wwn-0x$wwid"
+ dmpath="$(readlink -f "$path")"
+
+ lsblk "$path"
+ multipath -C "$dmpath"
+ # We should have 4 active paths for each multipath device
+ [[ "$(multipath -l "$path" | grep -c running)" -eq 4 ]]
+ done
+
+ # Test failover (with the first multipath device that has a partitioned disk)
+ echo "${FUNCNAME[0]}: test failover"
+ local device expected link mpoint part
+ local -a devices
+ mkdir -p /mnt
+ mpoint="$(mktemp -d /mnt/mpathXXX)"
+ wwid="deaddeadbeef0000"
+ path="/dev/disk/by-id/wwn-0x$wwid"
+
+ # All following symlinks should exists and should be valid
+ local -a part_links=(
+ "/dev/disk/by-id/wwn-0x$wwid-part2"
+ "/dev/disk/by-partlabel/failover_part"
+ "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000"
+ "/dev/disk/by-label/failover_vol"
+ "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111"
+ )
+ udevadm wait --settle --timeout=30 "${part_links[@]}"
+ helper_check_device_units "${part_links[@]}"
+
+ # Choose a random symlink to the failover data partition each time, for
+ # a better coverage
+ part="${part_links[$RANDOM % ${#part_links[@]}]}"
+
+ # Get all devices attached to a specific multipath device (in H:C:T:L format)
+ # and sort them in a random order, so we cut off different paths each time
+ mapfile -t devices < <(multipath -l "$path" | grep -Eo '[0-9]+:[0-9]+:[0-9]+:[0-9]+' | sort -R)
+ if [[ "${#devices[@]}" -ne 4 ]]; then
+ echo "Expected 4 devices attached to WWID=$wwid, got ${#devices[@]} instead"
+ return 1
+ fi
+ # Drop the last path from the array, since we want to leave at least one path active
+ unset "devices[3]"
+ # Mount the first multipath partition, write some data we can check later,
+ # and then disconnect the remaining paths one by one while checking if we
+ # can still read/write from the mount
+ mount -t ext4 "$part" "$mpoint"
+ expected=0
+ echo -n "$expected" >"$mpoint/test"
+ # Sanity check we actually wrote what we wanted
+ [[ "$(<"$mpoint/test")" == "$expected" ]]
+
+ for device in "${devices[@]}"; do
+ echo offline >"/sys/class/scsi_device/$device/device/state"
+ [[ "$(<"$mpoint/test")" == "$expected" ]]
+ expected="$((expected + 1))"
+ echo -n "$expected" >"$mpoint/test"
+
+ # Make sure all symlinks are still valid
+ udevadm wait --settle --timeout=30 "${part_links[@]}"
+ helper_check_device_units "${part_links[@]}"
+ done
+
+ multipath -l "$path"
+ # Three paths should be now marked as 'offline' and one as 'running'
+ [[ "$(multipath -l "$path" | grep -c offline)" -eq 3 ]]
+ [[ "$(multipath -l "$path" | grep -c running)" -eq 1 ]]
+
+ umount "$mpoint"
+ rm -fr "$mpoint"
+}
+
+testcase_simultaneous_events() {
+ local disk expected i iterations key link num_part part partscript rule target timeout
+ local -a devices symlinks
+ local -A running
+
+ if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then
+ num_part=2
+ iterations=10
+ timeout=240
+ else
+ num_part=10
+ iterations=100
+ timeout=30
+ fi
+
+ for disk in {0..9}; do
+ link="/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_deadbeeftest${disk}"
+ target="$(readlink -f "$link")"
+ if [[ ! -b "$target" ]]; then
+ echo "ERROR: failed to find the test SCSI block device $link"
+ return 1
+ fi
+
+ devices+=("$target")
+ done
+
+ for ((part = 1; part <= num_part; part++)); do
+ symlinks+=(
+ "/dev/disk/by-partlabel/test${part}"
+ )
+ done
+
+ partscript="$(mktemp)"
+
+ cat >"$partscript" <<EOF
+$(for ((part = 1; part <= num_part; part++)); do printf 'name="test%d", size=2M\n' "$part"; done)
+EOF
+
+ rule=/run/udev/rules.d/50-test.rules
+ mkdir -p "${rule%/*}"
+ cat >"$rule" <<EOF
+SUBSYSTEM=="block", KERNEL=="${devices[4]##*/}*|${devices[5]##*/}*", OPTIONS="link_priority=10"
+EOF
+
+ udevadm control --reload
+
+ # Delete the partitions, immediately recreate them, wait for udev to settle
+ # down, and then check if we have any dangling symlinks in /dev/disk/. Rinse
+ # and repeat.
+ #
+ # On unpatched udev versions the delete-recreate cycle may trigger a race
+ # leading to dead symlinks in /dev/disk/
+ for ((i = 1; i <= iterations; i++)); do
+ for disk in {0..9}; do
+ if ((disk % 2 == i % 2)); then
+ udevadm lock --device="${devices[$disk]}" sfdisk -q --delete "${devices[$disk]}" &
+ else
+ udevadm lock --device="${devices[$disk]}" sfdisk -q -X gpt "${devices[$disk]}" <"$partscript" &
+ fi
+ running[$disk]=$!
+ done
+
+ for key in "${!running[@]}"; do
+ wait "${running[$key]}"
+ unset "running[$key]"
+ done
+
+ if ((i % 10 <= 1)); then
+ udevadm wait --settle --timeout="$timeout" "${devices[@]}" "${symlinks[@]}"
+ helper_check_device_symlinks
+ helper_check_udev_watch
+ for ((part = 1; part <= num_part; part++)); do
+ link="/dev/disk/by-partlabel/test${part}"
+ target="$(readlink -f "$link")"
+ if ((i % 2 == 0)); then
+ expected="${devices[5]}$part"
+ else
+ expected="${devices[4]}$part"
+ fi
+ if [[ "$target" != "$expected" ]]; then
+ echo >&2 "ERROR: symlink '/dev/disk/by-partlabel/test${part}' points to '$target' but '$expected' was expected"
+ return 1
+ fi
+ done
+ fi
+ done
+
+ helper_check_device_units
+ rm -f "$rule" "$partscript"
+
+ udevadm control --reload
+}
+
+testcase_lvm_basic() {
+ local i iterations partitions part timeout
+ local vgroup="MyTestGroup$RANDOM"
+ local devices=(
+ /dev/disk/by-id/ata-foobar_deadbeeflvm{0..3}
+ )
+
+ if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then
+ timeout=180
+ else
+ timeout=30
+ fi
+ # Make sure all the necessary soon-to-be-LVM devices exist
+ ls -l "${devices[@]}"
+
+ # Add all test devices into a volume group, create two logical volumes,
+ # and check if necessary symlinks exist (and are valid)
+ lvm pvcreate -y "${devices[@]}"
+ lvm pvs
+ lvm vgcreate "$vgroup" -y "${devices[@]}"
+ lvm vgs
+ lvm vgchange -ay "$vgroup"
+ lvm lvcreate -y -L 4M "$vgroup" -n mypart1
+ lvm lvcreate -y -L 8M "$vgroup" -n mypart2
+ lvm lvs
+ udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2"
+ mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1"
+ udevadm wait --settle --timeout="$timeout" "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+
+ # Rename partitions (see issue #24518)
+ lvm lvrename "/dev/$vgroup/mypart1" renamed1
+ lvm lvrename "/dev/$vgroup/mypart2" renamed2
+ udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2"
+ udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/renamed1" "/dev/$vgroup/renamed2"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+
+ # Rename them back
+ lvm lvrename "/dev/$vgroup/renamed1" mypart1
+ lvm lvrename "/dev/$vgroup/renamed2" mypart2
+ udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/renamed1" "/dev/$vgroup/renamed2"
+ udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+
+ # Disable the VG and check symlinks...
+ lvm vgchange -an "$vgroup"
+ udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk"
+ helper_check_device_units
+
+ # reenable the VG and check the symlinks again if all LVs are properly activated
+ lvm vgchange -ay "$vgroup"
+ udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+
+ # Same as above, but now with more "stress"
+ if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then
+ iterations=10
+ else
+ iterations=50
+ fi
+
+ for ((i = 1; i <= iterations; i++)); do
+ lvm vgchange -an "$vgroup"
+ lvm vgchange -ay "$vgroup"
+
+ if ((i % 5 == 0)); then
+ udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+ fi
+ done
+
+ # Remove the first LV
+ lvm lvremove -y "$vgroup/mypart1"
+ udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/mypart1"
+ udevadm wait --timeout=0 "/dev/$vgroup/mypart2"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+
+ # Create & remove LVs in a loop, i.e. with more "stress"
+ if [[ -v ASAN_OPTIONS ]]; then
+ iterations=8
+ partitions=16
+ elif [[ "$(systemd-detect-virt -v)" == "qemu" ]]; then
+ iterations=8
+ partitions=8
+ else
+ iterations=16
+ partitions=16
+ fi
+
+ for ((i = 1; i <= iterations; i++)); do
+ # 1) Create some logical volumes
+ for ((part = 0; part < partitions; part++)); do
+ lvm lvcreate -y -L 4M "$vgroup" -n "looppart$part"
+ done
+
+ # 2) Immediately remove them
+ lvm lvremove -y $(seq -f "$vgroup/looppart%g" 0 "$((partitions - 1))")
+
+ # 3) On every 4th iteration settle udev and check if all partitions are
+ # indeed gone, and if all symlinks are still valid
+ if ((i % 4 == 0)); then
+ for ((part = 0; part < partitions; part++)); do
+ udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/looppart$part"
+ done
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+ fi
+ done
+}
+
+testcase_btrfs_basic() {
+ local dev_stub i label mpoint uuid
+ local devices=(
+ /dev/disk/by-id/ata-foobar_deadbeefbtrfs{0..3}
+ )
+
+ ls -l "${devices[@]}"
+
+ echo "Single device: default settings"
+ uuid="deadbeef-dead-dead-beef-000000000000"
+ label="btrfs_root"
+ udevadm lock --device="${devices[0]}" mkfs.btrfs -f -L "$label" -U "$uuid" "${devices[0]}"
+ udevadm wait --settle --timeout=30 "${devices[0]}" "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label"
+ btrfs filesystem show
+ helper_check_device_symlinks
+ helper_check_device_units
+
+ echo "Multiple devices: using partitions, data: single, metadata: raid1"
+ uuid="deadbeef-dead-dead-beef-000000000001"
+ label="btrfs_mpart"
+ udevadm lock --device="${devices[0]}" sfdisk --wipe=always "${devices[0]}" <<EOF
+label: gpt
+
+name="diskpart1", size=85M
+name="diskpart2", size=85M
+name="diskpart3", size=85M
+name="diskpart4", size=85M
+EOF
+ udevadm wait --settle --timeout=30 /dev/disk/by-partlabel/diskpart{1..4}
+ udevadm lock --device="${devices[0]}" mkfs.btrfs -f -d single -m raid1 -L "$label" -U "$uuid" /dev/disk/by-partlabel/diskpart{1..4}
+ udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label"
+ btrfs filesystem show
+ helper_check_device_symlinks
+ helper_check_device_units
+ wipefs -a -f "${devices[0]}"
+ udevadm wait --settle --timeout=30 --removed /dev/disk/by-partlabel/diskpart{1..4}
+
+ echo "Multiple devices: using disks, data: raid10, metadata: raid10, mixed mode"
+ uuid="deadbeef-dead-dead-beef-000000000002"
+ label="btrfs_mdisk"
+ udevadm lock \
+ --device=/dev/disk/by-id/ata-foobar_deadbeefbtrfs0 \
+ --device=/dev/disk/by-id/ata-foobar_deadbeefbtrfs1 \
+ --device=/dev/disk/by-id/ata-foobar_deadbeefbtrfs2 \
+ --device=/dev/disk/by-id/ata-foobar_deadbeefbtrfs3 \
+ mkfs.btrfs -f -M -d raid10 -m raid10 -L "$label" -U "$uuid" "${devices[@]}"
+ udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label"
+ btrfs filesystem show
+ helper_check_device_symlinks
+ helper_check_device_units
+
+ echo "Multiple devices: using LUKS encrypted disks, data: raid1, metadata: raid1, mixed mode"
+ uuid="deadbeef-dead-dead-beef-000000000003"
+ label="btrfs_mencdisk"
+ mpoint="/btrfs_enc$RANDOM"
+ mkdir "$mpoint"
+ # Create a key-file
+ dd if=/dev/urandom of=/etc/btrfs_keyfile bs=64 count=1 iflag=fullblock
+ chmod 0600 /etc/btrfs_keyfile
+ # Encrypt each device and add it to /etc/crypttab, so it can be mounted
+ # automagically later
+ : >/etc/crypttab
+ for ((i = 0; i < ${#devices[@]}; i++)); do
+ # Intentionally use weaker cipher-related settings, since we don't care
+ # about security here as it's a throwaway LUKS partition
+ cryptsetup luksFormat -q \
+ --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \
+ --uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile
+ udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" "/dev/disk/by-label/encdisk$i"
+ # Add the device into /etc/crypttab, reload systemd, and then activate
+ # the device so we can create a filesystem on it later
+ echo "encbtrfs$i UUID=deadbeef-dead-dead-beef-11111111111$i /etc/btrfs_keyfile luks" >>/etc/crypttab
+ systemctl daemon-reload
+ systemctl start "systemd-cryptsetup@encbtrfs$i"
+ done
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Check if we have all necessary DM devices
+ ls -l /dev/mapper/encbtrfs{0..3}
+ # Create a multi-device btrfs filesystem on the LUKS devices
+ udevadm lock \
+ --device=/dev/mapper/encbtrfs0 \
+ --device=/dev/mapper/encbtrfs1 \
+ --device=/dev/mapper/encbtrfs2 \
+ --device=/dev/mapper/encbtrfs3 \
+ mkfs.btrfs -f -M -d raid1 -m raid1 -L "$label" -U "$uuid" /dev/mapper/encbtrfs{0..3}
+ udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label"
+ btrfs filesystem show
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Mount it and write some data to it we can compare later
+ mount -t btrfs /dev/mapper/encbtrfs0 "$mpoint"
+ echo "hello there" >"$mpoint/test"
+ # "Deconstruct" the btrfs device and check if we're in a sane state (symlink-wise)
+ umount "$mpoint"
+ systemctl stop systemd-cryptsetup@encbtrfs{0..3}
+ udevadm wait --settle --timeout=30 --removed "/dev/disk/by-uuid/$uuid"
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Add the mount point to /etc/fstab and check if the device can be put together
+ # automagically. The source device is the DM name of the first LUKS device
+ # (from /etc/crypttab). We have to specify all LUKS devices manually, as
+ # registering the necessary devices is usually initrd's job (via btrfs device scan)
+ dev_stub="/dev/mapper/encbtrfs"
+ echo "/dev/mapper/encbtrfs0 $mpoint btrfs device=${dev_stub}0,device=${dev_stub}1,device=${dev_stub}2,device=${dev_stub}3 0 2" >>/etc/fstab
+ # Tell systemd about the new mount
+ systemctl daemon-reload
+ # Restart cryptsetup.target to trigger autounlock of partitions in /etc/crypttab
+ systemctl restart cryptsetup.target
+ # Start the corresponding mount unit and check if the btrfs device was reconstructed
+ # correctly
+ systemctl start "${mpoint##*/}.mount"
+ udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label"
+ btrfs filesystem show
+ helper_check_device_symlinks
+ helper_check_device_units
+ grep "hello there" "$mpoint/test"
+ # Cleanup
+ systemctl stop "${mpoint##*/}.mount"
+ systemctl stop systemd-cryptsetup@encbtrfs{0..3}
+ sed -i "/${mpoint##*/}/d" /etc/fstab
+ : >/etc/crypttab
+ rm -fr "$mpoint"
+ systemctl daemon-reload
+ udevadm settle
+}
+
+testcase_iscsi_lvm() {
+ local dev i label link lun_id mpoint target_name uuid
+ local target_ip="127.0.0.1"
+ local target_port="3260"
+ local vgroup="iscsi_lvm$RANDOM"
+ local expected_symlinks=()
+ local devices=(
+ /dev/disk/by-id/ata-foobar_deadbeefiscsi{0..3}
+ )
+
+ ls -l "${devices[@]}"
+
+ # Start the target daemon
+ systemctl start tgtd
+ systemctl status tgtd
+
+ echo "iSCSI LUNs backed by devices"
+ # See RFC3721 and RFC7143
+ target_name="iqn.2021-09.com.example:iscsi.test"
+ # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each
+ # backed by a device
+ tgtadm --lld iscsi --op new --mode target --tid=1 --targetname "$target_name"
+ for ((i = 0; i < ${#devices[@]}; i++)); do
+ # lun-0 is reserved by iSCSI
+ lun_id="$((i + 1))"
+ tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun "$lun_id" -b "${devices[$i]}"
+ tgtadm --lld iscsi --op update --mode logicalunit --tid 1 --lun "$lun_id"
+ expected_symlinks+=(
+ "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$lun_id"
+ )
+ done
+ tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL
+ # Configure the iSCSI initiator
+ iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Cleanup
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout
+ tgtadm --lld iscsi --op delete --mode target --tid=1
+
+ echo "iSCSI LUNs backed by files + LVM"
+ # Note: we use files here to "trick" LVM the disks are indeed on a different
+ # host, so it doesn't automagically detect another path to the backing
+ # device once we disconnect the iSCSI devices
+ target_name="iqn.2021-09.com.example:iscsi.lvm.test"
+ mpoint="$(mktemp -d /iscsi_storeXXX)"
+ expected_symlinks=()
+ # Use the first device as it's configured with larger capacity
+ mkfs.ext4 -L iscsi_store "${devices[0]}"
+ udevadm wait --settle --timeout=30 "${devices[0]}"
+ mount "${devices[0]}" "$mpoint"
+ for i in {1..4}; do
+ dd if=/dev/zero of="$mpoint/lun$i.img" bs=1M count=32
+ done
+ # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each
+ # backed by a file
+ tgtadm --lld iscsi --op new --mode target --tid=2 --targetname "$target_name"
+ # lun-0 is reserved by iSCSI
+ for i in {1..4}; do
+ tgtadm --lld iscsi --op new --mode logicalunit --tid 2 --lun "$i" -b "$mpoint/lun$i.img"
+ tgtadm --lld iscsi --op update --mode logicalunit --tid 2 --lun "$i"
+ expected_symlinks+=(
+ "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$i"
+ )
+ done
+ tgtadm --lld iscsi --op bind --mode target --tid 2 -I ALL
+ # Configure the iSCSI initiator
+ iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Add all iSCSI devices into a LVM volume group, create two logical volumes,
+ # and check if necessary symlinks exist (and are valid)
+ lvm pvcreate -y "${expected_symlinks[@]}"
+ lvm pvs
+ lvm vgcreate "$vgroup" -y "${expected_symlinks[@]}"
+ lvm vgs
+ lvm vgchange -ay "$vgroup"
+ lvm lvcreate -y -L 4M "$vgroup" -n mypart1
+ lvm lvcreate -y -L 8M "$vgroup" -n mypart2
+ lvm lvs
+ udevadm wait --settle --timeout=30 "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2"
+ mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1"
+ udevadm wait --settle --timeout=30 "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+ # Disconnect the iSCSI devices and check all the symlinks
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout
+ # "Reset" the DM state, since we yanked the backing storage from under the LVM,
+ # so the currently active VGs/LVs are invalid
+ dmsetup remove_all --deferred
+ # The LVM and iSCSI related symlinks should be gone
+ udevadm wait --settle --timeout=30 --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" "${expected_symlinks[@]}"
+ helper_check_device_symlinks "/dev/disk"
+ helper_check_device_units
+ # Reconnect the iSCSI devices and check if everything get detected correctly
+ iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1"
+ helper_check_device_symlinks "/dev/disk" "/dev/$vgroup"
+ helper_check_device_units
+ # Cleanup
+ iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout
+ tgtadm --lld iscsi --op delete --mode target --tid=2
+ umount "$mpoint"
+ rm -rf "$mpoint"
+}
+
+testcase_long_sysfs_path() {
+ local cursor link logfile mpoint
+ local expected_symlinks=(
+ "/dev/disk/by-label/data_vol"
+ "/dev/disk/by-label/swap_vol"
+ "/dev/disk/by-partlabel/test_swap"
+ "/dev/disk/by-partlabel/test_part"
+ "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000"
+ "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111"
+ "/dev/disk/by-uuid/deadbeef-dead-dead-beef-222222222222"
+ )
+
+ # Create a cursor file to skip messages generated by udevd in initrd, as it
+ # might not be the same up-to-date version as we currently run (hence generating
+ # messages we check for later and making the test fail)
+ cursor="$(mktemp)"
+ journalctl --cursor-file="${cursor:?}" -n0 -q
+
+ # Make sure the test device is connected and show its "wonderful" path
+ stat /sys/block/vda
+ readlink -f /sys/block/vda/dev
+
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+
+ # Try to mount the data partition manually (using its label)
+ mpoint="$(mktemp -d /logsysfsXXX)"
+ mount LABEL=data_vol "$mpoint"
+ touch "$mpoint/test"
+ umount "$mpoint"
+ # Do the same, but with UUID and using fstab
+ echo "UUID=deadbeef-dead-dead-beef-222222222222 $mpoint ext4 defaults 0 0" >>/etc/fstab
+ systemctl daemon-reload
+ mount "$mpoint"
+ timeout 30 bash -c "while ! systemctl -q is-active '$mpoint'; do sleep .2; done"
+ test -e "$mpoint/test"
+ umount "$mpoint"
+
+ # Test out the swap partition
+ swapon -v -L swap_vol
+ swapoff -v -L swap_vol
+
+ udevadm settle
+
+ logfile="$(mktemp)"
+ # Check state of affairs after https://github.com/systemd/systemd/pull/22759
+ # Note: can't use `--cursor-file` here, since we don't want to update the cursor
+ # after using it
+ [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic -p info --grep "Device path.*vda.?' too long to fit into unit name" | wc -l)" -eq 0 ]]
+ [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic --grep "Unit name .*vda.?\.device\" too long, falling back to hashed unit name" | wc -l)" -gt 0 ]]
+ # Check if the respective "hashed" units exist and are active (plugged)
+ systemctl status --no-pager "$(readlink -f /sys/block/vda/vda1)"
+ systemctl status --no-pager "$(readlink -f /sys/block/vda/vda2)"
+ # Make sure we don't unnecessarily spam the log
+ { journalctl -b -q --no-pager -o short-monotonic -p info --grep "/sys/devices/.+/vda[0-9]?" _PID=1 + UNIT=systemd-udevd.service || :;} | tee "$logfile"
+ [[ "$(wc -l <"$logfile")" -lt 10 ]]
+
+ : >/etc/fstab
+ rm -fr "${cursor:?}" "${logfile:?}" "${mpoint:?}"
+}
+
+testcase_mdadm_basic() {
+ local i part_name raid_name raid_dev uuid
+ local expected_symlinks=()
+ local devices=(
+ /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..4}
+ )
+
+ ls -l "${devices[@]}"
+
+ echo "Mirror raid (RAID 1)"
+ raid_name="mdmirror"
+ raid_dev="/dev/md/$raid_name"
+ part_name="${raid_name}_part"
+ uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000001"
+ expected_symlinks=(
+ "$raid_dev"
+ "/dev/disk/by-id/md-name-H:$raid_name"
+ "/dev/disk/by-id/md-uuid-$uuid"
+ "/dev/disk/by-label/$part_name" # ext4 partition
+ )
+ # Create a simple RAID 1 with an ext4 filesystem
+ echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..1} -v -f --level=1 --raid-devices=2
+ udevadm wait --settle --timeout=30 "$raid_dev"
+ mkfs.ext4 -L "$part_name" "$raid_dev"
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ for i in {0..9}; do
+ echo "Disassemble - reassemble loop, iteration #$i"
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ mdadm --assemble "$raid_dev" --name "$raid_name" -v
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ done
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Cleanup
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+
+ echo "Parity raid (RAID 5)"
+ raid_name="mdparity"
+ raid_dev="/dev/md/$raid_name"
+ part_name="${raid_name}_part"
+ uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000101"
+ expected_symlinks=(
+ "$raid_dev"
+ "/dev/disk/by-id/md-name-H:$raid_name"
+ "/dev/disk/by-id/md-uuid-$uuid"
+ "/dev/disk/by-label/$part_name" # ext4 partition
+ )
+ # Create a simple RAID 5 with an ext4 filesystem
+ echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..2} -v -f --level=5 --raid-devices=3
+ udevadm wait --settle --timeout=30 "$raid_dev"
+ mkfs.ext4 -L "$part_name" "$raid_dev"
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ for i in {0..9}; do
+ echo "Disassemble - reassemble loop, iteration #$i"
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ mdadm --assemble "$raid_dev" --name "$raid_name" -v
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ done
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Cleanup
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ helper_check_device_units
+
+ echo "Mirror + parity raid (RAID 10) + multiple partitions"
+ raid_name="mdmirpar"
+ raid_dev="/dev/md/$raid_name"
+ part_name="${raid_name}_part"
+ uuid="aaaaaaaa:bbbbbbbb:cccccccc:00001010"
+ expected_symlinks=(
+ "$raid_dev"
+ "/dev/disk/by-id/md-name-H:$raid_name"
+ "/dev/disk/by-id/md-uuid-$uuid"
+ "/dev/disk/by-label/$part_name" # ext4 partition
+ # Partitions
+ "${raid_dev}1"
+ "${raid_dev}2"
+ "${raid_dev}3"
+ "/dev/disk/by-id/md-name-H:$raid_name-part1"
+ "/dev/disk/by-id/md-name-H:$raid_name-part2"
+ "/dev/disk/by-id/md-name-H:$raid_name-part3"
+ "/dev/disk/by-id/md-uuid-$uuid-part1"
+ "/dev/disk/by-id/md-uuid-$uuid-part2"
+ "/dev/disk/by-id/md-uuid-$uuid-part3"
+ )
+ # Create a simple RAID 10 with an ext4 filesystem
+ echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..3} -v -f --level=10 --raid-devices=4
+ udevadm wait --settle --timeout=30 "$raid_dev"
+ # Partition the raid device
+ # Here, 'udevadm lock' is meaningless, as udevd does not lock MD devices.
+ sfdisk --wipe=always "$raid_dev" <<EOF
+label: gpt
+
+uuid="deadbeef-dead-dead-beef-111111111111", name="mdpart1", size=8M
+uuid="deadbeef-dead-dead-beef-222222222222", name="mdpart2", size=32M
+uuid="deadbeef-dead-dead-beef-333333333333", name="mdpart3", size=16M
+EOF
+ udevadm wait --settle --timeout=30 "/dev/disk/by-id/md-uuid-$uuid-part2"
+ mkfs.ext4 -L "$part_name" "/dev/disk/by-id/md-uuid-$uuid-part2"
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ for i in {0..9}; do
+ echo "Disassemble - reassemble loop, iteration #$i"
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ mdadm --assemble "$raid_dev" --name "$raid_name" -v
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ done
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Cleanup
+ mdadm -v --stop "$raid_dev"
+ # Check if all expected symlinks were removed after the cleanup
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ helper_check_device_units
+}
+
+testcase_mdadm_lvm() {
+ local part_name raid_name raid_dev uuid vgroup
+ local expected_symlinks=()
+ local devices=(
+ /dev/disk/by-id/ata-foobar_deadbeefmdadmlvm{0..4}
+ )
+
+ ls -l "${devices[@]}"
+
+ raid_name="mdlvm"
+ raid_dev="/dev/md/$raid_name"
+ part_name="${raid_name}_part"
+ vgroup="${raid_name}_vg"
+ uuid="aaaaaaaa:bbbbbbbb:ffffffff:00001010"
+ expected_symlinks=(
+ "$raid_dev"
+ "/dev/$vgroup/mypart1" # LVM partition
+ "/dev/$vgroup/mypart2" # LVM partition
+ "/dev/disk/by-id/md-name-H:$raid_name"
+ "/dev/disk/by-id/md-uuid-$uuid"
+ "/dev/disk/by-label/$part_name" # ext4 partition
+ )
+ # Create a RAID 10 with LVM + ext4
+ echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadmlvm{0..3} -v -f --level=10 --raid-devices=4
+ udevadm wait --settle --timeout=30 "$raid_dev"
+ # Create an LVM on the MD
+ lvm pvcreate -y "$raid_dev"
+ lvm pvs
+ lvm vgcreate "$vgroup" -y "$raid_dev"
+ lvm vgs
+ lvm vgchange -ay "$vgroup"
+ lvm lvcreate -y -L 4M "$vgroup" -n mypart1
+ lvm lvcreate -y -L 8M "$vgroup" -n mypart2
+ lvm lvs
+ udevadm wait --settle --timeout=30 "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2"
+ mkfs.ext4 -L "$part_name" "/dev/$vgroup/mypart2"
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ # Disassemble the array
+ lvm vgchange -an "$vgroup"
+ mdadm -v --stop "$raid_dev"
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Reassemble it and check if all required symlinks exist
+ mdadm --assemble "$raid_dev" --name "$raid_name" -v
+ udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"
+ helper_check_device_symlinks
+ helper_check_device_units
+ # Cleanup
+ lvm vgchange -an "$vgroup"
+ mdadm -v --stop "$raid_dev"
+ # Check if all expected symlinks were removed after the cleanup
+ udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}"
+ helper_check_device_units
+}
+
+: >/failed
+
+udevadm settle
+udevadm control --log-level debug
+lsblk -a
+
+echo "Check if all symlinks under /dev/disk/ are valid (pre-test)"
+helper_check_device_symlinks
+
+# TEST_FUNCTION_NAME is passed on the kernel command line via systemd.setenv=
+# in the respective test.sh file
+if ! command -v "${TEST_FUNCTION_NAME:?}"; then
+ echo >&2 "Missing verification handler for test case '$TEST_FUNCTION_NAME'"
+ exit 1
+fi
+
+echo "TEST_FUNCTION_NAME=$TEST_FUNCTION_NAME"
+"$TEST_FUNCTION_NAME"
+udevadm settle
+
+echo "Check if all symlinks under /dev/disk/ are valid (post-test)"
+helper_check_device_symlinks
+
+udevadm control --log-level info
+
+systemctl status systemd-udevd
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-65.service b/test/units/testsuite-65.service
new file mode 100644
index 0000000..3610baf
--- /dev/null
+++ b/test/units/testsuite-65.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-65-ANALYZE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh
new file mode 100755
index 0000000..0654e7b
--- /dev/null
+++ b/test/units/testsuite-65.sh
@@ -0,0 +1,883 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+runas() {
+ declare userid=$1
+ shift
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
+}
+
+systemctl log-level debug
+export SYSTEMD_LOG_LEVEL=debug
+
+# Sanity checks
+#
+# We can't really test time, blame, critical-chain and plot verbs here, as
+# the testsuite service is a part of the boot transaction, so let's assume
+# they fail
+systemd-analyze || :
+systemd-analyze time || :
+systemd-analyze blame || :
+systemd-analyze critical-chain || :
+systemd-analyze plot >/dev/null || :
+# legacy/deprecated options (moved to systemctl, but still usable from analyze)
+systemd-analyze log-level
+systemd-analyze log-level "$(systemctl log-level)"
+systemd-analyze get-log-level
+systemd-analyze set-log-level "$(systemctl log-level)"
+systemd-analyze log-target
+systemd-analyze log-target "$(systemctl log-target)"
+systemd-analyze get-log-target
+systemd-analyze set-log-target "$(systemctl log-target)"
+systemd-analyze service-watchdogs
+systemd-analyze service-watchdogs "$(systemctl service-watchdogs)"
+# dot
+systemd-analyze dot >/dev/null
+systemd-analyze dot systemd-journald.service >/dev/null
+systemd-analyze dot systemd-journald.service systemd-logind.service >/dev/null
+systemd-analyze dot --from-pattern="*" --from-pattern="*.service" systemd-journald.service >/dev/null
+systemd-analyze dot --to-pattern="*" --to-pattern="*.service" systemd-journald.service >/dev/null
+systemd-analyze dot --from-pattern="*.service" --to-pattern="*.service" systemd-journald.service >/dev/null
+systemd-analyze dot --order systemd-journald.service systemd-logind.service >/dev/null
+systemd-analyze dot --require systemd-journald.service systemd-logind.service >/dev/null
+systemd-analyze dot "systemd-*.service" >/dev/null
+(! systemd-analyze dot systemd-journald.service systemd-logind.service "*" bbb ccc)
+# dump
+# this should be rate limited to 10 calls in 10 minutes for unprivileged callers
+for _ in {1..10}; do
+ runas testuser systemd-analyze dump systemd-journald.service >/dev/null
+done
+(! runas testuser systemd-analyze dump >/dev/null)
+# still limited after a reload
+systemctl daemon-reload
+(! runas testuser systemd-analyze dump >/dev/null)
+# and a re-exec
+systemctl daemon-reexec
+(! runas testuser systemd-analyze dump >/dev/null)
+# privileged call, so should not be rate limited
+for _ in {1..10}; do
+ systemd-analyze dump systemd-journald.service >/dev/null
+done
+systemd-analyze dump >/dev/null
+systemd-analyze dump "*" >/dev/null
+systemd-analyze dump "*.socket" >/dev/null
+systemd-analyze dump "*.socket" "*.service" aaaaaaa ... >/dev/null
+systemd-analyze dump systemd-journald.service >/dev/null
+(! systemd-analyze dump "")
+# unit-files
+systemd-analyze unit-files >/dev/null
+systemd-analyze unit-files systemd-journald.service >/dev/null
+systemd-analyze unit-files "*" >/dev/null
+systemd-analyze unit-files "*" aaaaaa "*.service" "*.target" >/dev/null
+systemd-analyze unit-files --user >/dev/null
+systemd-analyze unit-files --user "*" aaaaaa "*.service" "*.target" >/dev/null
+# unit-paths
+systemd-analyze unit-paths
+systemd-analyze unit-paths --user
+systemd-analyze unit-paths --global
+# exist-status
+systemd-analyze exit-status
+systemd-analyze exit-status STDOUT BPF
+systemd-analyze exit-status 0 1 {63..65}
+(! systemd-analyze exit-status STDOUT BPF "hello*")
+# capability
+systemd-analyze capability
+systemd-analyze capability cap_chown CAP_KILL
+systemd-analyze capability 0 1 {30..32}
+(! systemd-analyze capability cap_chown CAP_KILL "hello*")
+# condition
+mkdir -p /run/systemd/system
+UNIT_NAME="analyze-condition-$RANDOM.service"
+cat >"/run/systemd/system/$UNIT_NAME" <<EOF
+[Unit]
+AssertPathExists=/etc/os-release
+AssertEnvironment=!FOOBAR
+ConditionKernelVersion=>1.0
+ConditionPathExists=/etc/os-release
+
+[Service]
+ExecStart=/bin/true
+EOF
+systemctl daemon-reload
+systemd-analyze condition --unit="$UNIT_NAME"
+systemd-analyze condition 'ConditionKernelVersion = ! <4.0' \
+ 'ConditionKernelVersion = >=3.1' \
+ 'ConditionACPower=|false' \
+ 'ConditionArchitecture=|!arm' \
+ 'AssertPathExists=/etc/os-release'
+(! systemd-analyze condition 'ConditionArchitecture=|!arm' 'AssertXYZ=foo')
+(! systemd-analyze condition 'ConditionKernelVersion=<1.0')
+(! systemd-analyze condition 'AssertKernelVersion=<1.0')
+# syscall-filter
+systemd-analyze syscall-filter >/dev/null
+systemd-analyze syscall-filter @chown @sync
+systemd-analyze syscall-filter @sync @sync @sync
+(! systemd-analyze syscall-filter @chown @sync @foobar)
+# filesystems (requires libbpf support)
+if systemctl --version | grep "+BPF_FRAMEWORK"; then
+ systemd-analyze filesystems >/dev/null
+ systemd-analyze filesystems @basic-api
+ systemd-analyze filesystems @basic-api @basic-api @basic-api
+ (! systemd-analyze filesystems @basic-api @basic-api @foobar @basic-api)
+fi
+# calendar
+systemd-analyze calendar '*-2-29 0:0:0'
+systemd-analyze calendar --iterations=5 '*-2-29 0:0:0'
+systemd-analyze calendar '*-* *:*:*'
+systemd-analyze calendar --iterations=5 '*-* *:*:*'
+systemd-analyze calendar --iterations=50 '*-* *:*:*'
+systemd-analyze calendar --iterations=0 '*-* *:*:*'
+systemd-analyze calendar --iterations=5 '01-01-22 01:00:00'
+systemd-analyze calendar --base-time=yesterday --iterations=5 '*-* *:*:*'
+(! systemd-analyze calendar --iterations=0 '*-* 99:*:*')
+(! systemd-analyze calendar --base-time=never '*-* *:*:*')
+(! systemd-analyze calendar 1)
+(! systemd-analyze calendar "")
+# timestamp
+systemd-analyze timestamp now
+systemd-analyze timestamp -- -1
+systemd-analyze timestamp yesterday now tomorrow
+(! systemd-analyze timestamp yesterday never tomorrow)
+(! systemd-analyze timestamp 1)
+(! systemd-analyze timestamp '*-2-29 0:0:0')
+(! systemd-analyze timestamp "")
+# timespan
+systemd-analyze timespan 1
+systemd-analyze timespan 1s 300s '1year 0.000001s'
+(! systemd-analyze timespan 1s 300s aaaaaa '1year 0.000001s')
+(! systemd-analyze timespan -- -1)
+(! systemd-analyze timespan '*-2-29 0:0:0')
+(! systemd-analyze timespan "")
+# cat-config
+systemd-analyze cat-config systemd/system.conf >/dev/null
+systemd-analyze cat-config /etc/systemd/system.conf >/dev/null
+systemd-analyze cat-config systemd/system.conf systemd/journald.conf >/dev/null
+systemd-analyze cat-config systemd/system.conf foo/bar systemd/journald.conf >/dev/null
+systemd-analyze cat-config foo/bar
+# security
+systemd-analyze security
+systemd-analyze security --json=off
+systemd-analyze security --json=pretty | jq
+systemd-analyze security --json=short | jq
+
+if [[ ! -v ASAN_OPTIONS ]]; then
+ # check that systemd-analyze cat-config paths work in a chroot
+ mkdir -p /tmp/root
+ mount --bind / /tmp/root
+ systemd-analyze cat-config systemd/system-preset >/tmp/out1
+ chroot /tmp/root systemd-analyze cat-config systemd/system-preset >/tmp/out2
+ diff /tmp/out{1,2}
+fi
+
+# verify
+mkdir -p /tmp/img/usr/lib/systemd/system/
+mkdir -p /tmp/img/opt/
+
+touch /tmp/img/opt/script0.sh
+chmod +x /tmp/img/opt/script0.sh
+
+cat <<EOF >/tmp/img/usr/lib/systemd/system/testfile.service
+[Service]
+ExecStart = /opt/script0.sh
+EOF
+
+set +e
+# Default behaviour is to recurse through all dependencies when unit is loaded
+(! systemd-analyze verify --root=/tmp/img/ testfile.service)
+
+# As above, recurses through all dependencies when unit is loaded
+(! systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service)
+
+# Recurses through unit file and its direct dependencies when unit is loaded
+(! systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service)
+
+set -e
+
+# zero exit status since dependencies are ignored when unit is loaded
+systemd-analyze verify --recursive-errors=no --root=/tmp/img/ testfile.service
+
+rm /tmp/img/usr/lib/systemd/system/testfile.service
+
+cat <<EOF >/tmp/testfile.service
+[Unit]
+foo = bar
+
+[Service]
+ExecStart = echo hello
+EOF
+
+cat <<EOF >/tmp/testfile2.service
+[Unit]
+Requires = testfile.service
+
+[Service]
+ExecStart = echo hello
+EOF
+
+# Zero exit status since no additional dependencies are recursively loaded when the unit file is loaded
+systemd-analyze verify --recursive-errors=no /tmp/testfile2.service
+
+set +e
+# Non-zero exit status since all associated dependencies are recursively loaded when the unit file is loaded
+(! systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service)
+set -e
+
+rm /tmp/testfile.service
+rm /tmp/testfile2.service
+
+cat <<EOF >/tmp/sample.service
+[Unit]
+Description = A Sample Service
+
+[Service]
+ExecStart = echo hello
+Slice=support.slice
+EOF
+
+# Zero exit status since no additional dependencies are recursively loaded when the unit file is loaded
+systemd-analyze verify --recursive-errors=no /tmp/sample.service
+
+cat <<EOF >/tmp/testfile.service
+[Service]
+ExecStart = echo hello
+DeviceAllow=/dev/sda
+EOF
+
+# Prevent regression from #13380 and #20859 where we can't verify hidden files
+cp /tmp/testfile.service /tmp/.testfile.service
+
+systemd-analyze verify /tmp/.testfile.service
+
+rm /tmp/.testfile.service
+
+# Alias a unit file's name on disk (see #20061)
+cp /tmp/testfile.service /tmp/testsrvc
+
+(! systemd-analyze verify /tmp/testsrvc)
+
+systemd-analyze verify /tmp/testsrvc:alias.service
+
+# Zero exit status since the value used for comparison determine exposure to security threats is by default 100
+systemd-analyze security --offline=true /tmp/testfile.service
+
+#The overall exposure level assigned to the unit is greater than the set threshold
+(! systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service)
+
+# Ensure we print the list of ACLs, see https://github.com/systemd/systemd/issues/23185
+systemd-analyze security --offline=true /tmp/testfile.service | grep -q -F "/dev/sda"
+
+rm /tmp/testfile.service
+
+cat <<EOF >/tmp/img/usr/lib/systemd/system/testfile.service
+[Service]
+ExecStart = echo hello
+PrivateNetwork = yes
+PrivateDevices = yes
+PrivateUsers = yes
+EOF
+
+# The new overall exposure level assigned to the unit is less than the set thresholds
+# Verifies that the --offline= option works with --root=
+systemd-analyze security --threshold=90 --offline=true --root=/tmp/img/ testfile.service
+
+cat <<EOF >/tmp/foo@.service
+[Service]
+ExecStart=ls
+EOF
+
+cat <<EOF >/tmp/hoge@test.service
+[Service]
+ExecStart=ls
+EOF
+
+# issue #30357
+pushd /tmp
+systemd-analyze verify foo@bar.service
+systemd-analyze verify foo@.service
+systemd-analyze verify hoge@test.service
+(! systemd-analyze verify hoge@nonexist.service)
+(! systemd-analyze verify hoge@.service)
+popd
+pushd /
+systemd-analyze verify tmp/foo@bar.service
+systemd-analyze verify tmp/foo@.service
+systemd-analyze verify tmp/hoge@test.service
+(! systemd-analyze verify tmp/hoge@nonexist.service)
+(! systemd-analyze verify tmp/hoge@.service)
+popd
+pushd /usr
+systemd-analyze verify ../tmp/foo@bar.service
+systemd-analyze verify ../tmp/foo@.service
+systemd-analyze verify ../tmp/hoge@test.service
+(! systemd-analyze verify ../tmp/hoge@nonexist.service)
+(! systemd-analyze verify ../tmp/hoge@.service)
+popd
+systemd-analyze verify /tmp/foo@bar.service
+systemd-analyze verify /tmp/foo@.service
+systemd-analyze verify /tmp/hoge@test.service
+(! systemd-analyze verify /tmp/hoge@nonexist.service)
+(! systemd-analyze verify /tmp/hoge@.service)
+
+# Added an additional "INVALID_ID" id to the .json to verify that nothing breaks when input is malformed
+# The PrivateNetwork id description and weight was changed to verify that 'security' is actually reading in
+# values from the .json file when required. The default weight for "PrivateNetwork" is 2500, and the new weight
+# assigned to that id in the .json file is 6000. This increased weight means that when the "PrivateNetwork" key is
+# set to 'yes' (as above in the case of testfile.service) in the content of the unit file, the overall exposure
+# level for the unit file should decrease to account for that increased weight.
+cat <<EOF >/tmp/testfile.json
+{"UserOrDynamicUser":
+ {"description_bad": "Service runs as root user",
+ "weight": 0,
+ "range": 10
+ },
+"SupplementaryGroups":
+ {"description_good": "Service has no supplementary groups",
+ "description_bad": "Service runs with supplementary groups",
+ "description_na": "Service runs as root, option does not matter",
+ "weight": 200,
+ "range": 1
+ },
+"PrivateDevices":
+ {"description_good": "Service has no access to hardware devices",
+ "description_bad": "Service potentially has access to hardware devices",
+ "weight": 1000,
+ "range": 1
+ },
+"PrivateMounts":
+ {"description_good": "Service cannot install system mounts",
+ "description_bad": "Service may install system mounts",
+ "weight": 1000,
+ "range": 1
+ },
+"PrivateNetwork":
+ {"description_good": "Service doesn't have access to the host's network",
+ "description_bad": "Service has access to the host's network",
+ "weight": 6000,
+ "range": 1
+ },
+"PrivateTmp":
+ {"description_good": "Service has no access to other software's temporary files",
+ "description_bad": "Service has access to other software's temporary files",
+ "weight": 1000,
+ "range": 1
+ },
+"PrivateUsers":
+ {"description_good": "Service does not have access to other users",
+ "description_bad": "Service has access to other users",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectControlGroups":
+ {"description_good": "Service cannot modify the control group file system",
+ "description_bad": "Service may modify the control group file system",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectKernelModules":
+ {"description_good": "Service cannot load or read kernel modules",
+ "description_bad": "Service may load or read kernel modules",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectKernelTunables":
+ {"description_good": "Service cannot alter kernel tunables (/proc/sys, …)",
+ "description_bad": "Service may alter kernel tunables",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectKernelLogs":
+ {"description_good": "Service cannot read from or write to the kernel log ring buffer",
+ "description_bad": "Service may read from or write to the kernel log ring buffer",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectClock":
+ {"description_good": "Service cannot write to the hardware clock or system clock",
+ "description_bad": "Service may write to the hardware clock or system clock",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectHome":
+ {"weight": 1000,
+ "range": 10
+ },
+"ProtectHostname":
+ {"description_good": "Service cannot change system host/domainname",
+ "description_bad": "Service may change system host/domainname",
+ "weight": 50,
+ "range": 1
+ },
+"ProtectSystem":
+ {"weight": 1000,
+ "range": 10
+ },
+"RootDirectoryOrRootImage":
+ {"description_good": "Service has its own root directory/image",
+ "description_bad": "Service runs within the host's root directory",
+ "weight": 200,
+ "range": 1
+ },
+"LockPersonality":
+ {"description_good": "Service cannot change ABI personality",
+ "description_bad": "Service may change ABI personality",
+ "weight": 100,
+ "range": 1
+ },
+"MemoryDenyWriteExecute":
+ {"description_good": "Service cannot create writable executable memory mappings",
+ "description_bad": "Service may create writable executable memory mappings",
+ "weight": 100,
+ "range": 1
+ },
+"NoNewPrivileges":
+ {"description_good": "Service processes cannot acquire new privileges",
+ "description_bad": "Service processes may acquire new privileges",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_ADMIN":
+ {"description_good": "Service has no administrator privileges",
+ "description_bad": "Service has administrator privileges",
+ "weight": 1500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SET_UID_GID_PCAP":
+ {"description_good": "Service cannot change UID/GID identities/capabilities",
+ "description_bad": "Service may change UID/GID identities/capabilities",
+ "weight": 1500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_PTRACE":
+ {"description_good": "Service has no ptrace() debugging abilities",
+ "description_bad": "Service has ptrace() debugging abilities",
+ "weight": 1500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_TIME":
+ {"description_good": "Service processes cannot change the system clock",
+ "description_bad": "Service processes may change the system clock",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_NET_ADMIN":
+ {"description_good": "Service has no network configuration privileges",
+ "description_bad": "Service has network configuration privileges",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_RAWIO":
+ {"description_good": "Service has no raw I/O access",
+ "description_bad": "Service has raw I/O access",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_MODULE":
+ {"description_good": "Service cannot load kernel modules",
+ "description_bad": "Service may load kernel modules",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_AUDIT":
+ {"description_good": "Service has no audit subsystem access",
+ "description_bad": "Service has audit subsystem access",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYSLOG":
+ {"description_good": "Service has no access to kernel logging",
+ "description_bad": "Service has access to kernel logging",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_NICE_RESOURCE":
+ {"description_good": "Service has no privileges to change resource use parameters",
+ "description_bad": "Service has privileges to change resource use parameters",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_MKNOD":
+ {"description_good": "Service cannot create device nodes",
+ "description_bad": "Service may create device nodes",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_CHOWN_FSETID_SETFCAP":
+ {"description_good": "Service cannot change file ownership/access mode/capabilities",
+ "description_bad": "Service may change file ownership/access mode/capabilities unrestricted",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_DAC_FOWNER_IPC_OWNER":
+ {"description_good": "Service cannot override UNIX file/IPC permission checks",
+ "description_bad": "Service may override UNIX file/IPC permission checks",
+ "weight": 1000,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_KILL":
+ {"description_good": "Service cannot send UNIX signals to arbitrary processes",
+ "description_bad": "Service may send UNIX signals to arbitrary processes",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW":
+ {"description_good": "Service has no elevated networking privileges",
+ "description_bad": "Service has elevated networking privileges",
+ "weight": 500,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_BOOT":
+ {"description_good": "Service cannot issue reboot()",
+ "description_bad": "Service may issue reboot()",
+ "weight": 100,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_MAC":
+ {"description_good": "Service cannot adjust SMACK MAC",
+ "description_bad": "Service may adjust SMACK MAC",
+ "weight": 100,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_LINUX_IMMUTABLE":
+ {"description_good": "Service cannot mark files immutable",
+ "description_bad": "Service may mark files immutable",
+ "weight": 75,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_IPC_LOCK":
+ {"description_good": "Service cannot lock memory into RAM",
+ "description_bad": "Service may lock memory into RAM",
+ "weight": 50,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_CHROOT":
+ {"description_good": "Service cannot issue chroot()",
+ "description_bad": "Service may issue chroot()",
+ "weight": 50,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_BLOCK_SUSPEND":
+ {"description_good": "Service cannot establish wake locks",
+ "description_bad": "Service may establish wake locks",
+ "weight": 25,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_WAKE_ALARM":
+ {"description_good": "Service cannot program timers that wake up the system",
+ "description_bad": "Service may program timers that wake up the system",
+ "weight": 25,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_LEASE":
+ {"description_good": "Service cannot create file leases",
+ "description_bad": "Service may create file leases",
+ "weight": 25,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_TTY_CONFIG":
+ {"description_good": "Service cannot issue vhangup()",
+ "description_bad": "Service may issue vhangup()",
+ "weight": 25,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_SYS_PACCT":
+ {"description_good": "Service cannot use acct()",
+ "description_bad": "Service may use acct()",
+ "weight": 25,
+ "range": 1
+ },
+"CapabilityBoundingSet_CAP_BPF":
+ {"description_good": "Service may load BPF programs",
+ "description_bad": "Service may not load BPF programs",
+ "weight": 25,
+ "range": 1
+ },
+"UMask":
+ {"weight": 100,
+ "range": 10
+ },
+"KeyringMode":
+ {"description_good": "Service doesn't share key material with other services",
+ "description_bad": "Service shares key material with other service",
+ "weight": 1000,
+ "range": 1
+ },
+"ProtectProc":
+ {"description_good": "Service has restricted access to process tree(/proc hidepid=)",
+ "description_bad": "Service has full access to process tree(/proc hidepid=)",
+ "weight": 1000,
+ "range": 3
+ },
+"ProcSubset":
+ {"description_good": "Service has no access to non-process/proc files(/proc subset=)",
+ "description_bad": "Service has full access to non-process/proc files(/proc subset=)",
+ "weight": 10,
+ "range": 1
+ },
+"NotifyAccess":
+ {"description_good": "Service child processes cannot alter service state",
+ "description_bad": "Service child processes may alter service state",
+ "weight": 1000,
+ "range": 1
+ },
+"RemoveIPC":
+ {"description_good": "Service user cannot leave SysV IPC objects around",
+ "description_bad": "Service user may leave SysV IPC objects around",
+ "description_na": "Service runs as root, option does not apply",
+ "weight": 100,
+ "range": 1
+ },
+"Delegate":
+ {"description_good": "Service does not maintain its own delegated control group subtree",
+ "description_bad": "Service maintains its own delegated control group subtree",
+ "weight": 100,
+ "range": 1
+ },
+"RestrictRealtime":
+ {"description_good": "Service realtime scheduling access is restricted",
+ "description_bad": "Service may acquire realtime scheduling",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictSUIDSGID":
+ {"description_good": "SUID/SGIDfilecreationbyserviceisrestricted",
+ "description_bad": "ServicemaycreateSUID/SGIDfiles",
+ "weight": 1000,
+ "range": 1
+ },
+"RestrictNamespaces_user":
+ {"description_good": "Servicecannotcreateusernamespaces",
+ "description_bad": "Servicemaycreateusernamespaces",
+ "weight": 1500,
+ "range": 1
+ },
+"RestrictNamespaces_mnt":
+ {"description_good": "Service cannot create file system namespaces",
+ "description_bad": "Service may create file system namespaces",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictNamespaces_ipc":
+ {"description_good": "Service cannot create IPC namespaces",
+ "description_bad": "Service may create IPC namespaces",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictNamespaces_pid":
+ {"description_good": "Service cannot create process namespaces",
+ "description_bad": "Service may create process namespaces",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictNamespaces_cgroup":
+ {"description_good": "Service cannot create cgroup namespaces",
+ "description_bad": "Service may create cgroup namespaces",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictNamespaces_net":
+ {"description_good": "Service cannot create network namespaces",
+ "description_bad": "Service may create network namespaces",
+ "weight": 500,
+ "range": 1
+ },
+"RestrictNamespaces_uts":
+ {"description_good": "Service cannot create hostname namespaces",
+ "description_bad": "Service may create hostname namespaces",
+ "weight": 100,
+ "range": 1
+ },
+"RestrictAddressFamilies_AF_INET_INET6":
+ {"description_good": "Service cannot allocate Internet sockets",
+ "description_bad": "Service may allocate Internet sockets",
+ "weight": 1500,
+ "range": 1
+ },
+"RestrictAddressFamilies_AF_UNIX":
+ {"description_good": "Service cannot allocate local sockets",
+ "description_bad": "Service may allocate local sockets",
+ "weight": 25,
+ "range": 1
+ },
+"RestrictAddressFamilies_AF_NETLINK":
+ {"description_good": "Service cannot allocate netlink sockets",
+ "description_bad": "Service may allocate netlink sockets",
+ "weight": 200,
+ "range": 1
+ },
+"RestrictAddressFamilies_AF_PACKET":
+ {"description_good": "Service cannot allocate packet sockets",
+ "description_bad": "Service may allocate packet sockets",
+ "weight": 1000,
+ "range": 1
+ },
+"RestrictAddressFamilies_OTHER":
+ {"description_good": "Service cannot allocate exotic sockets",
+ "description_bad": "Service may allocate exotic sockets",
+ "weight": 1250,
+ "range": 1
+ },
+"SystemCallArchitectures":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_swap":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_obsolete":
+ {"weight": 250,
+ "range": 10
+ },
+"SystemCallFilter_clock":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_cpu_emulation":
+ {"weight": 250,
+ "range": 10
+ },
+"SystemCallFilter_debug":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_mount":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_module":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_raw_io":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_reboot":
+ {"weight": 1000,
+ "range": 10
+ },
+"SystemCallFilter_privileged":
+ {"weight": 700,
+ "range": 10
+ },
+"SystemCallFilter_resources":
+ {"weight": 700,
+ "range": 10
+ },
+"IPAddressDeny":
+ {"weight": 1000,
+ "range": 10
+ },
+"DeviceAllow":
+ {"weight": 1000,
+ "range": 10
+ },
+"AmbientCapabilities":
+ {"description_good": "Service process does not receive ambient capabilities",
+ "description_bad": "Service process receives ambient capabilities",
+ "weight": 500,
+ "range": 1
+ },
+"INVALID_ID":
+ {"weight": 1000,
+ "range": 10
+ }
+}
+EOF
+
+# Reads in custom security requirements from the parsed .json file and uses these for comparison
+systemd-analyze security --threshold=90 --offline=true \
+ --security-policy=/tmp/testfile.json \
+ --root=/tmp/img/ testfile.service
+
+# The strict profile adds a lot of sanboxing options
+systemd-analyze security --threshold=25 --offline=true \
+ --security-policy=/tmp/testfile.json \
+ --profile=strict \
+ --root=/tmp/img/ testfile.service
+
+# The trusted profile doesn't add any sanboxing options
+(! systemd-analyze security --threshold=25 --offline=true \
+ --security-policy=/tmp/testfile.json \
+ --profile=/usr/lib/systemd/portable/profile/trusted/service.conf \
+ --root=/tmp/img/ testfile.service)
+
+(! systemd-analyze security --threshold=50 --offline=true \
+ --security-policy=/tmp/testfile.json \
+ --root=/tmp/img/ testfile.service)
+
+rm /tmp/img/usr/lib/systemd/system/testfile.service
+
+if systemd-analyze --version | grep -q -F "+ELFUTILS"; then
+ systemd-analyze inspect-elf --json=short /lib/systemd/systemd | grep -q -F '"elfType":"executable"'
+fi
+
+systemd-analyze --threshold=90 security systemd-journald.service
+
+# issue 23663
+check() {(
+ set +x
+ output=$(systemd-analyze security --offline="${2?}" "${3?}" | grep -F 'SystemCallFilter=')
+ assert_in "System call ${1?} list" "$output"
+ assert_in "[+✓] SystemCallFilter=~@swap" "$output"
+ assert_in "[+✓] SystemCallFilter=~@resources" "$output"
+ assert_in "[+✓] SystemCallFilter=~@reboot" "$output"
+ assert_in "[+✓] SystemCallFilter=~@raw-io" "$output"
+ assert_in "[-✗] SystemCallFilter=~@privileged" "$output"
+ assert_in "[+✓] SystemCallFilter=~@obsolete" "$output"
+ assert_in "[+✓] SystemCallFilter=~@mount" "$output"
+ assert_in "[+✓] SystemCallFilter=~@module" "$output"
+ assert_in "[+✓] SystemCallFilter=~@debug" "$output"
+ assert_in "[+✓] SystemCallFilter=~@cpu-emulation" "$output"
+ assert_in "[-✗] SystemCallFilter=~@clock" "$output"
+)}
+
+export -n SYSTEMD_LOG_LEVEL
+
+mkdir -p /run/systemd/system
+cat >/run/systemd/system/allow-list.service <<EOF
+[Service]
+ExecStart=false
+SystemCallFilter=@system-service
+SystemCallFilter=~@resources:ENOANO @privileged
+SystemCallFilter=@clock
+EOF
+
+cat >/run/systemd/system/deny-list.service <<EOF
+[Service]
+ExecStart=false
+SystemCallFilter=~@known
+SystemCallFilter=@system-service
+SystemCallFilter=~@resources:ENOANO @privileged
+SystemCallFilter=@clock
+EOF
+
+systemctl daemon-reload
+
+check allow yes /run/systemd/system/allow-list.service
+check allow no allow-list.service
+check deny yes /run/systemd/system/deny-list.service
+check deny no deny-list.service
+
+output=$(systemd-run -p "SystemCallFilter=@system-service" -p "SystemCallFilter=~@resources:ENOANO @privileged" -p "SystemCallFilter=@clock" sleep 60 2>&1)
+name=$(echo "$output" | awk '{ print $4 }')
+
+check allow yes /run/systemd/transient/"$name"
+check allow no "$name"
+
+output=$(systemd-run -p "SystemCallFilter=~@known" -p "SystemCallFilter=@system-service" -p "SystemCallFilter=~@resources:ENOANO @privileged" -p "SystemCallFilter=@clock" sleep 60 2>&1)
+name=$(echo "$output" | awk '{ print $4 }')
+
+check deny yes /run/systemd/transient/"$name"
+check deny no "$name"
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-66-deviceisolation.service b/test/units/testsuite-66-deviceisolation.service
new file mode 100644
index 0000000..9da4a08
--- /dev/null
+++ b/test/units/testsuite-66-deviceisolation.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Service that uses device isolation
+
+[Service]
+DevicePolicy=strict
+DeviceAllow=/dev/null r
+StandardOutput=file:/tmp/testsuite66serviceresults
+ExecStartPre=rm -f /tmp/testsuite66serviceresults
+ExecStart=/bin/bash -c "while true; do sleep 0.01 && echo meow > /dev/null && echo thisshouldnotbehere; done"
diff --git a/test/units/testsuite-66.service b/test/units/testsuite-66.service
new file mode 100644
index 0000000..7e9dc3b
--- /dev/null
+++ b/test/units/testsuite-66.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TESTSUITE-66-DEVICEISOLATION
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-66.sh b/test/units/testsuite-66.sh
new file mode 100755
index 0000000..7a88e5b
--- /dev/null
+++ b/test/units/testsuite-66.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+RESULTS_FILE=/tmp/testsuite66serviceresults
+
+systemd-analyze log-level debug
+
+systemctl start testsuite-66-deviceisolation.service
+
+sleep 5
+grep -q "Operation not permitted" "$RESULTS_FILE"
+
+systemctl daemon-reload
+systemctl daemon-reexec
+
+systemctl stop testsuite-66-deviceisolation.service
+
+grep -q "thisshouldnotbehere" "$RESULTS_FILE" && exit 42
+
+systemd-analyze log-level info
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-67.service b/test/units/testsuite-67.service
new file mode 100644
index 0000000..82f998e
--- /dev/null
+++ b/test/units/testsuite-67.service
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-67-INTEGRITY
+After=multi-user.target
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-67.sh b/test/units/testsuite-67.sh
new file mode 100755
index 0000000..b813621
--- /dev/null
+++ b/test/units/testsuite-67.sh
@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -euxo pipefail
+
+export DM_NAME="integrity_test"
+export FULL_DM_DEV_NAME="/dev/mapper/${DM_NAME}"
+export FS_UUID="01234567-ffff-eeee-eeee-0123456789ab"
+export GEN="/var/run/systemd/generator"
+
+image_dir=""
+
+cleanup()
+{
+ if [ -z "${image_dir}" ]; then
+ return
+ fi
+
+ if [ -f "${image_dir}/image" ]; then
+ if [ -e "${FULL_DM_DEV_NAME}" ]; then
+ integritysetup close "${DM_NAME}"
+ fi
+ losetup -d "${loop}"
+ fi
+
+ rm -rf "${image_dir}"
+}
+
+trap cleanup EXIT
+
+build_integrity_tab()
+{
+cat << _EOL > "/etc/integritytab"
+${DM_NAME} ${loop} - integrity-algorithm=$1
+_EOL
+}
+
+image_dir="$(mktemp -d -t -p / integrity.tmp.XXXXXX)"
+if [ -z "${image_dir}" ] || [ ! -d "${image_dir}" ]; then
+ echo "mktemp under / failed"
+ exit 1
+fi
+
+dd if=/dev/zero of="${image_dir}/image" bs=1048576 count=64 || exit 1
+dd if=/dev/zero of="${image_dir}/data" bs=1048576 count=64 || exit 1
+loop="$(losetup --show -f "${image_dir}/image")"
+
+if [[ ! -e ${loop} ]]; then
+ echo "Loopback device created not found!"
+ exit 1
+fi
+
+# Do one iteration with a separate data device, to test those branches
+separate_data=1
+
+for algorithm in crc32c crc32 sha1 sha256
+do
+ if [ "${separate_data}" -eq 1 ]; then
+ data_option="--data-device=${image_dir}/data"
+ else
+ data_option=""
+ fi
+ integritysetup format "${loop}" --batch-mode -I "${algorithm}" "${data_option}" || exit 1
+ integritysetup open -I "${algorithm}" "${loop}" "${DM_NAME}" "${data_option}" || exit 1
+ mkfs.ext4 -U "${FS_UUID}" "${FULL_DM_DEV_NAME}" || exit 1
+
+ # Give userspace time to handle udev events for new FS showing up ...
+ udevadm settle
+
+ integritysetup close "${DM_NAME}" || exit 1
+
+ # create integritytab, generate units, start service
+ if [ "${separate_data}" -eq 1 ]; then
+ data_option=",data-device=${image_dir}/data"
+ else
+ data_option=""
+ fi
+ build_integrity_tab "${algorithm}${data_option}"
+
+ # Cause the generator to re-run
+ systemctl daemon-reload || exit 1
+
+ # Check for existence of unit files...
+ if [[ ! -e "/run/systemd/generator/systemd-integritysetup@${DM_NAME}.service" ]]; then
+ echo "Service file does not exist!"
+ exit 1
+ fi
+
+ # Make sure we are in a consistent state, e.g. not already active before we start
+ systemctl stop systemd-integritysetup@"${DM_NAME}".service || exit 1
+ systemctl start systemd-integritysetup@"${DM_NAME}".service || exit 1
+
+ # Check the signature on the FS to ensure we can retrieve it and that is matches
+ if [ -e "${FULL_DM_DEV_NAME}" ]; then
+ # If a separate device is used for the metadata storage, then blkid will return one of the loop devices
+ if [ "${separate_data}" -eq 1 ]; then
+ dev_name="$(integritysetup status ${DM_NAME} | grep '^\s*device:' | awk '{print $2}')"
+ else
+ dev_name="${FULL_DM_DEV_NAME}"
+ fi
+ if [ "${dev_name}" != "$(blkid -U "${FS_UUID}")" ]; then
+ echo "Failed to locate FS with matching UUID!"
+ exit 1
+ fi
+ else
+ echo "Failed to bring up integrity device!"
+ exit 1
+ fi
+
+ systemctl stop systemd-integritysetup@"${DM_NAME}".service || exit 1
+
+ if [ -e "${FULL_DM_DEV_NAME}" ]; then
+ echo "Expecting ${FULL_DM_DEV_NAME} to not exist after stopping unit!"
+ exit 1
+ fi
+
+ separate_data=0
+done
+
+echo OK >/testok
diff --git a/test/units/testsuite-68.service b/test/units/testsuite-68.service
new file mode 100644
index 0000000..2d86e1f
--- /dev/null
+++ b/test/units/testsuite-68.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-68-PROPAGATE-EXIT-STATUS
+
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
diff --git a/test/units/testsuite-68.sh b/test/units/testsuite-68.sh
new file mode 100755
index 0000000..fefb030
--- /dev/null
+++ b/test/units/testsuite-68.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+# Wait for a service to enter a state within a timeout period, if it doesn't
+# enter the desired state within the timeout period then this function will
+# exit the test case with a non zero exit code.
+wait_on_state_or_fail () {
+ service=$1
+ expected_state=$2
+ timeout=$3
+
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ while [ "$state" != "$expected_state" ]; do
+ if [ "$timeout" = "0" ]; then
+ systemd-analyze log-level info
+ exit 1
+ fi
+ timeout=$((timeout - 1))
+ sleep 1
+ state=$(systemctl show "$service" --property=ActiveState --value)
+ done
+}
+
+systemd-analyze log-level debug
+
+cat >/run/systemd/system/testservice-failure-68.service <<EOF
+[Unit]
+OnFailure=testservice-failure-exit-handler-68.service
+
+[Service]
+ExecStart=sh -c "exit 1"
+EOF
+
+cat >/run/systemd/system/testservice-failure-68-template.service <<EOF
+[Unit]
+OnFailure=testservice-failure-exit-handler-68-template@%n.service
+
+[Service]
+ExecStart=sh -c "exit 1"
+EOF
+
+cat >/run/systemd/system/testservice-success-68.service <<EOF
+[Unit]
+OnSuccess=testservice-success-exit-handler-68.service
+
+[Service]
+ExecStart=sh -c "exit 0"
+EOF
+
+cat >/run/systemd/system/testservice-success-68-template.service <<EOF
+[Unit]
+OnSuccess=testservice-success-exit-handler-68-template@%n.service
+
+[Service]
+ExecStart=sh -c "exit 0"
+EOF
+
+# Script to check that when an OnSuccess= dependency fires, the correct
+# MONITOR* env variables are passed.
+cat >/tmp/check_on_success.sh <<"EOF"
+#!/bin/sh
+
+set -ex
+env | sort
+if [ "$MONITOR_SERVICE_RESULT" != "success" ]; then
+ echo "MONITOR_SERVICE_RESULT was '$MONITOR_SERVICE_RESULT', expected 'success'"
+ exit 1
+fi
+
+if [ "$MONITOR_EXIT_CODE" != "exited" ]; then
+ echo "MONITOR_EXIT_CODE was '$MONITOR_EXIT_CODE', expected 'exited'"
+ exit 1
+fi
+
+if [ "$MONITOR_EXIT_STATUS" != "0" ]; then
+ echo "MONITOR_EXIT_STATUS was '$MONITOR_EXIT_STATUS', expected '0'"
+ exit 1
+fi
+
+if [ -z "$MONITOR_INVOCATION_ID" ]; then
+ echo "MONITOR_INVOCATION_ID unset"
+ exit 1
+fi
+
+if [ "$MONITOR_UNIT" != "testservice-success-68.service" ] &&
+ [ "$MONITOR_UNIT" != "testservice-success-68-template.service" ] &&
+ [ "$MONITOR_UNIT" != "testservice-transient-success-68.service" ]; then
+
+ echo "MONITOR_UNIT was '$MONITOR_UNIT', expected 'testservice[-transient]-success-68[-template].service'"
+ exit 1
+fi
+
+exit 0
+EOF
+chmod +x /tmp/check_on_success.sh
+
+cat >/run/systemd/system/testservice-success-exit-handler-68.service <<EOF
+[Service]
+ExecStartPre=/tmp/check_on_success.sh
+ExecStart=/tmp/check_on_success.sh
+EOF
+
+cp /run/systemd/system/testservice-success-exit-handler-68.service \
+ /run/systemd/system/testservice-transient-success-exit-handler-68.service
+
+# Template version.
+cat >/run/systemd/system/testservice-success-exit-handler-68-template@.service <<EOF
+[Service]
+ExecStartPre=echo "triggered by %i"
+ExecStartPre=/tmp/check_on_success.sh
+ExecStart=/tmp/check_on_success.sh
+EOF
+
+# Script to check that when an OnFailure= dependency fires, the correct
+# MONITOR* env variables are passed.
+cat >/tmp/check_on_failure.sh <<"EOF"
+#!/bin/sh
+
+set -ex
+env | sort
+if [ "$MONITOR_SERVICE_RESULT" != "exit-code" ]; then
+ echo "MONITOR_SERVICE_RESULT was '$MONITOR_SERVICE_RESULT', expected 'exit-code'"
+ exit 1
+fi
+
+if [ "$MONITOR_EXIT_CODE" != "exited" ]; then
+ echo "MONITOR_EXIT_CODE was '$MONITOR_EXIT_CODE', expected 'exited'"
+ exit 1
+fi
+
+if [ "$MONITOR_EXIT_STATUS" != "1" ]; then
+ echo "MONITOR_EXIT_STATUS was '$MONITOR_EXIT_STATUS', expected '1'"
+ exit 1
+fi
+
+if [ -z "$MONITOR_INVOCATION_ID" ]; then
+ echo "MONITOR_INVOCATION_ID unset"
+ exit 1
+fi
+
+if [ "$MONITOR_UNIT" != "testservice-failure-68.service" ] &&
+ [ "$MONITOR_UNIT" != "testservice-failure-68-template.service" ] &&
+ [ "$MONITOR_UNIT" != "testservice-transient-failure-68.service" ]; then
+
+ echo "MONITOR_UNIT was '$MONITOR_UNIT', expected 'testservice[-transient]-failure-68[-template].service'"
+ exit 1
+fi
+
+exit 0
+EOF
+chmod +x /tmp/check_on_failure.sh
+
+
+cat >/run/systemd/system/testservice-failure-exit-handler-68.service <<EOF
+[Service]
+# repeat the check to make sure that values are set correctly on repeated invocations
+Type=oneshot
+ExecStartPre=/tmp/check_on_failure.sh
+ExecStartPre=/tmp/check_on_failure.sh
+ExecStart=/tmp/check_on_failure.sh
+ExecStart=/tmp/check_on_failure.sh
+ExecStartPost=test -z '$MONITOR_SERVICE_RESULT'
+EOF
+
+cp /run/systemd/system/testservice-failure-exit-handler-68.service \
+ /run/systemd/system/testservice-transient-failure-exit-handler-68.service
+
+# Template version.
+cat >/run/systemd/system/testservice-failure-exit-handler-68-template@.service <<EOF
+[Service]
+Type=oneshot
+ExecStartPre=echo "triggered by %i"
+ExecStartPre=/tmp/check_on_failure.sh
+ExecStartPre=/tmp/check_on_failure.sh
+ExecStart=/tmp/check_on_failure.sh
+ExecStart=/tmp/check_on_failure.sh
+ExecStartPost=test -z '$MONITOR_SERVICE_RESULT'
+EOF
+
+systemctl daemon-reload
+
+: "-------I----------------------------------------------------"
+systemctl start testservice-failure-68.service
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+
+: "-------II---------------------------------------------------"
+systemctl start testservice-success-68.service
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+
+# Test some transient units since these exit very quickly.
+: "-------III--------------------------------------------------"
+systemd-run --unit=testservice-transient-success-68 \
+ --property=OnSuccess=testservice-transient-success-exit-handler-68.service \
+ sh -c "exit 0"
+wait_on_state_or_fail "testservice-success-exit-handler-68.service" "inactive" "10"
+
+: "-------IIII-------------------------------------------------"
+systemd-run --unit=testservice-transient-failure-68 \
+ --property=OnFailure=testservice-transient-failure-exit-handler-68.service \
+ sh -c "exit 1"
+wait_on_state_or_fail "testservice-failure-exit-handler-68.service" "inactive" "10"
+
+# Test template handlers too
+: "-------V---------------------------------------------------"
+systemctl start testservice-success-68-template.service
+wait_on_state_or_fail "testservice-success-exit-handler-68-template@testservice-success-68-template.service.service" "inactive" "10"
+
+: "-------VI----------------------------------------------------"
+systemctl start testservice-failure-68-template.service
+wait_on_state_or_fail "testservice-failure-exit-handler-68-template@testservice-failure-68-template.service.service" "inactive" "10"
+
+systemd-analyze log-level info
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-69.service b/test/units/testsuite-69.service
new file mode 100644
index 0000000..7aa0664
--- /dev/null
+++ b/test/units/testsuite-69.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-69-SHUTDOWN
+
+[Service]
+Type=oneshot
+ExecStart=/bin/true
diff --git a/test/units/testsuite-70.service b/test/units/testsuite-70.service
new file mode 100644
index 0000000..c13c2d5
--- /dev/null
+++ b/test/units/testsuite-70.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-70-TPM2
+
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh
new file mode 100755
index 0000000..c049e8f
--- /dev/null
+++ b/test/units/testsuite-70.sh
@@ -0,0 +1,234 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -ex
+set -o pipefail
+
+export SYSTEMD_LOG_LEVEL=debug
+
+# Prepare fresh disk image
+img="/var/tmp/test.img"
+truncate -s 20M $img
+echo -n passphrase >/tmp/passphrase
+cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase
+
+# Unlocking via keyfile
+systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto $img
+
+# Enroll unlock with default PCR policy
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
+/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
+/usr/lib/systemd/systemd-cryptsetup detach test-volume
+
+# Check with wrong PCR
+tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
+(! /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
+
+# Enroll unlock with PCR+PIN policy
+systemd-cryptenroll --wipe-slot=tpm2 $img
+PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
+PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
+/usr/lib/systemd/systemd-cryptsetup detach test-volume
+
+# Check failure with wrong PIN
+(! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
+
+# Check LUKS2 token plugin unlock (i.e. without specifying tpm2-device=auto)
+if cryptsetup --help | grep -q 'LUKS2 external token plugin support is compiled-in' && \
+ [ -f "$(cryptsetup --help | sed -n -r 's/.*LUKS2 external token plugin path: (.*)\./\1/p')/libcryptsetup-token-systemd-tpm2.so" ]; then
+ PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1
+ /usr/lib/systemd/systemd-cryptsetup detach test-volume
+
+ # Check failure with wrong PIN
+ (! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1)
+else
+ echo 'cryptsetup has no LUKS2 token plugin support, skipping'
+fi
+
+# Check failure with wrong PCR (and correct PIN)
+tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
+(! PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
+
+# Enroll unlock with PCR 0+7
+systemd-cryptenroll --wipe-slot=tpm2 $img
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
+/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
+/usr/lib/systemd/systemd-cryptsetup detach test-volume
+
+# Check with wrong PCR 0
+tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
+/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1
+
+rm $img
+
+if [[ -e /usr/lib/systemd/systemd-measure ]]; then
+ echo HALLO > /tmp/tpmdata1
+ echo foobar > /tmp/tpmdata2
+
+ cat >/tmp/result <<EOF
+11:sha1=5177e4ad69db92192c10e5f80402bf81bfec8a81
+11:sha256=37b48bd0b222394dbe3cceff2fca4660c4b0a90ae9369ec90b42f14489989c13
+11:sha384=5573f9b2caf55b1d0a6a701f890662d682af961899f0419cf1e2d5ea4a6a68c1f25bd4f5b8a0865eeee82af90f5cb087
+11:sha512=961305d7e9981d6606d1ce97b3a9a1f92610cac033e9c39064895f0e306abc1680463d55767bd98e751eae115bdef3675a9ee1d29ed37da7885b1db45bb2555b
+EOF
+ /usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=: | cmp - /tmp/result
+
+ cat >/tmp/result.json <<EOF
+{"sha1":[{"pcr":11,"hash":"5177e4ad69db92192c10e5f80402bf81bfec8a81"}],"sha256":[{"pcr":11,"hash":"37b48bd0b222394dbe3cceff2fca4660c4b0a90ae9369ec90b42f14489989c13"}],"sha384":[{"pcr":11,"hash":"5573f9b2caf55b1d0a6a701f890662d682af961899f0419cf1e2d5ea4a6a68c1f25bd4f5b8a0865eeee82af90f5cb087"}],"sha512":[{"pcr":11,"hash":"961305d7e9981d6606d1ce97b3a9a1f92610cac033e9c39064895f0e306abc1680463d55767bd98e751eae115bdef3675a9ee1d29ed37da7885b1db45bb2555b"}]}
+EOF
+ /usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=: -j | diff -u - /tmp/result.json
+
+ cat >/tmp/result <<EOF
+11:sha1=6765ee305db063040c454d32697d922b3d4f232b
+11:sha256=21c49c1242042649e09c156546fd7d425ccc3c67359f840507b30be4e0f6f699
+11:sha384=08d0b003a134878eee552070d51d58abe942f457ca85704131dd36f73728e7327ca837594bc9d5ac7de818d02a3d5dd2
+11:sha512=65120f6ebc04b156421c6f3d543b2fad545363d9ca61c514205459e9c0e0b22e09c23605eae5853e38458ef3ca54e087168af8d8a882a98d220d9391e48be6d0
+EOF
+ /usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=foo | cmp - /tmp/result
+
+ cat >/tmp/result.json <<EOF
+{"sha1":[{"phase":"foo","pcr":11,"hash":"6765ee305db063040c454d32697d922b3d4f232b"}],"sha256":[{"phase":"foo","pcr":11,"hash":"21c49c1242042649e09c156546fd7d425ccc3c67359f840507b30be4e0f6f699"}],"sha384":[{"phase":"foo","pcr":11,"hash":"08d0b003a134878eee552070d51d58abe942f457ca85704131dd36f73728e7327ca837594bc9d5ac7de818d02a3d5dd2"}],"sha512":[{"phase":"foo","pcr":11,"hash":"65120f6ebc04b156421c6f3d543b2fad545363d9ca61c514205459e9c0e0b22e09c23605eae5853e38458ef3ca54e087168af8d8a882a98d220d9391e48be6d0"}]}
+EOF
+ /usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 --phase=foo -j | diff -u - /tmp/result.json
+
+ rm /tmp/result /tmp/result.json
+else
+ echo "/usr/lib/systemd/systemd-measure not found, skipping PCR policy test case"
+fi
+
+if [ -e /usr/lib/systemd/systemd-measure ] && \
+ [ -f /sys/class/tpm/tpm0/pcr-sha1/11 ] && \
+ [ -f /sys/class/tpm/tpm0/pcr-sha256/11 ]; then
+ # Generate key pair
+ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "/tmp/pcrsign-private.pem"
+ openssl rsa -pubout -in "/tmp/pcrsign-private.pem" -out "/tmp/pcrsign-public.pem"
+
+ MEASURE_BANKS=("--bank=sha256")
+ # Check if SHA1 signatures are supported
+ #
+ # Some distros have started phasing out SHA1, so make sure the SHA1
+ # signatures are supported before trying to use them.
+ if echo hello | openssl dgst -sign /tmp/pcrsign-private.pem -sha1 >/dev/null; then
+ MEASURE_BANKS+=("--bank=sha1")
+ fi
+
+ # Sign current PCR state with it
+ /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: | tee "/tmp/pcrsign.sig"
+ dd if=/dev/urandom of=/tmp/pcrtestdata bs=1024 count=64
+ systemd-creds encrypt /tmp/pcrtestdata /tmp/pcrtestdata.encrypted --with-key=host+tpm2-with-public-key --tpm2-public-key="/tmp/pcrsign-public.pem"
+ systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" | cmp - /tmp/pcrtestdata
+
+ # Invalidate PCR, decrypting should fail now
+ tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
+ (! systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" > /dev/null)
+
+ # Sign new PCR state, decrypting should work now.
+ /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: > "/tmp/pcrsign.sig2"
+ systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata
+
+ # Now, do the same, but with a cryptsetup binding
+ truncate -s 20M $img
+ cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase
+ # Ensure that an unrelated signature, when not requested, is not used
+ touch /run/systemd/tpm2-pcr-signature.json
+ systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" $img
+ # Reset and use the signature now
+ rm -f /run/systemd/tpm2-pcr-signature.json
+ systemd-cryptenroll --wipe-slot=tpm2 $img
+ systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-signature="/tmp/pcrsign.sig2" $img
+
+ # Check if we can activate that (without the token module stuff)
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ # Check if we can activate that (and a second time with the the token module stuff enabled)
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ # After extending the PCR things should fail
+ tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
+ (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
+ (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
+
+ # But once we sign the current PCRs, we should be able to unlock again
+ /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: > "/tmp/pcrsign.sig3"
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
+ /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
+ /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ rm $img
+else
+ echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case"
+fi
+
+# Ensure that sandboxing doesn't stop creds from being accessible
+echo "test" > /tmp/testdata
+systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2
+# LoadCredentialEncrypted
+systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata
+# SetCredentialEncrypted
+systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata
+rm /tmp/testdata
+
+# negative tests for cryptenroll
+
+# Prepare a new disk image
+img_2="/var/tmp/file_enroll.txt"
+truncate -s 20M $img_2
+echo -n password >/tmp/password
+cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password
+
+#boolean_arguments
+(! systemd-cryptenroll --fido2-with-client-pin=false)
+
+(! systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo)
+
+(! systemd-cryptenroll --fido2-with-client-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-client-pin=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-presence=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-presence=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-verification=1234 $img_2)
+
+(! systemd-cryptenroll --tpm2-with-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-verification=false $img_2
+
+#arg_enroll_type
+(! systemd-cryptenroll --recovery-key --password $img_2)
+
+(! systemd-cryptenroll --password --recovery-key $img_2)
+
+(! systemd-cryptenroll --password --fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2)
+
+(! systemd-cryptenroll --password --tpm2-device=auto $img_2)
+
+#arg_unlock_type
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2)
+
+#fido2_cred_algorithm
+(! systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2)
+
+#tpm2_errors
+(! systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2)
+
+(! systemd-cryptenroll --tpm2-pcrs=key $img_2)
+
+#wipe_slots
+(! systemd-cryptenroll --wipe-slot $img_2)
+
+(! systemd-cryptenroll --wipe-slot=10240000 $img_2)
+
+#fido2_multiple_auto
+(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2)
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-71.service b/test/units/testsuite-71.service
new file mode 100644
index 0000000..1718629
--- /dev/null
+++ b/test/units/testsuite-71.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-71-HOSTNAME
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-71.sh b/test/units/testsuite-71.sh
new file mode 100755
index 0000000..2382ccc
--- /dev/null
+++ b/test/units/testsuite-71.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+restore_hostname() {
+ if [[ -e /tmp/hostname.bak ]]; then
+ mv /tmp/hostname.bak /etc/hostname
+ else
+ rm -f /etc/hostname
+ fi
+}
+
+test_hostname() {
+ local orig=
+
+ if [[ -f /etc/hostname ]]; then
+ cp /etc/hostname /tmp/hostname.bak
+ orig=$(cat /etc/hostname)
+ fi
+
+ trap restore_hostname RETURN
+
+ # should activate daemon and work
+ if [[ -n "$orig" ]]; then
+ assert_in "Static hostname: $orig" "$(hostnamectl)"
+ fi
+ assert_in "Kernel: $(uname -s) $(uname -r)" "$(hostnamectl)"
+
+ # change hostname
+ assert_rc 0 hostnamectl set-hostname testhost
+ assert_eq "$(cat /etc/hostname)" "testhost"
+ assert_in "Static hostname: testhost" "$(hostnamectl)"
+
+ if [[ -n "$orig" ]]; then
+ # reset to original
+ assert_rc 0 hostnamectl set-hostname "$orig"
+ assert_eq "$(cat /etc/hostname)" "$orig"
+ assert_in "Static hostname: $orig" "$(hostnamectl)"
+ fi
+}
+
+restore_machine_info() {
+ if [[ -e /tmp/machine-info.bak ]]; then
+ mv /tmp/machine-info.bak /etc/machine-info
+ else
+ rm -f /etc/machine-info
+ fi
+}
+
+get_chassis() (
+ # shellcheck source=/dev/null
+ . /etc/machine-info
+
+ echo "$CHASSIS"
+)
+
+test_chassis() {
+ local i
+
+ if [[ -f /etc/machine-info ]]; then
+ cp /etc/machine-info /tmp/machine-info.bak
+ fi
+
+ trap restore_machine_info RETURN
+
+ # Invalid chassis type is refused
+ assert_rc 1 hostnamectl chassis hoge
+
+ # Valid chassis types
+ for i in vm container desktop laptop convertible server tablet handset watch embedded; do
+ hostnamectl chassis "$i"
+ assert_eq "$(hostnamectl chassis)" "$i"
+ assert_eq "$(get_chassis)" "$i"
+ done
+
+ systemctl stop systemd-hostnamed.service
+ rm -f /etc/machine-info
+
+ # fallback chassis type
+ if systemd-detect-virt --quiet --container; then
+ assert_eq "$(hostnamectl chassis)" container
+ elif systemd-detect-virt --quiet --vm; then
+ assert_eq "$(hostnamectl chassis)" vm
+ fi
+}
+
+: >/failed
+
+test_hostname
+test_chassis
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-72.service b/test/units/testsuite-72.service
new file mode 100644
index 0000000..1640350
--- /dev/null
+++ b/test/units/testsuite-72.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-72-SYSUPDATE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-72.sh b/test/units/testsuite-72.sh
new file mode 100755
index 0000000..9effc98
--- /dev/null
+++ b/test/units/testsuite-72.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -eux
+set -o pipefail
+
+SYSUPDATE=/lib/systemd/systemd-sysupdate
+
+if ! test -x "$SYSUPDATE"; then
+ echo "no systemd-sysupdate" >/skipped
+ exit 0
+fi
+
+export SYSTEMD_PAGER=cat
+export SYSTEMD_LOG_LEVEL=debug
+
+rm -f /var/tmp/72-joined.raw
+truncate -s 10M /var/tmp/72-joined.raw
+
+sfdisk /var/tmp/72-joined.raw <<EOF
+label: gpt
+unit: sectors
+sector-size: 512
+
+size=2048, type=4f68bce3-e8cd-4db1-96e7-fbcaf984b709, name=_empty
+size=2048, type=4f68bce3-e8cd-4db1-96e7-fbcaf984b709, name=_empty
+size=2048, type=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, name=_empty
+size=2048, type=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, name=_empty
+EOF
+
+rm -rf /var/tmp/72-dirs
+
+rm -rf /var/tmp/72-defs
+mkdir -p /var/tmp/72-defs
+
+cat >/var/tmp/72-defs/01-first.conf <<"EOF"
+[Source]
+Type=regular-file
+Path=/var/tmp/72-source
+MatchPattern=part1-@v.raw
+
+[Target]
+Type=partition
+Path=/var/tmp/72-joined.raw
+MatchPattern=part1-@v
+MatchPartitionType=root-x86-64
+EOF
+
+cat >/var/tmp/72-defs/02-second.conf <<"EOF"
+[Source]
+Type=regular-file
+Path=/var/tmp/72-source
+MatchPattern=part2-@v.raw.gz
+
+[Target]
+Type=partition
+Path=/var/tmp/72-joined.raw
+MatchPattern=part2-@v
+MatchPartitionType=root-x86-64-verity
+EOF
+
+cat >/var/tmp/72-defs/03-third.conf <<"EOF"
+[Source]
+Type=directory
+Path=/var/tmp/72-source
+MatchPattern=dir-@v
+
+[Target]
+Type=directory
+Path=/var/tmp/72-dirs
+CurrentSymlink=/var/tmp/72-dirs/current
+MatchPattern=dir-@v
+InstancesMax=3
+EOF
+
+rm -rf /var/tmp/72-source
+mkdir -p /var/tmp/72-source
+
+new_version() {
+ # Create a pair of random partition payloads, and compress one
+ dd if=/dev/urandom of="/var/tmp/72-source/part1-$1.raw" bs=1024 count=1024
+ dd if=/dev/urandom of="/var/tmp/72-source/part2-$1.raw" bs=1024 count=1024
+ gzip -k -f "/var/tmp/72-source/part2-$1.raw"
+
+ mkdir -p "/var/tmp/72-source/dir-$1"
+ echo $RANDOM >"/var/tmp/72-source/dir-$1/foo.txt"
+ echo $RANDOM >"/var/tmp/72-source/dir-$1/bar.txt"
+
+ tar --numeric-owner -C "/var/tmp/72-source/dir-$1/" -czf "/var/tmp/72-source/dir-$1.tar.gz" .
+
+ ( cd /var/tmp/72-source/ && sha256sum part* dir-*.tar.gz >SHA256SUMS )
+}
+
+update_now() {
+ # Update to newest version. First there should be an update ready, then we
+ # do the update, and then there should not be any ready anymore
+
+ "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new
+ "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no update
+ ( ! "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new )
+}
+
+verify_version() {
+ # Expects: version ID + sector offset of both partitions to compare
+ dd if=/var/tmp/72-joined.raw bs=1024 skip="$2" count=1024 | cmp "/var/tmp/72-source/part1-$1.raw"
+ dd if=/var/tmp/72-joined.raw bs=1024 skip="$3" count=1024 | cmp "/var/tmp/72-source/part2-$1.raw"
+ cmp "/var/tmp/72-source/dir-$1/foo.txt" /var/tmp/72-dirs/current/foo.txt
+ cmp "/var/tmp/72-source/dir-$1/bar.txt" /var/tmp/72-dirs/current/bar.txt
+}
+
+# Install initial version and verify
+new_version v1
+update_now
+verify_version v1 1024 3072
+
+# Create second version, update and verify that it is added
+new_version v2
+update_now
+verify_version v2 2048 4096
+
+# Create third version, update and verify it replaced the first version
+new_version v3
+update_now
+verify_version v3 1024 3072
+
+# Create fourth version, and update through a file:// URL. This should be
+# almost as good as testing HTTP, but is simpler for us to set up. file:// is
+# abstracted in curl for us, and since our main goal is to test our own code
+# (and not curl) this test should be quite good even if not comprehensive. This
+# will test the SHA256SUMS logic at least (we turn off GPG validation though,
+# see above)
+new_version v4
+
+cat >/var/tmp/72-defs/02-second.conf <<"EOF"
+[Source]
+Type=url-file
+Path=file:///var/tmp/72-source
+MatchPattern=part2-@v.raw.gz
+
+[Target]
+Type=partition
+Path=/var/tmp/72-joined.raw
+MatchPattern=part2-@v
+MatchPartitionType=root-x86-64-verity
+EOF
+
+cat >/var/tmp/72-defs/03-third.conf <<"EOF"
+[Source]
+Type=url-tar
+Path=file:///var/tmp/72-source
+MatchPattern=dir-@v.tar.gz
+
+[Target]
+Type=directory
+Path=/var/tmp/72-dirs
+CurrentSymlink=/var/tmp/72-dirs/current
+MatchPattern=dir-@v
+InstancesMax=3
+EOF
+
+update_now
+verify_version v4 2048 4096
+
+rm /var/tmp/72-joined.raw
+rm -r /var/tmp/72-dirs /var/tmp/72-defs /var/tmp/72-source
+
+echo OK >/testok
+
+exit 0
diff --git a/test/units/testsuite-73.service b/test/units/testsuite-73.service
new file mode 100644
index 0000000..3ebd24d
--- /dev/null
+++ b/test/units/testsuite-73.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-73-LOCALE
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-73.sh b/test/units/testsuite-73.sh
new file mode 100755
index 0000000..a5d801e
--- /dev/null
+++ b/test/units/testsuite-73.sh
@@ -0,0 +1,407 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+enable_debug() {
+ mkdir -p /run/systemd/system/systemd-localed.service.d
+ cat >>/run/systemd/system/systemd-localed.service.d/override.conf <<EOF
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+
+ mkdir -p /run/systemd/system/systemd-vconsole-setup.service.d
+ cat >>/run/systemd/system/systemd-vconsole-setup.service.d/override.conf <<EOF
+[Unit]
+StartLimitIntervalSec=0
+
+[Service]
+Environment=SYSTEMD_LOG_LEVEL=debug
+EOF
+
+ systemctl daemon-reload
+}
+
+restore_locale() {
+ if [[ -d /usr/lib/locale/xx_XX.UTF-8 ]]; then
+ rmdir /usr/lib/locale/xx_XX.UTF-8
+ fi
+
+ if [[ -f /tmp/locale.conf.bak ]]; then
+ mv /tmp/locale.conf.bak /etc/locale.conf
+ else
+ rm -f /etc/locale.conf
+ fi
+
+ if [[ -f /tmp/default-locale.bak ]]; then
+ mv /tmp/default-locale.bak /etc/default/locale
+ else
+ rm -f /etc/default/locale
+ rmdir --ignore-fail-on-non-empty /etc/default
+ fi
+
+ if [[ -f /tmp/locale.gen.bak ]]; then
+ mv /tmp/locale.gen.bak /etc/locale.gen
+ else
+ rm -f /etc/locale.gen
+ fi
+}
+
+test_locale() {
+ local i output
+
+ if [[ -f /etc/locale.conf ]]; then
+ cp /etc/locale.conf /tmp/locale.conf.bak
+ fi
+
+ # Debian/Ubuntu specific file
+ if [[ -f /etc/default/locale ]]; then
+ cp /etc/default/locale /tmp/default-locale.bak
+ fi
+
+ if [[ -f /etc/locale.gen ]]; then
+ cp /etc/locale.gen /tmp/locale.gen.bak
+ fi
+
+ # remove locale.conf to make /etc/default/locale used by Debian/Ubuntu
+ rm -f /etc/locale.conf
+ # also remove /etc/default/locale
+ rm -f /etc/default/locale
+ # and create /etc/default to make /etc/default/locale created by localed
+ mkdir -p /etc/default
+
+ trap restore_locale RETURN
+
+ if command -v locale-gen >/dev/null 2>&1 &&
+ ! localectl list-locales | grep -F "en_US.UTF-8"; then
+ # ensure at least one utf8 locale exist
+ echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
+ locale-gen en_US.UTF-8
+ fi
+
+ # create invalid locale
+ mkdir -p /usr/lib/locale/xx_XX.UTF-8
+ assert_not_in "xx_XX.UTF-8" "$(localectl list-locales)"
+
+ if [[ -z "$(localectl list-locales)" ]]; then
+ echo "No locale installed, skipping test."
+ return
+ fi
+
+ # start with a known default environment and make sure to also give a
+ # default value to LC_CTYPE= since we're about to also set/unset it. We
+ # also reload PID1 configuration to make sure that PID1 environment itself
+ # is updated as it's not always been the case.
+ assert_rc 0 localectl set-locale "LANG=en_US.UTF-8" "LC_CTYPE=C"
+ systemctl daemon-reload
+ output=$(localectl)
+ assert_in "System Locale: LANG=en_US.UTF-8" "$output"
+ assert_in "LC_CTYPE=C" "$output"
+ output=$(systemctl show-environment)
+ assert_in "LANG=en_US.UTF-8" "$output"
+ assert_in "LC_CTYPE=C" "$output"
+
+ # warn when kernel command line has locale settings
+ output=$(SYSTEMD_PROC_CMDLINE="locale.LANG=C.UTF-8 locale.LC_CTYPE=ja_JP.UTF-8" localectl 2>&1)
+ assert_in "Warning:" "$output"
+ assert_in "Command Line: LANG=C.UTF-8" "$output"
+ assert_in "LC_CTYPE=ja_JP.UTF-8" "$output"
+ assert_in "System Locale:" "$output"
+
+ # change locale
+ for i in $(localectl list-locales); do
+ assert_rc 0 localectl set-locale "LANG=C" "LC_CTYPE=$i"
+ if [[ -f /etc/default/locale ]]; then
+ assert_eq "$(cat /etc/default/locale)" "LANG=C
+LC_CTYPE=$i"
+ else
+ assert_eq "$(cat /etc/locale.conf)" "LANG=C
+LC_CTYPE=$i"
+ fi
+ output=$(localectl)
+ assert_in "System Locale: LANG=C" "$output"
+ assert_in "LC_CTYPE=$i" "$output"
+ output=$(systemctl show-environment)
+ assert_in "LANG=C" "$output"
+ assert_in "LC_CTYPE=$i" "$output"
+
+ assert_rc 0 localectl set-locale "$i"
+ if [[ -f /etc/default/locale ]]; then
+ assert_eq "$(cat /etc/default/locale)" "LANG=$i"
+ else
+ assert_eq "$(cat /etc/locale.conf)" "LANG=$i"
+ fi
+ output=$(localectl)
+ assert_in "System Locale: LANG=$i" "$output"
+ assert_not_in "LC_CTYPE=" "$output"
+ output=$(systemctl show-environment)
+ assert_in "LANG=$i" "$output"
+ assert_not_in "LC_CTYPE=" "$output"
+ done
+
+ # test if localed auto-runs locale-gen
+ if command -v locale-gen >/dev/null 2>&1 &&
+ ! localectl list-locales | grep -F "de_DE.UTF-8"; then
+
+ # clear previous locale
+ systemctl stop systemd-localed.service
+ rm -f /etc/locale.conf /etc/default/locale
+
+ # change locale
+ assert_rc 0 localectl set-locale de_DE.UTF-8
+ if [[ -f /etc/default/locale ]]; then
+ assert_eq "$(cat /etc/default/locale)" "LANG=de_DE.UTF-8"
+ else
+ assert_eq "$(cat /etc/locale.conf)" "LANG=de_DE.UTF-8"
+ fi
+ assert_in "System Locale: LANG=de_DE.UTF-8" "$(localectl)"
+ assert_in "LANG=de_DE.UTF-8" "$(systemctl show-environment)"
+
+ # ensure tested locale exists and works now
+ assert_in "de_DE.UTF-8" "$(localectl list-locales)"
+ fi
+}
+
+backup_keymap() {
+ if [[ -f /etc/vconsole.conf ]]; then
+ cp /etc/vconsole.conf /tmp/vconsole.conf.bak
+ fi
+
+ if [[ -f /etc/X11/xorg.conf.d/00-keyboard.conf ]]; then
+ cp /etc/X11/xorg.conf.d/00-keyboard.conf /tmp/00-keyboard.conf.bak
+ fi
+
+ # Debian/Ubuntu specific file
+ if [[ -f /etc/default/keyboard ]]; then
+ cp /etc/default/keyboard /tmp/default-keyboard.bak
+ fi
+
+ mkdir -p /etc/default
+}
+
+restore_keymap() {
+ if [[ -f /tmp/vconsole.conf.bak ]]; then
+ mv /tmp/vconsole.conf.bak /etc/vconsole.conf
+ else
+ rm -f /etc/vconsole.conf
+ fi
+
+ if [[ -f /tmp/00-keyboard.conf.bak ]]; then
+ mv /tmp/00-keyboard.conf.bak /etc/X11/xorg.conf.d/00-keyboard.conf
+ else
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf
+ fi
+
+ if [[ -f /tmp/default-keyboard.bak ]]; then
+ mv /tmp/default-keyboard.bak /etc/default/keyboard
+ else
+ rm -f /etc/default/keyboard
+ rmdir --ignore-fail-on-non-empty /etc/default
+ fi
+}
+
+wait_vconsole_setup() {
+ local i ss
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
+ ss="$(systemctl --property SubState --value show systemd-vconsole-setup.service)"
+ if [[ "$ss" == "exited" || "$ss" == "dead" || "$ss" == "condition" ]]; then
+ return 0
+ elif [[ "$ss" == "failed" ]]; then
+ echo "WARNING: systemd-vconsole-setup.service failed, ignoring." >&2
+ systemctl reset-failed systemd-vconsole-setup.service
+ return 0
+ fi
+ done
+
+ systemctl status systemd-vconsole-setup.service
+ return 1
+}
+
+test_vc_keymap() {
+ local i output
+
+ if [[ -z "$(localectl list-keymaps)" ]]; then
+ echo "No vconsole keymap installed, skipping test."
+ return
+ fi
+
+ backup_keymap
+ trap restore_keymap RETURN
+
+ # should activate daemon and work
+ assert_in "VC Keymap:" "$(localectl)"
+
+ for i in $(localectl list-keymaps); do
+ # clear previous conversion from VC -> X11 keymap
+ systemctl stop systemd-localed.service
+ wait_vconsole_setup
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+ # set VC keymap
+ assert_rc 0 localectl set-keymap "$i"
+ output=$(localectl)
+
+ # check VC keymap
+ assert_in "KEYMAP=$i" "$(cat /etc/vconsole.conf)"
+ assert_in "VC Keymap: $i" "$output"
+
+ # check VC -> X11 keymap conversion
+ if [[ "$i" == "us" ]]; then
+ assert_in "X11 Layout: us" "$output"
+ assert_in "X11 Model: pc105\+inet" "$output"
+ assert_not_in "X11 Variant:" "$output"
+ assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output"
+ elif [[ "$i" == "us-acentos" ]]; then
+ assert_in "X11 Layout: us" "$output"
+ assert_in 'X11 Model: pc105$' "$output"
+ assert_in "X11 Variant: intl" "$output"
+ assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output"
+ elif [[ "$i" =~ ^us-.* ]]; then
+ assert_in "X11 Layout: .unset." "$output"
+ assert_not_in "X11 Model:" "$output"
+ assert_not_in "X11 Variant:" "$output"
+ assert_not_in "X11 Options:" "$output"
+ fi
+ done
+
+ # gets along without config file
+ systemctl stop systemd-localed.service
+ wait_vconsole_setup
+ rm -f /etc/vconsole.conf
+ assert_in "VC Keymap: .unset." "$(localectl)"
+}
+
+test_x11_keymap() {
+ local output
+
+ if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then
+ echo "No x11 keymap installed, skipping test."
+ return
+ fi
+
+ backup_keymap
+ trap restore_keymap RETURN
+
+ # should activate daemon and work
+ assert_in "X11 Layout:" "$(localectl)"
+
+ # set x11 keymap (layout, model, variant, options)
+ assert_rc 0 localectl set-x11-keymap us pc105+inet intl terminate:ctrl_alt_bksp
+
+ if [[ -f /etc/default/keyboard ]]; then
+ assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us
+XKBMODEL=pc105+inet
+XKBVARIANT=intl
+XKBOPTIONS=terminate:ctrl_alt_bksp"
+ else
+ output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf)
+ assert_in 'Option "XkbLayout" "us"' "$output"
+ assert_in 'Option "XkbModel" "pc105\+inet"' "$output"
+ assert_in 'Option "XkbVariant" "intl"' "$output"
+ assert_in 'Option "XkbOptions" "terminate:ctrl_alt_bksp"' "$output"
+ fi
+
+ output=$(localectl)
+ assert_in "X11 Layout: us" "$output"
+ assert_in "X11 Model: pc105\+inet" "$output"
+ assert_in "X11 Variant: intl" "$output"
+ assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output"
+
+ # Debian/Ubuntu patch is buggy, unspecified settings are not cleared
+ rm -f /etc/default/keyboard
+
+ # set x11 keymap (layout, model, variant)
+ assert_rc 0 localectl set-x11-keymap us pc105+inet intl
+
+ if [[ -f /etc/default/keyboard ]]; then
+ assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us
+XKBMODEL=pc105+inet
+XKBVARIANT=intl"
+ else
+ output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf)
+ assert_in 'Option "XkbLayout" "us"' "$output"
+ assert_in 'Option "XkbModel" "pc105\+inet"' "$output"
+ assert_in 'Option "XkbVariant" "intl"' "$output"
+ assert_not_in 'Option "XkbOptions"' "$output"
+ fi
+
+ output=$(localectl)
+ assert_in "X11 Layout: us" "$output"
+ assert_in "X11 Model: pc105\+inet" "$output"
+ assert_in "X11 Variant: intl" "$output"
+ assert_not_in "X11 Options:" "$output"
+
+ # Debian/Ubuntu patch is buggy, unspecified settings are not cleared
+ rm -f /etc/default/keyboard
+
+ # set x11 keymap (layout, model)
+ assert_rc 0 localectl set-x11-keymap us pc105+inet
+
+ if [[ -f /etc/default/keyboard ]]; then
+ assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us
+XKBMODEL=pc105+inet"
+ else
+ output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf)
+ assert_in 'Option "XkbLayout" "us"' "$output"
+ assert_in 'Option "XkbModel" "pc105\+inet"' "$output"
+ assert_not_in 'Option "XkbVariant"' "$output"
+ assert_not_in 'Option "XkbOptions"' "$output"
+ fi
+
+ output=$(localectl)
+ assert_in "X11 Layout: us" "$output"
+ assert_in "X11 Model: pc105\+inet" "$output"
+ assert_not_in "X11 Variant:" "$output"
+ assert_not_in "X11 Options:" "$output"
+
+ # Debian/Ubuntu patch is buggy, unspecified settings are not cleared
+ rm -f /etc/default/keyboard
+
+ # set x11 keymap (layout)
+ assert_rc 0 localectl set-x11-keymap us
+
+ if [[ -f /etc/default/keyboard ]]; then
+ assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us"
+ else
+ output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf)
+ assert_in 'Option "XkbLayout" "us"' "$output"
+ assert_not_in 'Option "XkbModel"' "$output"
+ assert_not_in 'Option "XkbVariant"' "$output"
+ assert_not_in 'Option "XkbOptions"' "$output"
+ fi
+
+ output=$(localectl)
+ assert_in "X11 Layout: us" "$output"
+ assert_not_in "X11 Model:" "$output"
+ assert_not_in "X11 Variant:" "$output"
+ assert_not_in "X11 Options:" "$output"
+
+ # gets along without config file
+ systemctl stop systemd-localed.service
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+ output=$(localectl)
+ assert_in "X11 Layout: .unset." "$output"
+ assert_not_in "X11 Model:" "$output"
+ assert_not_in "X11 Variant:" "$output"
+ assert_not_in "X11 Options:" "$output"
+}
+
+: >/failed
+
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version intalled on the distro where the testsuite is
+# running on.
+export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map
+
+enable_debug
+test_locale
+test_vc_keymap
+test_x11_keymap
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-74.cgls.sh b/test/units/testsuite-74.cgls.sh
new file mode 100755
index 0000000..9268f42
--- /dev/null
+++ b/test/units/testsuite-74.cgls.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-cgls
+systemd-cgls --all --full
+systemd-cgls -k
+systemd-cgls --xattr=yes
+systemd-cgls --xattr=no
+systemd-cgls --cgroup-id=yes
+systemd-cgls --cgroup-id=no
+
+systemd-cgls /system.slice/systemd-journald.service
+systemd-cgls /system.slice/systemd-journald.service /init.scope
+systemd-cgls /sys/fs/cgroup/system.slice/systemd-journald.service /init.scope
+[[ -d /sys/fs/cgroup/init.scope ]] && init_scope="init.scope" || init_scope="systemd/init.scope"
+(cd "/sys/fs/cgroup/$init_scope" && systemd-cgls)
+systemd-cgls --unit=systemd-journald.service
+# There's most likely no user session running, so we need to create one
+systemd-run --user --wait --pipe -M testuser@.host systemd-cgls --user-unit=app.slice
+
+(! systemd-cgls /foo/bar)
+(! systemd-cgls --unit=hello.world)
+(! systemd-cgls --user-unit=hello.world)
+(! systemd-cgls --xattr=foo)
+(! systemd-cgls --cgroup-id=foo)
diff --git a/test/units/testsuite-74.cgtop.sh b/test/units/testsuite-74.cgtop.sh
new file mode 100755
index 0000000..cf98279
--- /dev/null
+++ b/test/units/testsuite-74.cgtop.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Without tty attached cgtop should default to --iterations=1
+systemd-cgtop
+systemd-cgtop --iterations=1
+# Same as --iterations=1
+systemd-cgtop -1
+systemd-cgtop --delay=1ms
+systemd-cgtop --raw
+systemd-cgtop --batch
+systemd-cgtop --cpu=percentage
+systemd-cgtop --cpu=time
+systemd-cgtop -P
+systemd-cgtop -k
+systemd-cgtop --recursive=no -P
+systemd-cgtop --recursive=no -k
+systemd-cgtop --depth=0
+systemd-cgtop --depth=100
+
+for order in path tasks cpu memory io; do
+ systemd-cgtop --order="$order"
+done
+systemd-cgtop -p -t -c -m -i
+
+(! systemd-cgtop --cpu=foo)
+(! systemd-cgtop --order=foo)
+(! systemd-cgtop --depth=-1)
+(! systemd-cgtop --recursive=foo)
+(! systemd-cgtop --delay=1foo)
diff --git a/test/units/testsuite-74.delta.sh b/test/units/testsuite-74.delta.sh
new file mode 100755
index 0000000..a0e1cb5
--- /dev/null
+++ b/test/units/testsuite-74.delta.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+at_exit() {
+ rm -rfv /{run,etc}/systemd/system/delta-test*
+}
+
+trap at_exit EXIT
+
+# Create a couple of supporting units with overrides
+#
+# Extended unit
+cat >"/run/systemd/system/delta-test-unit-extended.service" <<EOF
+[Service]
+ExecStart=/bin/true
+EOF
+mkdir -p "/run/systemd/system/delta-test-unit-extended.service.d"
+cat >"/run/systemd/system/delta-test-unit-extended.service.d/override.conf" <<EOF
+[Unit]
+Description=Foo Bar
+[Service]
+ExecStartPre=/bin/true
+EOF
+# Masked unit
+cp -fv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-masked.service
+systemctl mask delta-test-unit-masked.service
+# Overridden unit
+cp -fv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-overridden.service
+cp -fv /run/systemd/system/delta-test-unit-overridden.service /etc/systemd/system/delta-test-unit-overridden.service
+echo "ExecStartPost=/bin/true" >>/etc/systemd/system/delta-test-unit-overridden.service
+# Overridden but equivalent unit
+ln -srfv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-equivalent.service
+ln -sfv /run/systemd/system/delta-test-unit-extended.service /etc/systemd/system/delta-test-unit-equivalent.service
+# Redirected unit
+ln -srfv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-redirected.service
+ln -sfv /run/systemd/system/delta-test-unit-overidden.service /etc/systemd/system/delta-test-unit-extended.service
+
+systemctl daemon-reload
+
+systemd-delta
+systemd-delta /run
+systemd-delta systemd/system
+systemd-delta /run systemd/system /run
+systemd-delta /run foo/bar hello/world systemd/system /run
+systemd-delta foo/bar
+systemd-delta --diff=true
+systemd-delta --diff=false
+
+for type in masked equivalent redirected overridden extended unchanged; do
+ systemd-delta --type="$type"
+ systemd-delta --type="$type" /run
+done
+systemd-delta --type=equivalent,redirected
+
+(! systemd-delta --diff=foo)
+(! systemd-delta --type=foo)
+(! systemd-delta --type=equivalent,redirected,foo)
diff --git a/test/units/testsuite-74.firstboot.sh b/test/units/testsuite-74.firstboot.sh
new file mode 100755
index 0000000..92a6075
--- /dev/null
+++ b/test/units/testsuite-74.firstboot.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+if ! command -v systemd-firstboot >/dev/null; then
+ echo "systemd-firstboot not found, skipping the test"
+ exit 0
+fi
+
+at_exit() {
+ if [[ -n "${ROOT:-}" ]]; then
+ ls -lR "$ROOT"
+ rm -fr "$ROOT"
+ fi
+}
+
+trap at_exit EXIT
+
+# Generated via `mkpasswd -m sha-512 -S foobarsalt password1`
+# shellcheck disable=SC2016
+ROOT_HASHED_PASSWORD1='$6$foobarsalt$YbwdaATX6IsFxvWbY3QcZj2gB31R/LFRFrjlFrJtTTqFtSfn4dfOAg/km2k4Sl.a2g7LOYDo31wMTaEsCo9j41'
+# Generated via `mkpasswd -m sha-512 -S foobarsalt password2`
+# shellcheck disable=SC2016
+ROOT_HASHED_PASSWORD2='$6$foobarsalt$q.P2932zYMLbKnjFwIxPI8y3iuxeuJ2BgE372LcZMMnj3Gcg/9mJg2LPKUl.ha0TG/.fRNNnRQcLfzM0SNot3.'
+
+# Debian and Ubuntu use /etc/default/locale instead of /etc/locale.conf. Make
+# sure we use the appropriate path for locale configuration.
+LOCALE_PATH="/etc/locale.conf"
+[ -e "$LOCALE_PATH" ] || LOCALE_PATH="/etc/default/locale"
+[ -e "$LOCALE_PATH" ] || systemd-firstboot --locale=C.UTF-8
+
+# Create a minimal root so we don't modify the testbed
+ROOT=test-root
+mkdir -p "$ROOT/bin"
+# Dummy shell for --root-shell=
+touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell"
+
+systemd-firstboot --root="$ROOT" --locale=foo
+grep -q "LANG=foo" "$ROOT$LOCALE_PATH"
+rm -fv "$ROOT$LOCALE_PATH"
+systemd-firstboot --root="$ROOT" --locale-messages=foo
+grep -q "LC_MESSAGES=foo" "$ROOT$LOCALE_PATH"
+rm -fv "$ROOT$LOCALE_PATH"
+systemd-firstboot --root="$ROOT" --locale=foo --locale-messages=bar
+grep -q "LANG=foo" "$ROOT$LOCALE_PATH"
+grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH"
+
+systemd-firstboot --root="$ROOT" --keymap=foo
+grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf"
+
+systemd-firstboot --root="$ROOT" --timezone=Europe/Berlin
+readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin"
+
+systemd-firstboot --root="$ROOT" --hostname "foobar"
+grep -q "foobar" "$ROOT/etc/hostname"
+
+systemd-firstboot --root="$ROOT" --machine-id=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id"
+
+rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow"
+systemd-firstboot --root="$ROOT" --root-password=foo
+grep -q "^root:x:0:0:" "$ROOT/etc/passwd"
+grep -q "^root:" "$ROOT/etc/shadow"
+rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow"
+echo "foo" >root.passwd
+systemd-firstboot --root="$ROOT" --root-password-file=root.passwd
+grep -q "^root:x:0:0:" "$ROOT/etc/passwd"
+grep -q "^root:" "$ROOT/etc/shadow"
+rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" root.passwd
+# Set the shell together with the password, as firstboot won't touch
+# /etc/passwd if it already exists
+systemd-firstboot --root="$ROOT" --root-password-hashed="$ROOT_HASHED_PASSWORD1" --root-shell=/bin/fooshell
+grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
+grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow"
+
+systemd-firstboot --root="$ROOT" --kernel-command-line="foo.bar=42"
+grep -q "foo.bar=42" "$ROOT/etc/kernel/cmdline"
+
+# Configs should not get overwritten if they exist unless --force is used
+systemd-firstboot --root="$ROOT" \
+ --locale=locale-overwrite \
+ --locale-messages=messages-overwrite \
+ --keymap=keymap-overwrite \
+ --timezone=CET \
+ --hostname=hostname-overwrite \
+ --machine-id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
+ --root-password-hashed="$ROOT_HASHED_PASSWORD2" \
+ --root-shell=/bin/barshell \
+ --kernel-command-line="hello.world=0"
+grep -q "LANG=foo" "$ROOT$LOCALE_PATH"
+grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH"
+grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf"
+readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$"
+grep -q "foobar" "$ROOT/etc/hostname"
+grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id"
+grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
+grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow"
+grep -q "foo.bar=42" "$ROOT/etc/kernel/cmdline"
+
+# The same thing, but now with --force
+systemd-firstboot --root="$ROOT" --force \
+ --locale=locale-overwrite \
+ --locale-messages=messages-overwrite \
+ --keymap=keymap-overwrite \
+ --timezone=CET \
+ --hostname=hostname-overwrite \
+ --machine-id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
+ --root-password-hashed="$ROOT_HASHED_PASSWORD2" \
+ --root-shell=/bin/barshell \
+ --kernel-command-line="hello.world=0"
+grep -q "LANG=locale-overwrite" "$ROOT$LOCALE_PATH"
+grep -q "LC_MESSAGES=messages-overwrite" "$ROOT$LOCALE_PATH"
+grep -q "KEYMAP=keymap-overwrite" "$ROOT/etc/vconsole.conf"
+readlink "$ROOT/etc/localtime" | grep -q "/CET$"
+grep -q "hostname-overwrite" "$ROOT/etc/hostname"
+grep -q "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "$ROOT/etc/machine-id"
+grep -q "^root:x:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd"
+grep -q "^root:$ROOT_HASHED_PASSWORD2:" "$ROOT/etc/shadow"
+grep -q "hello.world=0" "$ROOT/etc/kernel/cmdline"
+
+# --copy-* options
+rm -fr "$ROOT"
+mkdir "$ROOT"
+# Copy everything at once (--copy)
+systemd-firstboot --root="$ROOT" --copy
+diff $LOCALE_PATH "$ROOT$LOCALE_PATH"
+diff <(awk -F: '/^root/ { print $7; }' /etc/passwd) <(awk -F: '/^root/ { print $7; }' "$ROOT/etc/passwd")
+diff <(awk -F: '/^root/ { print $2; }' /etc/shadow) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow")
+[[ -e /etc/vconsole.conf ]] && diff /etc/vconsole.conf "$ROOT/etc/vconsole.conf"
+[[ -e /etc/localtime ]] && diff <(readlink /etc/localtime) <(readlink "$ROOT/etc/localtime")
+rm -fr "$ROOT"
+mkdir "$ROOT"
+# Copy everything at once, but now by using separate switches
+systemd-firstboot --root="$ROOT" --copy-locale --copy-keymap --copy-timezone --copy-root-password --copy-root-shell
+diff $LOCALE_PATH "$ROOT$LOCALE_PATH"
+diff <(awk -F: '/^root/ { print $7; }' /etc/passwd) <(awk -F: '/^root/ { print $7; }' "$ROOT/etc/passwd")
+diff <(awk -F: '/^root/ { print $2; }' /etc/shadow) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow")
+[[ -e /etc/vconsole.conf ]] && diff /etc/vconsole.conf "$ROOT/etc/vconsole.conf"
+[[ -e /etc/localtime ]] && diff <(readlink /etc/localtime) <(readlink "$ROOT/etc/localtime")
+
+# --prompt-* options
+rm -fr "$ROOT"
+mkdir -p "$ROOT/bin"
+touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell"
+# Temporarily disable pipefail to avoid `echo: write error: Broken pipe
+set +o pipefail
+# We can do only limited testing here, since it's all an interactive stuff,
+# so --prompt and --prompt-root-password are skipped on purpose
+echo -ne "\nfoo\nbar\n" | systemd-firstboot --root="$ROOT" --prompt-locale
+grep -q "LANG=foo" "$ROOT$LOCALE_PATH"
+grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH"
+# systemd-firstboot in prompt-keymap mode requires keymaps to be installed so
+# it can present them as a list to the user. As Debian does not ship/provide
+# compatible keymaps (from the kbd package), skip this test if the keymaps are
+# missing.
+if [ -d "/usr/share/keymaps/" ] || [ -d "/usr/share/kbd/keymaps/" ] || [ -d "/usr/lib/kbd/keymaps/" ] ; then
+ echo -ne "\nfoo\n" | systemd-firstboot --root="$ROOT" --prompt-keymap
+ grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf"
+fi
+echo -ne "\nEurope/Berlin\n" | systemd-firstboot --root="$ROOT" --prompt-timezone
+readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$"
+echo -ne "\nfoobar\n" | systemd-firstboot --root="$ROOT" --prompt-hostname
+grep -q "foobar" "$ROOT/etc/hostname"
+echo -ne "\n/bin/fooshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
+grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
+# Existing files should not get overwritten
+echo -ne "\n/bin/barshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
+grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
+# Now without the welcome screen but with force
+echo -ne "/bin/barshell\n" | systemd-firstboot --root="$ROOT" --force --prompt-root-shell --welcome=no
+grep -q "^root:.*:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd"
+# Re-enable pipefail
+set -o pipefail
+
+# Assorted tests
+rm -fr "$ROOT"
+mkdir "$ROOT"
+
+systemd-firstboot --root="$ROOT" --setup-machine-id
+grep -E "[a-z0-9]{32}" "$ROOT/etc/machine-id"
+
+systemd-firstboot --root="$ROOT" --delete-root-password
+diff <(echo) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow")
+
+(! systemd-firstboot --root="$ROOT" --root-shell=/bin/nonexistentshell)
+(! systemd-firstboot --root="$ROOT" --machine-id=invalidmachineid)
+(! systemd-firstboot --root="$ROOT" --timezone=Foo/Bar)
diff --git a/test/units/testsuite-74.service b/test/units/testsuite-74.service
new file mode 100644
index 0000000..f782132
--- /dev/null
+++ b/test/units/testsuite-74.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-74-AUX-UTILS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-74.sh b/test/units/testsuite-74.sh
new file mode 100755
index 0000000..13c767e
--- /dev/null
+++ b/test/units/testsuite-74.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+for script in "${0%.sh}".*.sh; do
+ echo "Running $script"
+ "./$script"
+done
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-75.service b/test/units/testsuite-75.service
new file mode 100644
index 0000000..1b0cd56
--- /dev/null
+++ b/test/units/testsuite-75.service
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Tests for systemd-resolved
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+StandardOutput=journal+console
+StandardError=journal+console
diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh
new file mode 100755
index 0000000..eb24b70
--- /dev/null
+++ b/test/units/testsuite-75.sh
@@ -0,0 +1,320 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# vi: ts=4 sw=4 tw=0 et:
+
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+: >/failed
+
+RUN_OUT="$(mktemp)"
+
+run() {
+ "$@" |& tee "$RUN_OUT"
+}
+
+monitor_check_rr() (
+ set +x
+ set +o pipefail
+ local since="${1:?}"
+ local match="${2:?}"
+
+ # Wait until the first mention of the specified log message is
+ # displayed. We turn off pipefail for this, since we don't care about the
+ # lhs of this pipe expression, we only care about the rhs' result to be
+ # clean
+ journalctl -u resmontest.service --since "$since" -f --full | grep -m1 "$match"
+)
+
+# Test for resolvectl, resolvconf
+systemctl unmask systemd-resolved.service
+systemctl enable --now systemd-resolved.service
+systemctl service-log-level systemd-resolved.service debug
+ip link add hoge type dummy
+ip link add hoge.foo type dummy
+resolvectl dns hoge 10.0.0.1 10.0.0.2
+resolvectl dns hoge.foo 10.0.0.3 10.0.0.4
+assert_in '10.0.0.1 10.0.0.2' "$(resolvectl dns hoge)"
+assert_in '10.0.0.3 10.0.0.4' "$(resolvectl dns hoge.foo)"
+resolvectl dns hoge 10.0.1.1 10.0.1.2
+resolvectl dns hoge.foo 10.0.1.3 10.0.1.4
+assert_in '10.0.1.1 10.0.1.2' "$(resolvectl dns hoge)"
+assert_in '10.0.1.3 10.0.1.4' "$(resolvectl dns hoge.foo)"
+if ! RESOLVCONF=$(command -v resolvconf 2>/dev/null); then
+ TMPDIR=$(mktemp -d -p /tmp resolvconf-tests.XXXXXX)
+ RESOLVCONF="$TMPDIR"/resolvconf
+ ln -s "$(command -v resolvectl 2>/dev/null)" "$RESOLVCONF"
+fi
+echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge
+echo nameserver 10.0.2.3 10.0.2.4 | "$RESOLVCONF" -a hoge.foo
+assert_in '10.0.2.1 10.0.2.2' "$(resolvectl dns hoge)"
+assert_in '10.0.2.3 10.0.2.4' "$(resolvectl dns hoge.foo)"
+echo nameserver 10.0.3.1 10.0.3.2 | "$RESOLVCONF" -a hoge.inet.ipsec.192.168.35
+echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp
+assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)"
+assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)"
+ip link del hoge
+ip link del hoge.foo
+
+### SETUP ###
+# Configure network
+hostnamectl hostname ns1.unsigned.test
+echo "10.0.0.1 ns1.unsigned.test" >>/etc/hosts
+
+mkdir -p /etc/systemd/network
+cat >/etc/systemd/network/dns0.netdev <<EOF
+[NetDev]
+Name=dns0
+Kind=dummy
+EOF
+cat >/etc/systemd/network/dns0.network <<EOF
+[Match]
+Name=dns0
+
+[Network]
+Address=10.0.0.1/24
+DNSSEC=allow-downgrade
+DNS=10.0.0.1
+EOF
+
+{
+ echo "FallbackDNS="
+ echo "DNSSEC=allow-downgrade"
+ echo "DNSOverTLS=opportunistic"
+} >>/etc/systemd/resolved.conf
+ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
+# Override the default NTA list, which turns off DNSSEC validation for (among
+# others) the test. domain
+mkdir -p "/etc/dnssec-trust-anchors.d/"
+echo local >/etc/dnssec-trust-anchors.d/local.negative
+
+# Sign the root zone
+keymgr . generate algorithm=ECDSAP256SHA256 ksk=yes zsk=yes
+# Create a trust anchor for resolved with our root zone
+keymgr . ds | sed 's/ DS/ IN DS/g' >/etc/dnssec-trust-anchors.d/root.positive
+# Create a bind-compatible trust anchor (for delv)
+# Note: the trust-anchors directive is relatively new, so use the original
+# managed-keys one until it's widespread enough
+{
+ echo 'managed-keys {'
+ keymgr . dnskey | sed -r 's/^\. DNSKEY ([0-9]+ [0-9]+ [0-9]+) (.+)$/. static-key \1 "\2";/g'
+ echo '};'
+} >/etc/bind.keys
+# Create an /etc/bind/bind.keys symlink, which is used by delv on Ubuntu
+mkdir -p /etc/bind
+ln -svf /etc/bind.keys /etc/bind/bind.keys
+
+# Start the services
+systemctl unmask systemd-networkd
+systemctl start systemd-networkd
+systemctl restart systemd-resolved
+# Create knot's runtime dir, since from certain version it's provided only by
+# the package and not created by tmpfiles/systemd
+if [[ ! -d /run/knot ]]; then
+ mkdir -p /run/knot
+ chown -R knot:knot /run/knot
+fi
+systemctl start knot
+# Wait a bit for the keys to propagate
+sleep 4
+
+networkctl status
+resolvectl status
+resolvectl log-level debug
+
+# Start monitoring queries
+systemd-run -u resmontest.service -p Type=notify resolvectl monitor
+# Wait for the monitoring service to become active
+for _ in {0..9}; do
+ [[ "$(systemctl show -P ActiveState resmontest.service)" == "active" ]] && break
+ sleep .5
+done
+
+# We need to manually propagate the DS records of onlinesign.test. to the parent
+# zone, since they're generated online
+knotc zone-begin test.
+if knotc zone-get test. onlinesign.test. ds | grep .; then
+ # Drop any old DS records, if present (e.g. on test re-run)
+ knotc zone-unset test. onlinesign.test. ds
+fi
+# Propagate the new DS records
+while read -ra line; do
+ knotc zone-set test. "${line[0]}" 600 "${line[@]:1}"
+done < <(keymgr onlinesign.test. ds)
+knotc zone-commit test.
+
+knotc reload
+
+### SETUP END ###
+
+: "--- nss-resolve/nss-myhostname tests"
+# Sanity check
+TIMESTAMP=$(date '+%F %T')
+run getent -s resolve hosts ns1.unsigned.test
+grep -qE "^10\.0\.0\.1\s+ns1\.unsigned\.test" "$RUN_OUT"
+monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN A 10.0.0.1"
+
+# Issue: https://github.com/systemd/systemd/issues/18812
+# PR: https://github.com/systemd/systemd/pull/18896
+# Follow-up issue: https://github.com/systemd/systemd/issues/23152
+# Follow-up PR: https://github.com/systemd/systemd/pull/23161
+# With IPv6 enabled
+run getent -s resolve hosts localhost
+grep -qE "^::1\s+localhost" "$RUN_OUT"
+run getent -s myhostname hosts localhost
+grep -qE "^::1\s+localhost" "$RUN_OUT"
+# With IPv6 disabled
+sysctl -w net.ipv6.conf.all.disable_ipv6=1
+run getent -s resolve hosts localhost
+grep -qE "^127\.0\.0\.1\s+localhost" "$RUN_OUT"
+run getent -s myhostname hosts localhost
+grep -qE "^127\.0\.0\.1\s+localhost" "$RUN_OUT"
+sysctl -w net.ipv6.conf.all.disable_ipv6=0
+
+
+: "--- Basic resolved tests ---"
+# Issue: https://github.com/systemd/systemd/issues/22229
+# PR: https://github.com/systemd/systemd/pull/22231
+FILTERED_NAMES=(
+ "0.in-addr.arpa"
+ "255.255.255.255.in-addr.arpa"
+ "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"
+ "hello.invalid"
+)
+
+for name in "${FILTERED_NAMES[@]}"; do
+ (! run host "$name")
+ grep -qF "NXDOMAIN" "$RUN_OUT"
+done
+
+# Follow-up
+# Issue: https://github.com/systemd/systemd/issues/22401
+# PR: https://github.com/systemd/systemd/pull/22414
+run dig +noall +authority +comments SRV .
+grep -qF "status: NOERROR" "$RUN_OUT"
+grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT"
+
+
+: "--- ZONE: unsigned.test. ---"
+run dig @10.0.0.1 +short unsigned.test
+grep -qF "10.0.0.101" "$RUN_OUT"
+run resolvectl query unsigned.test
+grep -qF "unsigned.test: 10.0.0.10" "$RUN_OUT"
+grep -qF "authenticated: no" "$RUN_OUT"
+run dig @10.0.0.1 +short MX unsigned.test
+grep -qF "15 mail.unsigned.test." "$RUN_OUT"
+run resolvectl query --legend=no -t MX unsigned.test
+grep -qF "unsigned.test IN MX 15 mail.unsigned.test" "$RUN_OUT"
+
+
+: "--- ZONE: signed.test (static DNSSEC) ---"
+# Check the trust chain (with and without systemd-resolved in between
+# Issue: https://github.com/systemd/systemd/issues/22002
+# PR: https://github.com/systemd/systemd/pull/23289
+run delv @10.0.0.1 signed.test
+grep -qF "; fully validated" "$RUN_OUT"
+run delv signed.test
+grep -qF "; fully validated" "$RUN_OUT"
+
+run dig +short signed.test
+grep -qF "10.0.0.10" "$RUN_OUT"
+run resolvectl query signed.test
+grep -qF "signed.test: 10.0.0.10" "$RUN_OUT"
+grep -qF "authenticated: yes" "$RUN_OUT"
+run dig @10.0.0.1 +short MX signed.test
+grep -qF "10 mail.signed.test." "$RUN_OUT"
+run resolvectl query --legend=no -t MX signed.test
+grep -qF "signed.test IN MX 10 mail.signed.test" "$RUN_OUT"
+# Check a non-existent domain
+run dig +dnssec this.does.not.exist.signed.test
+grep -qF "status: NXDOMAIN" "$RUN_OUT"
+# Check a wildcard record
+run resolvectl query -t TXT this.should.be.authenticated.wild.signed.test
+grep -qF 'this.should.be.authenticated.wild.signed.test IN TXT "this is a wildcard"' "$RUN_OUT"
+grep -qF "authenticated: yes" "$RUN_OUT"
+
+# DNSSEC validation with multiple records of the same type for the same name
+# Issue: https://github.com/systemd/systemd/issues/22002
+# PR: https://github.com/systemd/systemd/pull/23289
+run delv @10.0.0.1 dupe.signed.test
+grep -qF "; fully validated" "$RUN_OUT"
+run delv dupe.signed.test
+grep -qF "; fully validated" "$RUN_OUT"
+
+# Test resolution of CNAME chains
+TIMESTAMP=$(date '+%F %T')
+run resolvectl query -t A cname-chain.signed.test
+grep -qF "follow14.final.signed.test IN A 10.0.0.14" "$RUN_OUT"
+grep -qF "authenticated: yes" "$RUN_OUT"
+
+monitor_check_rr "$TIMESTAMP" "follow10.so.close.signed.test IN CNAME follow11.yet.so.far.signed.test"
+monitor_check_rr "$TIMESTAMP" "follow11.yet.so.far.signed.test IN CNAME follow12.getting.hot.signed.test"
+monitor_check_rr "$TIMESTAMP" "follow12.getting.hot.signed.test IN CNAME follow13.almost.final.signed.test"
+monitor_check_rr "$TIMESTAMP" "follow13.almost.final.signed.test IN CNAME follow14.final.signed.test"
+monitor_check_rr "$TIMESTAMP" "follow14.final.signed.test IN A 10.0.0.14"
+
+# Non-existing RR + CNAME chain
+run dig +dnssec AAAA cname-chain.signed.test
+grep -qF "status: NOERROR" "$RUN_OUT"
+grep -qE "^follow14\.final\.signed\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
+
+
+: "--- ZONE: onlinesign.test (dynamic DNSSEC) ---"
+# Check the trust chain (with and without systemd-resolved in between
+# Issue: https://github.com/systemd/systemd/issues/22002
+# PR: https://github.com/systemd/systemd/pull/23289
+run delv @10.0.0.1 sub.onlinesign.test
+grep -qF "; fully validated" "$RUN_OUT"
+run delv sub.onlinesign.test
+grep -qF "; fully validated" "$RUN_OUT"
+
+run dig +short sub.onlinesign.test
+grep -qF "10.0.0.133" "$RUN_OUT"
+run resolvectl query sub.onlinesign.test
+grep -qF "sub.onlinesign.test: 10.0.0.133" "$RUN_OUT"
+grep -qF "authenticated: yes" "$RUN_OUT"
+run dig @10.0.0.1 +short TXT onlinesign.test
+grep -qF '"hello from onlinesign"' "$RUN_OUT"
+run resolvectl query --legend=no -t TXT onlinesign.test
+grep -qF 'onlinesign.test IN TXT "hello from onlinesign"' "$RUN_OUT"
+# Check a non-existent domain
+# Note: mod-onlinesign utilizes Minimally Covering NSEC Records, hence the
+# different response than with "standard" DNSSEC
+run dig +dnssec this.does.not.exist.onlinesign.test
+grep -qF "status: NOERROR" "$RUN_OUT"
+grep -qF "NSEC \\000.this.does.not.exist.onlinesign.test." "$RUN_OUT"
+# Check a wildcard record
+run resolvectl query -t TXT this.should.be.authenticated.wild.onlinesign.test
+grep -qF 'this.should.be.authenticated.wild.onlinesign.test IN TXT "this is an onlinesign wildcard"' "$RUN_OUT"
+grep -qF "authenticated: yes" "$RUN_OUT"
+
+# Resolve via dbus method
+TIMESTAMP=$(date '+%F %T')
+run busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager ResolveHostname 'isit' 0 secondsub.onlinesign.test 0 0
+grep -qF '10 0 0 134 "secondsub.onlinesign.test"' "$RUN_OUT"
+monitor_check_rr "$TIMESTAMP" "secondsub.onlinesign.test IN A 10.0.0.134"
+
+: "--- ZONE: untrusted.test (DNSSEC without propagated DS records) ---"
+run dig +short untrusted.test
+grep -qF "10.0.0.121" "$RUN_OUT"
+run resolvectl query untrusted.test
+grep -qF "untrusted.test: 10.0.0.121" "$RUN_OUT"
+grep -qF "authenticated: no" "$RUN_OUT"
+
+# Issue: https://github.com/systemd/systemd/issues/19472
+# 1) Query for a non-existing RR should return NOERROR + NSEC (?), not NXDOMAIN
+# FIXME: re-enable once the issue is resolved
+#run dig +dnssec AAAA untrusted.test
+#grep -qF "status: NOERROR" "$RUN_OUT"
+#grep -qE "^untrusted\.test\..+IN\s+NSEC\s+" "$RUN_OUT"
+## 2) Query for a non-existing name should return NXDOMAIN, not SERVFAIL
+#run dig +dnssec this.does.not.exist.untrusted.test
+#grep -qF "status: NXDOMAIN" "$RUN_OUT"
+
+systemctl stop resmontest.service
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-76.service b/test/units/testsuite-76.service
new file mode 100644
index 0000000..3c8a9e8
--- /dev/null
+++ b/test/units/testsuite-76.service
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-76-SYSCTL
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-76.sh b/test/units/testsuite-76.sh
new file mode 100755
index 0000000..cb571f8
--- /dev/null
+++ b/test/units/testsuite-76.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+export SYSTEMD_LOG_LEVEL=debug
+
+echo "foo.bar=42" > /tmp/foo.conf
+assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf
+assert_rc 1 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf
+
+echo "-foo.foo=42" > /tmp/foo.conf
+assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf
+assert_rc 0 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf
+
+if ! systemd-detect-virt --quiet --container; then
+ ip link add hoge type dummy
+ udevadm wait /sys/class/net/hoge
+
+ cat >/tmp/foo.conf <<EOF
+net.ipv4.conf.*.drop_gratuitous_arp=1
+net.ipv4.*.*.bootp_relay=1
+net.ipv4.aaa.*.disable_policy=1
+EOF
+
+ echo 0 > /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp
+ echo 0 > /proc/sys/net/ipv4/conf/hoge/bootp_relay
+ echo 0 > /proc/sys/net/ipv4/conf/hoge/disable_policy
+
+ assert_rc 0 /usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge /tmp/foo.conf
+ assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp)" "1"
+ assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/bootp_relay)" "1"
+ assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/disable_policy)" "0"
+fi
+
+touch /testok
diff --git a/test/units/testsuite.target b/test/units/testsuite.target
new file mode 100644
index 0000000..6bcbfec
--- /dev/null
+++ b/test/units/testsuite.target
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Testsuite target
+Requires=multi-user.target
+After=multi-user.target
+Conflicts=rescue.target
+AllowIsolate=yes
diff --git a/test/units/timers.target b/test/units/timers.target
new file mode 100644
index 0000000..99f82e3
--- /dev/null
+++ b/test/units/timers.target
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Timers
+Documentation=man:systemd.special(7)
+
+DefaultDependencies=no
+Conflicts=shutdown.target
diff --git a/test/units/unit-.service.d/10-override.conf b/test/units/unit-.service.d/10-override.conf
new file mode 100644
index 0000000..1bc5e1c
--- /dev/null
+++ b/test/units/unit-.service.d/10-override.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=override0
diff --git a/test/units/unit-with-.service.d/20-override.conf b/test/units/unit-with-.service.d/20-override.conf
new file mode 100644
index 0000000..17fe084
--- /dev/null
+++ b/test/units/unit-with-.service.d/20-override.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Documentation=man:override1
diff --git a/test/units/unit-with-multiple-.service.d/20-override.conf b/test/units/unit-with-multiple-.service.d/20-override.conf
new file mode 100644
index 0000000..5b48784
--- /dev/null
+++ b/test/units/unit-with-multiple-.service.d/20-override.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Documentation=man:override2
diff --git a/test/units/unit-with-multiple-.service.d/30-override.conf b/test/units/unit-with-multiple-.service.d/30-override.conf
new file mode 100644
index 0000000..4d3423a
--- /dev/null
+++ b/test/units/unit-with-multiple-.service.d/30-override.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Documentation=man:override3
diff --git a/test/units/unit-with-multiple-dashes.service b/test/units/unit-with-multiple-dashes.service
new file mode 100644
index 0000000..4aca904
--- /dev/null
+++ b/test/units/unit-with-multiple-dashes.service
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=A unit with multiple dashes
+Documentation=man:test
+
+[Service]
+ExecStart=/bin/true
diff --git a/test/units/unit-with-multiple-dashes.service.d/10-override.conf b/test/units/unit-with-multiple-dashes.service.d/10-override.conf
new file mode 100644
index 0000000..e249b20
--- /dev/null
+++ b/test/units/unit-with-multiple-dashes.service.d/10-override.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=override4