diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:35:18 +0000 |
commit | b750101eb236130cf056c675997decbac904cc49 (patch) | |
tree | a5df1a06754bdd014cb975c051c83b01c9a97532 /test/units | |
parent | Initial commit. (diff) | |
download | systemd-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')
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 |