]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/test-functions
test: add integration test for unpriv mountfsd/nsresourced
[thirdparty/systemd.git] / test / test-functions
CommitLineData
ff12a795 1#!/usr/bin/env bash
898720b7 2# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
8f5bcd61
ZJS
3# SPDX-License-Identifier: LGPL-2.1-or-later
4#
5# shellcheck disable=SC2030,SC2031
96af59aa
FS
6# ex: ts=8 sw=4 sts=4 et filetype=sh tw=180
7# Note: the shellcheck line above disables warning for variables which were
8# modified in a subshell. In our case this behavior is expected, but
9# `shellcheck` can't distinguish this because of poor variable tracking,
10# which results in warning for every instance of such variable used
11# throughout this file.
12# See:
13# * comment in function install_verity_minimal()
14# * koalaman/shellcheck#280
15set -o pipefail
16
b54bc139
FS
17# Simple wrapper to unify boolean checks.
18# Note: this function needs to stay near the top of the file, so we can use it
19# in code in the outermost scope.
20get_bool() {
21 # Make the value lowercase to make the regex matching simpler
22 local _bool="${1,,}"
23
24 # Consider empty value as "false"
25 if [[ -z "$_bool" || "$_bool" =~ ^(0|no|false)$ ]]; then
26 return 1
27 elif [[ "$_bool" =~ ^(1|yes|true)$ ]]; then
28 return 0
29 else
30 echo >&2 "Value '$_bool' is not a valid boolean value"
31 exit 1
32 fi
33}
34
898720b7
HH
35PATH=/sbin:/bin:/usr/sbin:/usr/bin
36export PATH
37
ae6c5987 38os_release=$(test -e /etc/os-release && echo /etc/os-release || echo /usr/lib/os-release)
1b8fcd9c
FS
39# shellcheck source=/dev/null
40source "$os_release"
b54bc139 41[[ "$ID" == "debian" || " $ID_LIKE " == *" debian "* ]] && LOOKS_LIKE_DEBIAN=yes || LOOKS_LIKE_DEBIAN=no
62564681
LP
42# shellcheck disable=SC2034
43[[ "$ID" == "ubuntu" ]] && LOOKS_LIKE_UBUNTU=yes || LOOKS_LIKE_UBUNTU=no
b54bc139 44[[ "$ID" == "arch" || " $ID_LIKE " == *" arch "* ]] && LOOKS_LIKE_ARCH=yes || LOOKS_LIKE_ARCH=no
8ddbd9e0 45[[ "$ID" == "fedora" ]] && LOOKS_LIKE_FEDORA=yes || LOOKS_LIKE_FEDORA=no
b54bc139
FS
46[[ " $ID_LIKE " == *" suse "* ]] && LOOKS_LIKE_SUSE=yes || LOOKS_LIKE_SUSE=no
47
1b8fcd9c 48KERNEL_VER="${KERNEL_VER-$(uname -r)}"
cd15f7f6
FS
49QEMU_TIMEOUT="${QEMU_TIMEOUT:-1800}"
50NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-1800}"
b2ecd099 51TIMED_OUT= # will be 1 after run_* if *_TIMEOUT is set and test timed out
18c769b0 52get_bool "$LOOKS_LIKE_SUSE" && FSTYPE="${FSTYPE:-btrfs}" || FSTYPE="${FSTYPE:-ext4}"
22f1f8f2 53UNIFIED_CGROUP_HIERARCHY="${UNIFIED_CGROUP_HIERARCHY:-default}"
5f28f3dd 54EFI_MOUNT="${EFI_MOUNT:-$(bootctl -x 2>/dev/null || echo /boot)}"
d9e606e8
LB
55# Note that defining a different IMAGE_NAME in a test setup script will only result
56# in default.img being copied and renamed. It can then be extended by defining
57# a test_append_files() function. The $1 parameter will be the root directory.
58# To force creating a new image from scratch (eg: to encrypt it), also define
59# TEST_FORCE_NEWIMAGE=1 in the test setup script.
8c3534b5 60IMAGE_NAME=${IMAGE_NAME:-default}
9d84eb20 61TEST_REQUIRE_INSTALL_TESTS="${TEST_REQUIRE_INSTALL_TESTS:-1}"
7a57256c 62TEST_PARALLELIZE="${TEST_PARALLELIZE:-0}"
508a7f04 63TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED="${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED:-1}"
1506edca 64LOOPDEV=
898720b7 65
b92c3df2
FS
66# Since in Bash we can have only one handler per signal, let's overcome this
67# limitation by having one global handler for the EXIT signal which executes
68# all registered handlers
69_AT_EXIT_HANDLERS=()
70_at_exit() {
71 set +e
72
73 # Run the EXIT handlers in reverse order
74 for ((i = ${#_AT_EXIT_HANDLERS[@]} - 1; i >= 0; i--)); do
75 ddebug "Running EXIT handler '${_AT_EXIT_HANDLERS[$i]}'"
18fa5c82 76 eval "${_AT_EXIT_HANDLERS[$i]}"
b92c3df2
FS
77 done
78}
79
80trap _at_exit EXIT
81
82add_at_exit_handler() {
18fa5c82 83 _AT_EXIT_HANDLERS+=("${1:?}")
b92c3df2
FS
84}
85
3e8caa34 86# Decide if we can (and want to) run qemu with KVM acceleration.
501deda1
FS
87# Check if nested KVM is explicitly enabled (TEST_NESTED_KVM). If not,
88# check if it's not explicitly disabled (TEST_NO_KVM) and we're not already
89# running under KVM. If these conditions are met, enable KVM (and possibly
90# nested KVM), otherwise disable it.
12ee072d 91if get_bool "${TEST_NESTED_KVM:=}" || (! get_bool "${TEST_NO_KVM:=}" && ! systemd-detect-virt -qv); then
501deda1
FS
92 QEMU_KVM=yes
93else
94 QEMU_KVM=no
95fi
96
3486cb6c 97if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then
b0d3095f 98 echo "WARNING! Cannot determine libdir from pkg-config, assuming /usr/lib/systemd" >&2
3486cb6c
MP
99 ROOTLIBDIR=/usr/lib/systemd
100fi
101
42f3b48c 102# The calling test.sh scripts have TEST_BASE_DIR set via their Makefile, but we don't need them to provide it
1b8fcd9c 103TEST_BASE_DIR=${TEST_BASE_DIR:-$(realpath "$(dirname "${BASH_SOURCE[0]}")")}
c4cd6205 104TEST_UNITS_DIR="$(realpath "$TEST_BASE_DIR/units")"
42f3b48c 105SOURCE_DIR=$(realpath "$TEST_BASE_DIR/..")
1b8fcd9c 106# These variables are used by test scripts
04ef5d1b 107export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR
42f3b48c 108
04ef5d1b 109TOOLS_DIR="$SOURCE_DIR/tools"
12d31e4e 110# note that find-build-dir.sh will return $BUILD_DIR if provided, else it will try to find it
dfd73ccb
FB
111if get_bool "${NO_BUILD:=}"; then
112 BUILD_DIR="$SOURCE_DIR"
113elif ! BUILD_DIR="$("$TOOLS_DIR"/find-build-dir.sh)"; then
114 echo "ERROR: no build found, please set BUILD_DIR or use NO_BUILD" >&2
115 exit 1
12d31e4e
DS
116fi
117
a33e2692
FS
118PATH_TO_INIT="$ROOTLIBDIR/systemd"
119SYSTEMD_JOURNALD="${SYSTEMD_JOURNALD:-$(command -v "$BUILD_DIR/systemd-journald" || command -v "$ROOTLIBDIR/systemd-journald")}"
cca3050b 120SYSTEMD_JOURNAL_REMOTE="${SYSTEMD_JOURNAL_REMOTE:-$(command -v "$BUILD_DIR/systemd-journal-remote" || command -v "$ROOTLIBDIR/systemd-journal-remote" || echo "")}"
a33e2692
FS
121SYSTEMD="${SYSTEMD:-$(command -v "$BUILD_DIR/systemd" || command -v "$ROOTLIBDIR/systemd")}"
122SYSTEMD_NSPAWN="${SYSTEMD_NSPAWN:-$(command -v "$BUILD_DIR/systemd-nspawn" || command -v systemd-nspawn)}"
123JOURNALCTL="${JOURNALCTL:-$(command -v "$BUILD_DIR/journalctl" || command -v journalctl)}"
354b3364 124SYSTEMCTL="${SYSTEMCTL:-$(command -v "$BUILD_DIR/systemctl" || command -v systemctl)}"
a0b50e4d 125SYSTEMD_ID128="${SYSTEMD_ID128:-$(command -v "$BUILD_DIR/systemd-id128" || command -v systemd-id128)}"
016fa3b9 126
1b8fcd9c 127TESTFILE="${BASH_SOURCE[1]}"
42f3b48c
DS
128if [ -z "$TESTFILE" ]; then
129 echo "ERROR: test-functions must be sourced from one of the TEST-*/test.sh scripts" >&2
130 exit 1
131fi
1b8fcd9c 132TESTNAME="$(basename "$(dirname "$(realpath "$TESTFILE")")")"
0c566150
FB
133
134WORKDIR="/var/tmp/systemd-tests"
135if get_bool "${NO_BUILD:=}"; then
136 STATEDIR="$WORKDIR/$TESTNAME"
137else
138 STATEDIR="$BUILD_DIR/test/$TESTNAME"
139fi
140
19184069
DS
141STATEFILE="$STATEDIR/.testdir"
142IMAGESTATEDIR="$STATEDIR/.."
143TESTLOG="$STATEDIR/test.log"
144
c4cd6205
FS
145if ! [[ "$TESTNAME" =~ ^TEST\-([0-9]+)\-.+$ ]]; then
146 echo "ERROR: Test name '$TESTNAME' is not in the expected format: TEST-[0-9]+-*" >&2
147 exit 1
148fi
149TESTID="${BASH_REMATCH[1]:?}"
150
151if [[ ! -f "$TEST_UNITS_DIR/testsuite-$TESTID.service" ]]; then
152 echo "ERROR: Test '$TESTNAME' is missing its service file '$TEST_UNITS_DIR/testsuite-$TESTID.service" >&2
153 exit 1
154fi
155
4110a6de 156BASICTOOLS=(
f4c40fd7 157 awk
e7848266 158 base64
f4c40fd7 159 basename
4110a6de 160 bash
f4c40fd7 161 capsh
4110a6de 162 cat
52c1fb68 163 chgrp
4110a6de
ZJS
164 chmod
165 chown
a7eed3ec 166 chroot
4110a6de 167 cmp
6162caa2 168 cp
4110a6de 169 cryptsetup
f4c40fd7 170 cut
4110a6de 171 date
f4c40fd7 172 dd
6162caa2 173 dhclient
f4c40fd7
ZJS
174 diff
175 dirname
4110a6de
ZJS
176 dmsetup
177 echo
178 env
179 false
6162caa2 180 find
af70e202 181 findmnt
41d1aaea 182 flock
e4ff8040 183 getconf
f4c40fd7
ZJS
184 getent
185 getfacl
a2dd5920 186 getfattr
e29e4d57 187 grep
6162caa2 188 grep
f4c40fd7 189 gunzip
4110a6de
ZJS
190 gzip
191 head
6162caa2
FS
192 hostname
193 id
f4c40fd7 194 ionice
e5badaf3 195 ip
7906b790 196 jq
5c08efee 197 killall
42867dfe 198 ldd
4110a6de 199 ln
6162caa2 200 ln
4110a6de
ZJS
201 loadkeys
202 login
92ecc875 203 losetup
6162caa2 204 ls
378db9e2 205 lsattr
af70e202 206 lsblk
f4c40fd7 207 lz4cat
6162caa2 208 mkdir
c0b97b0f 209 mkfifo
e6c281c1 210 mknod
c0b97b0f 211 mktemp
4110a6de
ZJS
212 modprobe
213 mount
e47add9e 214 mountpoint
d10029bb
ZJS
215 mv
216 nc
f4c40fd7 217 nproc
6162caa2 218 ping
9c94ab0f 219 pkill
6162caa2 220 ps
c0b97b0f 221 readlink
bf9b5691 222 realpath
f4c40fd7 223 rev
4110a6de 224 rm
e5badaf3 225 rmdir
9c94ab0f 226 rmmod
990b629e 227 script
4110a6de 228 sed
f4c40fd7 229 seq
d5a6ff8c 230 setfacl
59331b8e 231 setfattr
4110a6de 232 setfont
c7bf1959 233 setpriv
4110a6de 234 setsid
e29e4d57 235 sfdisk
4110a6de
ZJS
236 sh
237 sleep
6162caa2 238 sort
d10029bb 239 stat
61961e69 240 stty
d0ac89a1 241 su
4110a6de 242 sulogin
e5badaf3 243 sysctl
4110a6de 244 tail
6e796683 245 tar
4110a6de
ZJS
246 tee
247 test
d446ae89 248 timeout
3ac189d8 249 touch
f4c40fd7 250 tr
4110a6de 251 true
e29e4d57 252 truncate
6162caa2 253 tty
4110a6de 254 umount
f4c40fd7 255 uname
e5badaf3 256 unshare
9c94ab0f
YW
257 useradd
258 userdel
9fbb13ac 259 wc
dfdcc7c9 260 whoami
4110a6de 261 xargs
f4c40fd7 262 xzcat
4110a6de
ZJS
263)
264
265DEBUGTOOLS=(
4110a6de 266 df
4110a6de
ZJS
267 dmesg
268 du
4110a6de 269 free
4110a6de 270 less
4110a6de 271 strace
4110a6de 272 vi
8afe2f53 273 /usr/libexec/vi
4110a6de 274)
889a9042 275
ec9181d2 276is_built_with_asan() {
b727d7e0
FS
277 local _bin="${1:?}"
278
ec9181d2 279 if ! type -P objdump >/dev/null; then
f258a763 280 echo "Failed to find objdump, assuming systemd hasn't been built with ASAN."
ec9181d2
EV
281 return 1
282 fi
283
284 # Borrowed from https://github.com/google/oss-fuzz/blob/cd9acd02f9d3f6e80011cc1e9549be526ce5f270/infra/base-images/base-runner/bad_build_check#L182
1b8fcd9c 285 local _asan_calls
b727d7e0 286 _asan_calls="$(objdump -dC "$_bin" | grep -E "(callq?|brasl?|bl)\s.+__asan" -c)"
1b8fcd9c 287 if ((_asan_calls < 1000)); then
ec9181d2
EV
288 return 1
289 else
290 return 0
291 fi
292}
293
7a6c5b6a
FS
294is_built_with_coverage() {
295 if get_bool "${NO_BUILD:=}" || ! command -v meson >/dev/null; then
296 return 1
297 fi
298
299 meson configure "${BUILD_DIR:?}" | grep 'b_coverage' | awk '{ print $2 }' | grep -q 'true'
300}
301
b727d7e0 302IS_BUILT_WITH_ASAN=$(is_built_with_asan "$SYSTEMD_JOURNALD" && echo yes || echo no)
7a6c5b6a 303IS_BUILT_WITH_COVERAGE=$(is_built_with_coverage && echo yes || echo no)
ec9181d2 304
23f8e019 305if get_bool "$IS_BUILT_WITH_ASAN"; then
ba79e8c2 306 PATH_TO_INIT="$ROOTLIBDIR/systemd-under-asan"
dc350e78 307 QEMU_MEM="${QEMU_MEM:-2G}"
648fd189 308 QEMU_SMP="${QEMU_SMP:-4}"
37ee8dc8
FS
309
310 # We need to correctly distinguish between gcc's and clang's ASan DSOs.
d49b881e 311 if ASAN_RT_NAME="$(awk '/libasan.so/ {x=$1; exit} END {print x; exit x==""}' < <(ldd "$SYSTEMD"))"; then
37ee8dc8 312 ASAN_COMPILER=gcc
648fd189 313 ASAN_RT_PATH="$(readlink -f "$(${CC:-gcc} --print-file-name "$ASAN_RT_NAME")")"
d49b881e 314 elif ASAN_RT_NAME="$(awk '/libclang_rt.asan/ {x=$1; exit} END {print x; exit x==""}' < <(ldd "$SYSTEMD"))"; then
37ee8dc8 315 ASAN_COMPILER=clang
648fd189 316 ASAN_RT_PATH="$(readlink -f "$(${CC:-clang} --print-file-name "$ASAN_RT_NAME")")"
37ee8dc8
FS
317
318 # As clang's ASan DSO is usually in a non-standard path, let's check if
319 # the environment is set accordingly. If not, warn the user and exit.
320 # We're not setting the LD_LIBRARY_PATH automagically here, because
321 # user should encounter (and fix) the same issue when running the unit
322 # tests (meson test)
84c49ad1 323 if ldd "$SYSTEMD" | grep -q "libclang_rt.asan.*not found"; then
648fd189
FS
324 echo >&2 "clang's ASan DSO ($ASAN_RT_NAME) is not present in the runtime library path"
325 echo >&2 "Consider setting LD_LIBRARY_PATH=${ASAN_RT_PATH%/*}"
37ee8dc8
FS
326 exit 1
327 fi
328 else
329 echo >&2 "systemd is not linked against the ASan DSO"
330 echo >&2 "gcc does this by default, for clang compile with -shared-libasan"
331 exit 1
332 fi
648fd189
FS
333
334 echo "Detected ASan RT '$ASAN_RT_NAME' located at '$ASAN_RT_PATH'"
ec9181d2
EV
335fi
336
70bdf6e6
FS
337test_require_bin() {
338 local bin
339
340 for bin in "$@"; do
341 if ! command -v "$bin" >/dev/null; then
342 echo "Required binary $bin not available, skipping the test"
343 exit 0
344 fi
345 done
346}
347
1b8fcd9c
FS
348find_qemu_bin() {
349 QEMU_BIN="${QEMU_BIN:-""}"
3e7aa2ed 350 # SUSE and Red Hat call the binary qemu-kvm. Debian and Gentoo call it kvm.
23f8e019 351 if get_bool "$QEMU_KVM"; then
1b8fcd9c 352 [[ -n "$QEMU_BIN" ]] || QEMU_BIN="$(command -v kvm qemu-kvm 2>/dev/null | grep '^/' -m1)"
3e7aa2ed 353 fi
c6a77179 354
1b8fcd9c 355 [[ -n "$ARCH" ]] || ARCH="$(uname -m)"
c6a77179
RC
356 case $ARCH in
357 x86_64)
358 # QEMU's own build system calls it qemu-system-x86_64
1b8fcd9c 359 [[ -n "$QEMU_BIN" ]] || QEMU_BIN="$(command -v qemu-system-x86_64 2>/dev/null | grep '^/' -m1)"
c6a77179
RC
360 ;;
361 i*86)
362 # new i386 version of QEMU
1b8fcd9c 363 [[ -n "$QEMU_BIN" ]] || QEMU_BIN="$(command -v qemu-system-i386 2>/dev/null | grep '^/' -m1)"
c6a77179
RC
364
365 # i386 version of QEMU
1b8fcd9c 366 [[ -n "$QEMU_BIN" ]] || QEMU_BIN="$(command -v qemu 2>/dev/null | grep '^/' -m1)"
c6a77179 367 ;;
cf5f9bb8 368 ppc64*)
1b8fcd9c 369 [[ -n "$QEMU_BIN" ]] || QEMU_BIN="$(command -v qemu-system-ppc64 2>/dev/null | grep '^/' -m1)"
cf5f9bb8 370 ;;
c6a77179
RC
371 esac
372
1b8fcd9c 373 if [[ ! -e "$QEMU_BIN" ]]; then
3e8caa34 374 echo "Could not find a suitable qemu binary" >&2
c6a77179
RC
375 return 1
376 fi
377}
378
18fa5c82
FS
379qemu_setup_swtpm_socket() {
380 local pid state_dir tpm_device
381
382 if ! tpm_device="$(qemu_get_tpm_device)"; then
383 dinfo "Found QEMU version is too old for TPM2 on ppc64le"
384 exit 0
385 fi
386
387 state_dir="$(mktemp -d)"
388 swtpm socket --tpm2 --tpmstate dir="$state_dir" --ctrl type=unixio,path="$state_dir/sock" &
389 pid=$!
390 if ! kill -0 "$pid"; then
18c3ffbf
FS
391 derror "Failed to start swtpm"
392 return 1
393 fi
394
395 if ! timeout 5 bash -c "until [[ -S $state_dir/sock ]]; do sleep .5; done"; then
396 derror "Failed to setup swtpm socket"
18fa5c82
FS
397 return 1
398 fi
399
400 dinfo "Started swtpm as PID $pid with state dir $state_dir"
401
402 add_at_exit_handler "kill -TERM $pid 2>/dev/null; rm -rf '$state_dir'"
403
404 QEMU_OPTIONS+=" -chardev socket,id=chrtpm,path=$state_dir/sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device $tpm_device,tpmdev=tpm0"
405 dinfo "Configured emulated TPM2 device $tpm_device"
406
407 return 0
408}
409
410qemu_get_tpm_device() {
411 local tpm_device="tpm-tis"
412
413 if [[ "$(uname -m)" == "ppc64le" ]]; then
414 # tpm-spapr support was introduced in qemu 5.0.0
415 if ! qemu_min_version "5.0.0"; then
416 return 1
417 fi
418
419 tpm_device="tpm-spapr"
420 fi
421
422 echo "$tpm_device"
423 return 0
424}
425
43b49470
CE
426# Compares argument #1=X.Y.Z (X&Y&Z = numeric) to the version of the installed qemu
427# returns 0 if newer or equal
428# returns 1 if older
429# returns 2 if failing
1b8fcd9c 430qemu_min_version() {
43b49470
CE
431 find_qemu_bin || return 2
432
433 # get version from binary
1b8fcd9c
FS
434 local qemu_ver
435 qemu_ver="$("$QEMU_BIN" --version | awk '/^QEMU emulator version ([0-9]*\.[0-9]*\.[0-9]*)/ {print $4}')"
43b49470
CE
436
437 # Check version string format
438 echo "$qemu_ver" | grep -q '^[0-9]*\.[0-9]*\.[0-9]*$' || return 2
439 echo "$1" | grep -q '^[0-9]*\.[0-9]*\.[0-9]*$' || return 2
440
441 # compare as last command to return that value
442 printf "%s\n%s\n" "$1" "$qemu_ver" | sort -V -C
443}
444
4a262d56
LP
445# Pads a file to multiple of 4 bytes
446pad4_file() {
447 local size
448 size=$(stat -c "%s" "$1")
449 local padded
450 padded=$((((size + 3) / 4) * 4))
451 truncate -s "$padded" "$1"
452}
453
3e8caa34
ZJS
454# Return 0 if qemu did run (then you must check the result state/logs for actual
455# success), or 1 if qemu is not available.
889a9042 456run_qemu() {
4a262d56
LP
457 if declare -F run_qemu_hook >/dev/null; then
458 if ! run_qemu_hook "${workspace}"; then
459 derror "check_qemu_hook() returned with EC > 0"
460 ret=4
461 fi
462 fi
463
b22d90e5
FS
464 # If the test provided its own initrd, use it (e.g. TEST-24)
465 if [[ -z "$INITRD" && -f "${TESTDIR:?}/initrd.img" ]]; then
466 INITRD="$TESTDIR/initrd.img"
467 fi
468
b6f0c419 469 if [ -f /etc/machine-id ]; then
1b8fcd9c 470 read -r MACHINE_ID </etc/machine-id
906bbac4
ZJS
471 [ -z "$INITRD" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" ] \
472 && INITRD="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd"
473 [ -z "$KERNEL_BIN" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux" ] \
474 && KERNEL_BIN="$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/linux"
b6f0c419
HH
475 fi
476
1b8fcd9c 477 local CONSOLE=ttyS0
9a2e265b 478
60f9c49b
FS
479 # Reset the boot counter, if present
480 rm -f "${initdir:?}/var/tmp/.systemd_reboot_count"
80b44b38 481 rm -f "$initdir"/{testok,failed,skipped}
ec43f686
ZJS
482 # make sure the initdir is not mounted to avoid concurrent access
483 cleanup_initdir
484 umount_loopback
485
e1a27318 486 if [[ ! "$KERNEL_BIN" ]]; then
23f8e019 487 if get_bool "$LOOKS_LIKE_ARCH"; then
e1a27318
EV
488 KERNEL_BIN=/boot/vmlinuz-linux
489 else
eaa602cb
DJL
490 [ "$ARCH" ] || ARCH=$(uname -m)
491 case $ARCH in
492 ppc64*)
46db176f
FS
493 # Ubuntu ppc64* calls the kernel binary as vmlinux-*, RHEL/CentOS
494 # uses the "standard" vmlinuz- prefix
495 [[ -e "/boot/vmlinux-$KERNEL_VER" ]] && KERNEL_BIN="/boot/vmlinux-$KERNEL_VER" || KERNEL_BIN="/boot/vmlinuz-$KERNEL_VER"
496 CONSOLE=hvc0
497 ;;
eaa602cb 498 *)
46db176f
FS
499 KERNEL_BIN="/boot/vmlinuz-$KERNEL_VER"
500 ;;
eaa602cb 501 esac
e1a27318
EV
502 fi
503 fi
504
1b8fcd9c
FS
505 local default_fedora_initrd="/boot/initramfs-${KERNEL_VER}.img"
506 local default_debian_initrd="/boot/initrd.img-${KERNEL_VER}"
507 local default_arch_initrd="/boot/initramfs-linux-fallback.img"
508 local default_suse_initrd="/boot/initrd-${KERNEL_VER}"
e1a27318
EV
509 if [[ ! "$INITRD" ]]; then
510 if [[ -e "$default_fedora_initrd" ]]; then
511 INITRD="$default_fedora_initrd"
b54bc139 512 elif get_bool "$LOOKS_LIKE_DEBIAN" && [[ -e "$default_debian_initrd" ]]; then
e1a27318 513 INITRD="$default_debian_initrd"
b54bc139 514 elif get_bool "$LOOKS_LIKE_ARCH" && [[ -e "$default_arch_initrd" ]]; then
e1a27318 515 INITRD="$default_arch_initrd"
b54bc139 516 elif get_bool "$LOOKS_LIKE_SUSE" && [[ -e "$default_suse_initrd" ]]; then
85393d8f 517 INITRD="$default_suse_initrd"
e1a27318
EV
518 fi
519 fi
520
5bfb2a93
FS
521 # If QEMU_SMP was not explicitly set, try to determine the value 'dynamically'
522 # i.e. use the number of online CPUs on the host machine. If the nproc utility
523 # is not installed or there's some other error when calling it, fall back
524 # to the original value (QEMU_SMP=1).
1b8fcd9c 525 if [[ -z "${QEMU_SMP:=}" ]]; then
5bfb2a93
FS
526 if ! QEMU_SMP=$(nproc); then
527 dwarn "nproc utility is not installed, falling back to QEMU_SMP=1"
528 QEMU_SMP=1
529 fi
530 fi
c6a77179
RC
531
532 find_qemu_bin || return 1
533
18fa5c82
FS
534 if get_bool "${TEST_SETUP_SWTPM:-}"; then
535 qemu_setup_swtpm_socket || return 1
536 fi
537
8c3534b5 538 # Umount initdir to avoid concurrent access to the filesystem
0f194705
FS
539 _umount_dir "$initdir"
540
541 local kernel_params=()
542 local qemu_options=()
543 local qemu_cmd=("$QEMU_BIN")
8c3534b5 544
22f1f8f2 545 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" ]]; then
0f194705 546 kernel_params+=("systemd.unified_cgroup_hierarchy=yes")
22f1f8f2 547 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
0f194705 548 kernel_params+=("systemd.unified_cgroup_hierarchy=no" "systemd.legacy_systemd_cgroup_controller=yes")
22f1f8f2 549 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
0f194705 550 kernel_params+=("systemd.unified_cgroup_hierarchy=no" "systemd.legacy_systemd_cgroup_controller=no")
22f1f8f2
EV
551 elif [[ "$UNIFIED_CGROUP_HIERARCHY" != "default" ]]; then
552 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
553 exit 1
554 fi
555
23f8e019 556 if get_bool "$LOOKS_LIKE_SUSE"; then
0f194705 557 kernel_params+=("rd.hostonly=0")
cc5549ca 558 fi
85393d8f 559
8fb26ccd
LB
560 # Debian/Ubuntu's initramfs tries to check if it can resume from hibernation
561 # and wastes a minute or so probing disks, skip that as it's not useful here
0f194705 562 kernel_params+=(
bac05644 563 "root=LABEL=systemd_boot"
0f194705
FS
564 "rw"
565 "raid=noautodetect"
566 "rd.luks=0"
567 "loglevel=2"
568 "init=$PATH_TO_INIT"
569 "console=$CONSOLE"
0f194705
FS
570 "SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-$1.units:/usr/lib/systemd/tests/testdata/units:"
571 "systemd.unit=testsuite.target"
572 "systemd.wants=testsuite-$1.service"
8fb26ccd 573 "noresume"
5ab2f737 574 "oops=panic"
0efa27bd
FS
575 ${TEST_MATCH_SUBTEST:+"systemd.setenv=TEST_MATCH_SUBTEST=$TEST_MATCH_SUBTEST"}
576 ${TEST_MATCH_TESTCASE:+"systemd.setenv=TEST_MATCH_TESTCASE=$TEST_MATCH_TESTCASE"}
0f194705
FS
577 )
578
5ab2f737 579 if ! get_bool "$INTERACTIVE_DEBUG" && ! get_bool "$TEST_SKIP_SHUTDOWN"; then
842a9d5f 580 kernel_params+=(
842a9d5f 581 "panic=1"
4e086c38 582 "softlockup_panic=1"
842a9d5f
FS
583 "systemd.wants=end.service"
584 )
fe85f2bb
ZJS
585 fi
586
e8945092 587 [ -e "$IMAGE_PRIVATE" ] && image="$IMAGE_PRIVATE" || image="$IMAGE_PUBLIC"
0f194705
FS
588 qemu_options+=(
589 -smp "$QEMU_SMP"
590 -net none
6a9c4977 591 -m "${QEMU_MEM:-768M}"
0f194705
FS
592 -nographic
593 -kernel "$KERNEL_BIN"
594 -drive "format=raw,cache=unsafe,file=$image"
4d5d28e9 595 -device "virtio-rng-pci,max-bytes=1024,period=1000"
0f194705
FS
596 )
597
598 if [[ -n "${QEMU_OPTIONS:=}" ]]; then
599 local user_qemu_options
600 read -ra user_qemu_options <<< "$QEMU_OPTIONS"
601 qemu_options+=("${user_qemu_options[@]}")
602 fi
18a6cd4b 603 qemu_options+=(${QEMU_OPTIONS_ARRAY:+"${QEMU_OPTIONS_ARRAY[@]}"})
0f194705 604
93b896e9 605 if [[ -n "$INITRD" ]]; then
4a262d56
LP
606 if [[ -n "$INITRD_EXTRA" ]]; then
607 # An addition initrd has been specified, let's combine it with the main one.
608 local t="$WORKDIR"/initrd.combined."$RANDOM"
609
610 # First, show contents of additional initrd
611 echo "Additional initrd contents:"
612 cpio -tv < "$INITRD_EXTRA"
613
614 # Copy the main initrd
615 zstd -d -c -f "$INITRD" > "$t"
616 add_at_exit_handler "rm $t"
617 # Kernel requires this to be padded to multiple of 4 bytes with zeroes
618 pad4_file "$t"
619
620 # Copy the additional initrd
621 cat "$INITRD_EXTRA" >> "$t"
622 pad4_file "$t"
623
624 qemu_options+=(-initrd "$t")
625 else
626 qemu_options+=(-initrd "$INITRD")
627 fi
c6a77179
RC
628 fi
629
501deda1 630 # Let's use KVM if possible
23f8e019 631 if [[ -c /dev/kvm ]] && get_bool $QEMU_KVM; then
0f194705 632 qemu_options+=(-machine "accel=kvm" -enable-kvm -cpu host)
dbf43a42
DM
633 fi
634
91f9f8f1 635 if [[ "$QEMU_TIMEOUT" != "infinity" ]]; then
0f194705 636 qemu_cmd=(timeout --foreground "$QEMU_TIMEOUT" "$QEMU_BIN")
91f9f8f1 637 fi
0f194705 638
4f3d8def 639 (set -x; "${qemu_cmd[@]}" "${qemu_options[@]}" -append "${kernel_params[*]} ${KERNEL_APPEND:-}" |& tee "${TESTDIR:?}/console.log")
b2ecd099 640 rc=$?
23f8e019
FS
641 if [ "$rc" -eq 124 ] && [ "$QEMU_TIMEOUT" != "infinity" ]; then
642 derror "Test timed out after ${QEMU_TIMEOUT}s"
b2ecd099
MP
643 TIMED_OUT=1
644 else
3e8caa34 645 [ "$rc" != 0 ] && derror "qemu failed with exit code $rc"
b2ecd099
MP
646 fi
647 return 0
889a9042
RC
648}
649
7cad32bb
MP
650# Return 0 if nspawn did run (then you must check the result state/logs for actual
651# success), or 1 if nspawn is not available.
889a9042 652run_nspawn() {
7cad32bb 653 [[ -d /run/systemd/system ]] || return 1
60f9c49b
FS
654 # Reset the boot counter, if present
655 rm -f "${initdir:?}/var/tmp/.systemd_reboot_count"
0f194705
FS
656 rm -f "${initdir:?}"/{testok,failed,skipped}
657
658 local nspawn_cmd=()
659 local nspawn_options=(
660 "--register=no"
661 "--kill-signal=SIGKILL"
662 "--directory=${1:?}"
663 "--setenv=SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-$2.units:/usr/lib/systemd/tests/testdata/units:"
10d7ed12 664 "--machine=TEST-$TESTID"
0f194705
FS
665 )
666 local kernel_params=(
667 "$PATH_TO_INIT"
668 "systemd.unit=testsuite.target"
669 "systemd.wants=testsuite-$2.service"
0efa27bd
FS
670 ${TEST_MATCH_SUBTEST:+"systemd.setenv=TEST_MATCH_SUBTEST=$TEST_MATCH_SUBTEST"}
671 ${TEST_MATCH_TESTCASE:+"systemd.setenv=TEST_MATCH_TESTCASE=$TEST_MATCH_TESTCASE"}
fe85f2bb
ZJS
672 )
673
5599c84b
FS
674 if get_bool "$INTERACTIVE_DEBUG"; then
675 nspawn_options+=("--console=interactive")
5ab2f737 676 elif ! get_bool "$TEST_SKIP_SHUTDOWN"; then
0f194705 677 kernel_params+=("systemd.wants=end.service")
fe85f2bb
ZJS
678 fi
679
0f194705
FS
680 if [[ -n "${NSPAWN_ARGUMENTS:=}" ]]; then
681 local user_nspawn_arguments
682 read -ra user_nspawn_arguments <<< "$NSPAWN_ARGUMENTS"
683 nspawn_options+=("${user_nspawn_arguments[@]}")
684 fi
685
22f1f8f2 686 if [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
c78c095b 687 dwarn "nspawn doesn't support SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=hybrid, skipping"
22f1f8f2
EV
688 exit
689 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" || "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
0f194705 690 nspawn_cmd+=(env "SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY")
22f1f8f2 691 elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "default" ]]; then
0f194705 692 nspawn_cmd+=(env "--unset=UNIFIED_CGROUP_HIERARCHY" "--unset=SYSTEMD_NSPAWN_UNIFIED_HIERARCHY")
22f1f8f2
EV
693 else
694 dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
695 exit 1
696 fi
856ca72b 697
0f194705
FS
698 if [[ "$NSPAWN_TIMEOUT" != "infinity" ]]; then
699 nspawn_cmd+=(timeout --foreground "$NSPAWN_TIMEOUT" "$SYSTEMD_NSPAWN")
700 else
701 nspawn_cmd+=("$SYSTEMD_NSPAWN")
702 fi
703
4f3d8def
FS
704 # Word splitting here is intentional
705 # shellcheck disable=SC2086
706 (set -x; "${nspawn_cmd[@]}" "${nspawn_options[@]}" "${kernel_params[@]}" ${KERNEL_APPEND:-} |& tee "${TESTDIR:?}/console.log")
b2ecd099 707 rc=$?
23f8e019
FS
708 if [ "$rc" -eq 124 ] && [ "$NSPAWN_TIMEOUT" != "infinity" ]; then
709 derror "Test timed out after ${NSPAWN_TIMEOUT}s"
b2ecd099
MP
710 TIMED_OUT=1
711 else
712 [ "$rc" != 0 ] && derror "nspawn failed with exit code $rc"
713 fi
714 return 0
889a9042
RC
715}
716
9785c44d
LB
717# Build two very minimal root images, with two units, one is the same and one is different across them
718install_verity_minimal() {
1b8fcd9c
FS
719 dinfo "Set up a set of minimal images for verity verification"
720 if [ -e "$initdir/usr/share/minimal.raw" ]; then
9785c44d
LB
721 return
722 fi
723 if ! command -v mksquashfs >/dev/null 2>&1; then
724 dfatal "mksquashfs not found"
725 exit 1
726 fi
727 if ! command -v veritysetup >/dev/null 2>&1; then
728 dfatal "veritysetup not found"
729 exit 1
730 fi
1b8fcd9c
FS
731 # Local modifications of some global variables is intentional in this
732 # subshell (SC2030)
733 # shellcheck disable=SC2030
9785c44d
LB
734 (
735 BASICTOOLS=(
736 bash
737 cat
62564681 738 echo
93f59701 739 grep
9785c44d
LB
740 mount
741 sleep
d110169b 742 touch
9785c44d 743 )
1b8fcd9c
FS
744 oldinitdir="$initdir"
745 rm -rfv "$TESTDIR/minimal"
746 export initdir="$TESTDIR/minimal"
3fa80e5e
LB
747 # app0 will use TemporaryFileSystem=/var/lib, app1 will need the mount point in the base image
748 mkdir -p "$initdir/usr/lib/systemd/system" "$initdir/usr/lib/extension-release.d" "$initdir/etc" "$initdir/var/tmp" "$initdir/opt" "$initdir/var/lib/app1"
9785c44d
LB
749 setup_basic_dirs
750 install_basic_tools
4163c877 751 install_ld_so_conf
1b8fcd9c
FS
752 # Shellcheck treats [[ -v VAR ]] as an assignment to avoid a different
753 # issue, thus falsely triggering SC2030 in this case
754 # See: koalaman/shellcheck#1409
9f6235e1
FS
755 if [[ -v ASAN_RT_PATH ]]; then
756 # If we're compiled with ASan, install the ASan RT (and its dependencies)
757 # into the verity images to get rid of the annoying errors about
758 # missing $LD_PRELOAD libraries.
759 inst_libs "$ASAN_RT_PATH"
760 inst_library "$ASAN_RT_PATH"
761 fi
1b8fcd9c
FS
762 cp "$os_release" "$initdir/usr/lib/os-release"
763 ln -s ../usr/lib/os-release "$initdir/etc/os-release"
764 touch "$initdir/etc/machine-id" "$initdir/etc/resolv.conf"
765 touch "$initdir/opt/some_file"
766 echo MARKER=1 >>"$initdir/usr/lib/os-release"
d76f0de7
LB
767 echo "PORTABLE_PREFIXES=app0 minimal minimal-app0" >>"$initdir/usr/lib/os-release"
768 cat >"$initdir/usr/lib/systemd/system/minimal-app0.service" <<EOF
769[Service]
770ExecStartPre=cat /usr/lib/os-release
771ExecStart=sleep 120
772EOF
773 cp "$initdir/usr/lib/systemd/system/minimal-app0.service" "$initdir/usr/lib/systemd/system/minimal-app0-foo.service"
1b8fcd9c 774
4f2dba98 775 mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_0.raw" -noappend
1b8fcd9c
FS
776 veritysetup format "$oldinitdir/usr/share/minimal_0.raw" "$oldinitdir/usr/share/minimal_0.verity" | \
777 grep '^Root hash:' | cut -f2 | tr -d '\n' >"$oldinitdir/usr/share/minimal_0.roothash"
778
779 sed -i "s/MARKER=1/MARKER=2/g" "$initdir/usr/lib/os-release"
d76f0de7
LB
780 rm "$initdir/usr/lib/systemd/system/minimal-app0-foo.service"
781 cp "$initdir/usr/lib/systemd/system/minimal-app0.service" "$initdir/usr/lib/systemd/system/minimal-app0-bar.service"
1b8fcd9c 782
4f2dba98 783 mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_1.raw" -noappend
1b8fcd9c
FS
784 veritysetup format "$oldinitdir/usr/share/minimal_1.raw" "$oldinitdir/usr/share/minimal_1.verity" | \
785 grep '^Root hash:' | cut -f2 | tr -d '\n' >"$oldinitdir/usr/share/minimal_1.roothash"
93f59701
LB
786
787 # Rolling distros like Arch do not set VERSION_ID
788 local version_id=""
1b8fcd9c
FS
789 if grep -q "^VERSION_ID=" "$os_release"; then
790 version_id="$(grep "^VERSION_ID=" "$os_release")"
93f59701
LB
791 fi
792
1b8fcd9c
FS
793 export initdir="$TESTDIR/app0"
794 mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
795 grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
796 echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
e8114a4f
LB
797 ( echo "${version_id}"
798 echo "SYSEXT_IMAGE_ID=app" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
1b8fcd9c 799 cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
93f59701
LB
800[Service]
801Type=oneshot
802RemainAfterExit=yes
803ExecStart=/opt/script0.sh
3fa80e5e
LB
804TemporaryFileSystem=/var/lib
805StateDirectory=app0
806RuntimeDirectory=app0
93f59701 807EOF
1b8fcd9c 808 cat >"$initdir/opt/script0.sh" <<EOF
93f59701
LB
809#!/bin/bash
810set -e
811test -e /usr/lib/os-release
7a17e41d 812echo bar >\${STATE_DIRECTORY}/foo
93f59701
LB
813cat /usr/lib/extension-release.d/extension-release.app0
814EOF
1b8fcd9c
FS
815 chmod +x "$initdir/opt/script0.sh"
816 echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
4f2dba98 817 mksquashfs "$initdir" "$oldinitdir/usr/share/app0.raw" -noappend
1b8fcd9c 818
db776f69
MG
819 export initdir="$TESTDIR/conf0"
820 mkdir -p "$initdir/etc/extension-release.d" "$initdir/etc/systemd/system" "$initdir/opt"
821 grep "^ID=" "$os_release" >"$initdir/etc/extension-release.d/extension-release.conf0"
822 echo "${version_id}" >>"$initdir/etc/extension-release.d/extension-release.conf0"
823 ( echo "${version_id}"
824 echo "CONFEXT_IMAGE_ID=app" ) >>"$initdir/etc/extension-release.d/extension-release.conf0"
825 echo MARKER_1 >"$initdir/etc/systemd/system/some_file"
826 mksquashfs "$initdir" "$oldinitdir/usr/share/conf0.raw" -noappend
827
1b8fcd9c
FS
828 export initdir="$TESTDIR/app1"
829 mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
9a4b883b 830 grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
9ead4184
LP
831 ( echo "${version_id}"
832 echo "SYSEXT_SCOPE=portable"
e8114a4f
LB
833 echo "SYSEXT_IMAGE_ID=app"
834 echo "SYSEXT_IMAGE_VERSION=1"
9ead4184 835 echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
9a4b883b 836 setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
1b8fcd9c 837 cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
93f59701
LB
838[Service]
839Type=oneshot
840RemainAfterExit=yes
841ExecStart=/opt/script1.sh
3fa80e5e
LB
842StateDirectory=app1
843RuntimeDirectory=app1
93f59701 844EOF
1b8fcd9c 845 cat >"$initdir/opt/script1.sh" <<EOF
93f59701
LB
846#!/bin/bash
847set -e
848test -e /usr/lib/os-release
7a17e41d 849echo baz >\${STATE_DIRECTORY}/foo
9a4b883b 850cat /usr/lib/extension-release.d/extension-release.app2
93f59701 851EOF
1b8fcd9c
FS
852 chmod +x "$initdir/opt/script1.sh"
853 echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"
4f2dba98 854 mksquashfs "$initdir" "$oldinitdir/usr/share/app1.raw" -noappend
ab4d43c5
KL
855
856 export initdir="$TESTDIR/app-nodistro"
857 mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
16c1ca0d
KL
858 ( echo "ID=_any"
859 echo "ARCHITECTURE=_any" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-nodistro"
ab4d43c5
KL
860 echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
861 mksquashfs "$initdir" "$oldinitdir/usr/share/app-nodistro.raw" -noappend
b856f1df
MG
862
863 export initdir="$TESTDIR/service-scoped-test"
864 mkdir -p "$initdir/etc/extension-release.d" "$initdir/etc/systemd/system"
865 ( echo "ID=_any"
866 echo "ARCHITECTURE=_any" ) >"$initdir/etc/extension-release.d/extension-release.service-scoped-test"
867 echo MARKER_CONFEXT_123 >"$initdir/etc/systemd/system/some_file"
868 mksquashfs "$initdir" "$oldinitdir/etc/service-scoped-test.raw" -noappend
41712cd1
MT
869
870 # We need to create a dedicated sysext image to test the reload mechanism. If we share an image to install the
871 # 'foo.service' it will be loaded from another test run, which will impact the targeted test.
872 export initdir="$TESTDIR/app-reload"
873 mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
874 ( echo "ID=_any"
875 echo "ARCHITECTURE=_any"
876 echo "EXTENSION_RELOAD_MANAGER=1" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-reload"
877 mkdir -p "$initdir/usr/lib/systemd/system/multi-user.target.d"
878 cat >"${initdir}/usr/lib/systemd/system/foo.service" <<EOF
879[Service]
880Type=oneshot
881RemainAfterExit=yes
882ExecStart=echo foo
883
884[Install]
885WantedBy=multi-user.target
886EOF
887 { echo "[Unit]"; echo "Upholds=foo.service"; } > "$initdir/usr/lib/systemd/system/multi-user.target.d/10-foo-service.conf"
888 mksquashfs "$initdir" "$oldinitdir/usr/share/app-reload.raw" -noappend
9785c44d
LB
889 )
890}
891
889a9042
RC
892setup_basic_environment() {
893 # create the basic filesystem layout
894 setup_basic_dirs
895
896 install_systemd
897 install_missing_libraries
898 install_config_files
f4c40fd7 899 install_zoneinfo
889a9042
RC
900 create_rc_local
901 install_basic_tools
902 install_libnss
903 install_pam
904 install_dbus
905 install_fonts
4ce68ea9 906 install_locales
889a9042 907 install_keymaps
1136175c 908 install_x11_keymaps
889a9042
RC
909 install_terminfo
910 install_execs
0dd77c15
ZJS
911 install_fs_tools
912 install_modules
889a9042 913 install_plymouth
d93857ae 914 install_haveged
889a9042
RC
915 install_debug_tools
916 install_ld_so_conf
d0ac89a1
ZJS
917 install_testuser
918 has_user_dbus_socket && install_user_dbus
e3ce42e7 919 setup_selinux
889a9042
RC
920 install_depmod_files
921 generate_module_dependencies
23f8e019 922 if get_bool "$IS_BUILT_WITH_ASAN"; then
37ee8dc8 923 create_asan_wrapper
ec9181d2 924 fi
23f8e019 925 if get_bool "$TEST_INSTALL_VERITY_MINIMAL"; then
9785c44d
LB
926 install_verity_minimal
927 fi
889a9042
RC
928}
929
e3ce42e7 930setup_selinux() {
1b8fcd9c 931 dinfo "Setup SELinux"
e3ce42e7 932 # don't forget KERNEL_APPEND='... selinux=1 ...'
23f8e019 933 if ! get_bool "$SETUP_SELINUX"; then
1b8fcd9c 934 dinfo "SETUP_SELINUX != yes, skipping SELinux configuration"
e3ce42e7
EV
935 return 0
936 fi
e3ce42e7 937
5ef964f8
FS
938 for dir in /etc/selinux /usr/share/selinux; do
939 rm -rf "${initdir:?}/$dir"
940 if ! cp -ar "$dir" "$initdir/$dir"; then
941 dfatal "Failed to copy $dir"
942 exit 1
943 fi
944 done
e3ce42e7 945
8c0ace57
FS
946 # We use a custom autorelabel service instead of the SELinux provided set
947 # of units & a generator, since the generator overrides the default target
948 # to the SELinux one when it detects /.autorelabel. However, we use
949 # systemd.unit= on the kernel command cmdline which always takes precedence,
950 # rendering all SELinux efforts useless. Also, pulling in selinux-autorelabel.service
951 # explicitly doesn't work either, as it doesn't check for the presence of /.autorelabel
952 # and does the relabeling unconditionally which always ends with a reboot, so
953 # we end up in a reboot loop (and it also spews quite a lot of errors as it
954 # wants /etc/fstab and dracut-initramfs-restore).
1b8fcd9c
FS
955 touch "$initdir/.autorelabel"
956 mkdir -p "$initdir/usr/lib/systemd/tests/testdata/units/basic.target.wants"
957 ln -sf ../autorelabel.service "$initdir/usr/lib/systemd/tests/testdata/units/basic.target.wants/"
e3ce42e7 958
5ef964f8
FS
959 # Tools requires by fixfiles
960 image_install awk bash cat chcon expr egrep find grep head secon setfiles rm sort uname uniq
961 image_install fixfiles getenforce load_policy selinuxenabled sestatus
e3ce42e7
EV
962}
963
a2fbff31
EV
964install_valgrind() {
965 if ! type -p valgrind; then
966 dfatal "Failed to install valgrind"
967 exit 1
968 fi
969
71116990 970 local valgrind_bins valgrind_libs valgrind_supp
1b8fcd9c 971
71116990
ZJS
972 readarray -t valgrind_bins < <(strace -e execve valgrind /bin/true 2>&1 >/dev/null |
973 sed -r -n 's/execve\("([^"]*)".*/\1/p')
c66e2f6c 974 image_install "${valgrind_bins[@]}"
a2fbff31 975
71116990
ZJS
976 readarray -t valgrind_libs < <(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null |
977 sed -r -n 's|.*calling init: (/.*vgpreload_.*)|\1|p')
c66e2f6c 978 image_install "${valgrind_libs[@]}"
a2fbff31 979
71116990
ZJS
980 readarray -t valgrind_supp < <(strace -e open valgrind /bin/true 2>&1 >/dev/null |
981 sed -r -n 's,open\("([^"]*(/debug[^"]*|\.supp))".*= [0-9].*,\1,p')
982 image_install "${valgrind_supp[@]}"
a2fbff31
EV
983}
984
cb2f9d3f 985create_valgrind_wrapper() {
1b8fcd9c
FS
986 local valgrind_wrapper="$initdir/$ROOTLIBDIR/systemd-under-valgrind"
987 ddebug "Create $valgrind_wrapper"
988 cat >"$valgrind_wrapper" <<EOF
ff12a795 989#!/usr/bin/env bash
cb2f9d3f 990
23cabb68 991mount -t proc proc /proc
8e78dca9 992exec valgrind --leak-check=full --track-fds=yes --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@"
cb2f9d3f 993EOF
1b8fcd9c 994 chmod 0755 "$valgrind_wrapper"
cb2f9d3f
EV
995}
996
1786fae3 997create_asan_wrapper() {
88c98cb2 998 local asan_wrapper default_asan_options default_ubsan_options default_environment manager_environment
37ee8dc8 999
648fd189
FS
1000 [[ -z "$ASAN_RT_PATH" ]] && dfatal "ASAN_RT_PATH is empty, but it shouldn't be"
1001
91da9458 1002 default_asan_options="${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:disable_coredump=0:use_madv_dontdump=1}"
ba79e8c2 1003 default_ubsan_options="${UBSAN_OPTIONS:-print_stacktrace=1:print_summary=1:halt_on_error=1}"
37ee8dc8 1004
ba79e8c2
FS
1005 if [[ "$ASAN_COMPILER" == "clang" ]]; then
1006 # clang: install llvm-symbolizer to generate useful reports
1007 # See: https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
1008 image_install "llvm-symbolizer"
1786fae3 1009
ba79e8c2
FS
1010 # Let's add the ASan DSO's path to the dynamic linker's cache. This is pretty
1011 # unnecessary for gcc & libasan, however, for clang this is crucial, as its
1012 # runtime ASan DSO is in a non-standard (library) path.
1013 mkdir -p "${initdir:?}/etc/ld.so.conf.d/"
1014 echo "${ASAN_RT_PATH%/*}" >"${initdir:?}/etc/ld.so.conf.d/asan-path-override.conf"
25bc4697 1015 ldconfig -r "$initdir"
ba79e8c2 1016 fi
25213e16 1017
ba79e8c2
FS
1018 # Create a simple environment file which can be included by systemd services
1019 # that need it (i.e. services that utilize DynamicUser=true and bash, etc.)
1020 cat >"${initdir:?}/usr/lib/systemd/systemd-asan-env" <<EOF
25213e16 1021LD_PRELOAD=$ASAN_RT_PATH
ba79e8c2 1022ASAN_OPTIONS=$default_asan_options
b8dd2766 1023LSAN_OPTIONS=detect_leaks=0
ba79e8c2
FS
1024UBSAN_OPTIONS=$default_ubsan_options
1025EOF
1786fae3 1026
ba79e8c2
FS
1027 default_environment=(
1028 "ASAN_OPTIONS='$default_asan_options'"
1029 "UBSAN_OPTIONS='$default_ubsan_options'"
1030 "ASAN_RT_PATH='$ASAN_RT_PATH'"
1031 )
88c98cb2
FS
1032 manager_environment=(
1033 "ASAN_OPTIONS='$default_asan_options:log_path=/systemd-pid1.asan.log:log_to_syslog=1'"
1034 "UBSAN_OPTIONS='$default_ubsan_options:log_path=/systemd-pid1.ubsan.log:log_to_syslog=1'"
1035 "ASAN_RT_PATH='$ASAN_RT_PATH'"
1036 )
648fd189 1037
ba79e8c2
FS
1038 mkdir -p "${initdir:?}/etc/systemd/system.conf.d/"
1039 cat >"${initdir:?}/etc/systemd/system.conf.d/asan.conf" <<EOF
1040[Manager]
1041DefaultEnvironment=${default_environment[*]}
88c98cb2 1042ManagerEnvironment=${manager_environment[*]}
ba79e8c2 1043DefaultTimeoutStartSec=180s
ba79e8c2 1044EOF
1786fae3 1045
ba79e8c2
FS
1046 # ASAN and syscall filters aren't compatible with each other.
1047 find "${initdir:?}" -name '*.service' -type f -print0 | xargs -0 sed -i 's/^\(MemoryDeny\|SystemCall\)/#\1/'
1786fae3 1048
ba79e8c2
FS
1049 mkdir -p "${initdir:?}/etc/systemd/system/systemd-journald.service.d/"
1050 cat >"${initdir:?}/etc/systemd/system/systemd-journald.service.d/asan-env.conf" <<EOF
1051[Service]
88ed0f26
EV
1052# The redirection of ASAN reports to a file prevents them from ending up in /dev/null.
1053# But, apparently, sometimes it doesn't work: https://github.com/google/sanitizers/issues/886.
ba79e8c2 1054Environment=ASAN_OPTIONS=$default_asan_options:log_path=/systemd-journald.asan.log UBSAN_OPTIONS=$default_ubsan_options:log_path=/systemd-journald.ubsan.log
88ed0f26 1055
6141c6c9
EV
1056# Sometimes UBSan sends its reports to stderr regardless of what is specified in log_path
1057# Let's try to catch them by redirecting stderr (and stdout just in case) to a file
1058# See https://github.com/systemd/systemd/pull/12524#issuecomment-491108821
ba79e8c2
FS
1059StandardOutput=file:/systemd-journald.out
1060EOF
6141c6c9 1061
ba79e8c2
FS
1062 # 90s isn't enough for some services to finish when literally everything is run
1063 # under ASan+UBSan in containers, which, in turn, are run in VMs.
1064 # Let's limit which environments such services should be executed in.
1065 mkdir -p "${initdir:?}/etc/systemd/system/systemd-hwdb-update.service.d/"
1066 cat >"${initdir:?}/etc/systemd/system/systemd-hwdb-update.service.d/asan.conf" <<EOF
1067[Unit]
1068ConditionVirtualization=container
082bcdca 1069
ba79e8c2
FS
1070[Service]
1071TimeoutSec=240s
1072EOF
a5372344 1073
ba79e8c2
FS
1074 # Let's override another hard-coded timeout that kicks in too early
1075 mkdir -p "${initdir:?}/etc/systemd/system/systemd-journal-flush.service.d/"
1076 cat >"${initdir:?}/etc/systemd/system/systemd-journal-flush.service.d/asan.conf" <<EOF
1077[Service]
1078TimeoutSec=180s
1786fae3
EV
1079EOF
1080
ba79e8c2
FS
1081 asan_wrapper="${initdir:?}/${PATH_TO_INIT:?}"
1082 # Sanity check to make sure we don't overwrite something we shouldn't.
1083 [[ "$asan_wrapper" =~ systemd-under-asan$ ]]
1084
1085 cat >"$asan_wrapper" <<EOF
1086#!/usr/bin/env bash
1087set -eux
1088
3bba91ef 1089export PATH="/sbin:/bin:/usr/sbin:/usr/bin"
88c98cb2 1090export ${manager_environment[@]}
ba79e8c2 1091[[ -n "\$ASAN_OPTIONS" && -n "\$UBSAN_OPTIONS" ]]
3bba91ef 1092
ba79e8c2
FS
1093exec "$ROOTLIBDIR/systemd" "\$@"
1094EOF
1b8fcd9c 1095 chmod 0755 "$asan_wrapper"
1786fae3
EV
1096}
1097
45dbd7b6 1098create_strace_wrapper() {
1b8fcd9c
FS
1099 local strace_wrapper="$initdir/$ROOTLIBDIR/systemd-under-strace"
1100 ddebug "Create $strace_wrapper"
1101 cat >"$strace_wrapper" <<EOF
ff12a795 1102#!/usr/bin/env bash
45dbd7b6 1103
1b8fcd9c 1104exec strace -f -D -o /strace.out "$ROOTLIBDIR/systemd" "\$@"
45dbd7b6 1105EOF
1b8fcd9c 1106 chmod 0755 "$strace_wrapper"
45dbd7b6
EV
1107}
1108
0dd77c15 1109install_fs_tools() {
1b8fcd9c 1110 dinfo "Install fsck"
39e17536
FS
1111 image_install /sbin/fsck*
1112 image_install -o /bin/fsck*
331fb4ca
EV
1113
1114 # fskc.reiserfs calls reiserfsck. so, install it
39e17536 1115 image_install -o reiserfsck
0dd77c15
ZJS
1116
1117 # we use mkfs in system-repart tests
39e17536
FS
1118 image_install /sbin/mkfs.ext4
1119 image_install /sbin/mkfs.vfat
0dd77c15
ZJS
1120}
1121
1122install_modules() {
1123 dinfo "Install modules"
1124
adafa3b2 1125 instmods bridge dummy ext4 ipvlan macvlan vfat veth
807626d1 1126 instmods loop =block
35cde9e9 1127 instmods nls_ascii =nls
807626d1 1128 instmods overlay =overlayfs
23ff8a77 1129 instmods scsi_debug
0dd77c15 1130
23f8e019 1131 if get_bool "$LOOKS_LIKE_SUSE"; then
adafa3b2 1132 instmods af_packet
0dd77c15 1133 fi
9974ff63
EV
1134}
1135
889a9042
RC
1136install_dmevent() {
1137 instmods dm_crypt =crypto
59279e96 1138 inst_binary dmeventd
e3d9a2e7 1139 image_install "${ROOTLIBDIR:?}"/system/dm-event.{service,socket}
23f8e019 1140 if get_bool "$LOOKS_LIKE_DEBIAN"; then
ac289ce3 1141 # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu
9c869ff6
DJL
1142 # and since buster/bionic 95-dm-notify.rules
1143 # see https://gitlab.com/debian-lvm/lvm2/blob/master/debian/patches/udev.patch
1144 inst_rules 55-dm.rules 60-persistent-storage-dm.rules 95-dm-notify.rules
ac289ce3
EV
1145 else
1146 inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules
1147 fi
23f8e019 1148 if get_bool "$LOOKS_LIKE_SUSE"; then
2aa5a13a
ER
1149 inst_rules 60-persistent-storage.rules 61-persistent-storage-compat.rules 99-systemd.rules
1150 fi
889a9042
RC
1151}
1152
9e7c3bd4
FS
1153install_multipath() {
1154 instmods "=md" multipath
1155 image_install kpartx /lib/udev/kpartx_id lsmod mpathpersist multipath multipathd partx
1156 image_install "${ROOTLIBDIR:?}"/system/multipathd.{service,socket}
1157 if get_bool "$LOOKS_LIKE_DEBIAN"; then
7eb234fe 1158 # Note: try both 60-kpartx.rules (as seen on Debian Sid with 0.9.4-7) and 95-kpartx.rules (as seen on
519f0074 1159 # Ubuntu Jammy with 0.8.8-1ubuntu1.22.04.4)
7eb234fe 1160 inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-kpartx.rules 60-multipath.rules 68-del-part-nodes.rules 95-kpartx.rules
9e7c3bd4
FS
1161 else
1162 inst_rules 11-dm-mpath.rules 11-dm-parts.rules 62-multipath.rules 66-kpartx.rules 68-del-part-nodes.rules
1163 fi
1164 mkdir -p "${initdir:?}/etc/multipath"
1165
1166 local file
1167 while read -r file; do
1168 # Install libraries required by the given library
1169 inst_libs "$file"
1170 # Install the library itself and create necessary symlinks
1171 inst_library "$file"
1172 done < <(find /lib*/multipath -type f)
9e7c3bd4
FS
1173}
1174
4999f368 1175install_lvm() {
f9ba9d3e
FS
1176 local lvm_rules rule_prefix
1177
4999f368
FS
1178 image_install lvm
1179 image_install "${ROOTLIBDIR:?}"/system/lvm2-lvmpolld.{service,socket}
e50d743f 1180 image_install "${ROOTLIBDIR:?}"/system/{blk-availability,lvm2-monitor}.service
4999f368 1181 image_install -o "/lib/tmpfiles.d/lvm2.conf"
f9ba9d3e 1182
4999f368 1183 if get_bool "$LOOKS_LIKE_DEBIAN"; then
f9ba9d3e
FS
1184 lvm_rules="56-lvm.rules"
1185 rule_prefix=""
4999f368 1186 else
f9ba9d3e
FS
1187 lvm_rules="11-dm-lvm.rules"
1188 rule_prefix="dm-"
4999f368 1189 fi
f9ba9d3e
FS
1190
1191 # Support the new udev autoactivation introduced in lvm 2.03.14
1192 # https://sourceware.org/git/?p=lvm2.git;a=commit;h=67722b312390cdab29c076c912e14bd739c5c0f6
1193 # Static autoactivation (via lvm2-activation-generator) was dropped
1194 # in lvm 2.03.15
1195 # https://sourceware.org/git/?p=lvm2.git;a=commit;h=ee8fb0310c53ed003a43b324c99cdfd891dd1a7c
1196 if [[ -f "/lib/udev/rules.d/69-${rule_prefix}lvm.rules" ]]; then
1197 inst_rules "$lvm_rules" "69-${rule_prefix}lvm.rules"
1198 else
1199 image_install "${ROOTLIBDIR:?}"/system-generators/lvm2-activation-generator
1200 image_install "${ROOTLIBDIR:?}"/system/lvm2-pvscan@.service
1201 inst_rules "$lvm_rules" "69-${rule_prefix}lvm-metad.rules"
1202 fi
1203
4999f368
FS
1204 mkdir -p "${initdir:?}/etc/lvm"
1205}
1206
5b4fa6f1
YW
1207host_has_btrfs() (
1208 set -e
1209 modprobe -nv btrfs && command -v mkfs.btrfs && command -v btrfs || return $?
1210)
1211
babe9355
FS
1212install_btrfs() {
1213 instmods btrfs
1214 # Not all utilities provided by btrfs-progs are listed here; extend the list
1215 # if necessary
1216 image_install btrfs btrfstune mkfs.btrfs
1217 inst_rules 64-btrfs-dm.rules
1218}
1219
f4e64b6e
FS
1220install_iscsi() {
1221 # Install both client and server side stuff by default
1222 local inst="${1:-}"
1223 local file
1224
1225 # Install client-side stuff ("initiator" in iSCSI jargon) - Open-iSCSI in this case
1226 # (open-iscsi on Debian, iscsi-initiator-utils on Fedora, etc.)
1227 if [[ -z "$inst" || "$inst" =~ (client|initiator) ]]; then
1228 image_install iscsi-iname iscsiadm iscsid iscsistart
1229 image_install -o "${ROOTLIBDIR:?}"/system/iscsi-{init,onboot,shutdown}.service
1230 image_install "${ROOTLIBDIR:?}"/system/iscsid.{service,socket}
1231 image_install "${ROOTLIBDIR:?}"/system/iscsi.service
1232 mkdir -p "${initdir:?}"/var/lib/iscsi/{ifaces,isns,nodes,send_targets,slp,static}
1233 mkdir -p "${initdir:?}/etc/iscsi"
1234 echo "iscsid.startup = /bin/systemctl start iscsid.socket" >"${initdir:?}/etc/iscsi/iscsid.conf"
d9e1cb28
FS
1235 # Since open-iscsi 2.1.2 [0] the initiator name should be generated via
1236 # a one-time service instead of distro package's post-install scripts.
1237 # However, some distros still use this approach even after this patch,
1238 # so prefer the already existing initiatorname.iscsi file if it exists.
1239 #
1240 # [0] https://github.com/open-iscsi/open-iscsi/commit/f37d5b653f9f251845db3f29b1a3dcb90ec89731
1241 if [[ ! -e /etc/iscsi/initiatorname.iscsi ]]; then
1242 image_install "${ROOTLIBDIR:?}"/system/iscsi-init.service
326425fb
FS
1243 if get_bool "$IS_BUILT_WITH_ASAN"; then
1244 # The iscsi-init.service calls `sh` which might, in certain circumstances,
1245 # pull in instrumented systemd NSS modules causing `sh` to fail. Let's mitigate
1246 # this by pulling in an env file crafted by `create_asan_wrapper()` that
1247 # (among others) pre-loads ASan's DSO.
1248 mkdir -p "${initdir:?}/etc/systemd/system/iscsi-init.service.d/"
1249 printf "[Service]\nEnvironmentFile=/usr/lib/systemd/systemd-asan-env" >"${initdir:?}/etc/systemd/system/iscsi-init.service.d/asan-env.conf"
1250 fi
d9e1cb28
FS
1251 else
1252 inst_simple "/etc/iscsi/initiatorname.iscsi"
1253 fi
f4e64b6e
FS
1254 fi
1255
1256 # Install server-side stuff ("target" in iSCSI jargon) - TGT in this case
1257 # (tgt on Debian, scsi-target-utils on Fedora, etc.)
1258 if [[ -z "$inst" || "$inst" =~ (server|target) ]]; then
1259 image_install tgt-admin tgt-setup-lun tgtadm tgtd tgtimg
1260 image_install -o /etc/sysconfig/tgtd
1261 image_install "${ROOTLIBDIR:?}"/system/tgtd.service
1262 mkdir -p "${initdir:?}/etc/tgt"
1263 touch "${initdir:?}"/etc/tgt/{tgtd,targets}.conf
1264 # Install perl modules required by tgt-admin
1265 #
1266 # Forgive me father for I have sinned. The monstrosity below appends
1267 # a perl snippet to the `tgt-admin` perl script on the fly, which
1268 # dumps a list of files (perl modules) required by `tgt-admin` at
1269 # the runtime plus any DSOs loaded via DynaLoader. This list is then
1270 # passed to `inst_simple` which installs the necessary files into the image
1c3f490f
FS
1271 #
1272 # shellcheck disable=SC2016
f4e64b6e
FS
1273 while read -r file; do
1274 inst_simple "$file"
1c3f490f 1275 done < <(perl -- <(cat "$(command -v tgt-admin)" <(echo -e 'use DynaLoader; print map { "$_\n" } values %INC; print join("\n", @DynaLoader::dl_shared_objects)')) -p | awk '/^\// { print $1 }')
f4e64b6e
FS
1276 fi
1277}
1278
5b4fa6f1
YW
1279host_has_mdadm() (
1280 set -e
1281 command -v mdadm || return $?
1282)
1283
3c9af05c
FS
1284install_mdadm() {
1285 local unit
1286 local mdadm_units=(
1287 system/mdadm-grow-continue@.service
1288 system/mdadm-last-resort@.service
1289 system/mdadm-last-resort@.timer
1290 system/mdmon@.service
1291 system/mdmonitor-oneshot.service
1292 system/mdmonitor-oneshot.timer
1293 system/mdmonitor.service
1294 system-shutdown/mdadm.shutdown
1295 )
1296
4ed943e9 1297 instmods "=md"
3c9af05c
FS
1298 image_install mdadm mdmon
1299 inst_rules 01-md-raid-creating.rules 63-md-raid-arrays.rules 64-md-raid-assembly.rules 69-md-clustered-confirm-device.rules
1300 # Fedora/CentOS/RHEL ships this rule file
1301 [[ -f /lib/udev/rules.d/65-md-incremental.rules ]] && inst_rules 65-md-incremental.rules
1302
1303 for unit in "${mdadm_units[@]}"; do
1304 image_install "${ROOTLIBDIR:?}/$unit"
1305 done
0f236e8c
YW
1306
1307 # Disable the mdmonitor service, since it fails if there's no valid email address
1308 # configured in /etc/mdadm.conf, which just unnecessarily pollutes the logs
1309 "${SYSTEMCTL:?}" mask --root "${initdir:?}" mdmonitor.service || :
3c9af05c
FS
1310}
1311
8fa03808 1312install_compiled_systemd() {
1b8fcd9c 1313 dinfo "Install compiled systemd"
9f927e46 1314
1b8fcd9c
FS
1315 local ninja_bin
1316 ninja_bin="$(type -P ninja || type -P ninja-build)"
1317 if [[ -z "$ninja_bin" ]]; then
ca992ecf
EV
1318 dfatal "ninja was not found"
1319 exit 1
1320 fi
1b8fcd9c 1321 (set -x; DESTDIR="$initdir" "$ninja_bin" -C "$BUILD_DIR" install)
c82dc15b
LB
1322
1323 # If we are doing coverage runs, copy over the binary notes files, as lcov expects to
1324 # find them in the same directory as the runtime data counts
02d7e730 1325 if get_bool "$IS_BUILT_WITH_COVERAGE"; then
c82dc15b
LB
1326 mkdir -p "${initdir}/${BUILD_DIR:?}/"
1327 rsync -am --include='*/' --include='*.gcno' --exclude='*' "${BUILD_DIR:?}/" "${initdir}/${BUILD_DIR:?}/"
e4c822e9
FS
1328 # Set effective & default ACLs for the build dir so unprivileged
1329 # processes can write gcda files with coverage stats
1330 setfacl -R -m 'd:o:rwX' -m 'o:rwX' "${initdir}/${BUILD_DIR:?}/"
c82dc15b 1331 fi
8fa03808
DS
1332}
1333
fdd380dd
FS
1334install_package_file() {
1335 local file="${1:?}"
1336
1337 # Skip missing files (like /etc/machine-info)
1338 [[ ! -e "$file" ]] && return 0
1339 # Skip python unit tests, since the image_install machinery will try to pull
1340 # in the whole python stack in a very questionable state, making the tests fail.
1341 # And given we're trying to transition to mkosi-based images anyway I'm not even
1342 # going to bother
1343 [[ "$file" =~ /tests/unit-tests/.*.py$ ]] && return 0
1344 # If the current file is a directory, create it with the original
1345 # mode; if it's a symlink to a directory, copy it as-is
1346 if [[ -d "$file" ]]; then
1347 inst_dir "$file"
1348 else
1349 inst "$file"
1350 fi
1351}
1352
8fa03808 1353install_debian_systemd() {
1b8fcd9c 1354 dinfo "Install debian systemd"
8fa03808 1355
fdd380dd 1356 local deb file
96af59aa
FS
1357
1358 while read -r deb; do
8fa03808 1359 ddebug "Install debian files from package $deb"
fdd380dd
FS
1360 while read -r file; do
1361 install_package_file "$file"
1362 done < <(dpkg-query -L "$deb" 2>/dev/null)
96af59aa 1363 done < <(grep -E '^Package:' "${SOURCE_DIR}/debian/control" | cut -d ':' -f 2)
8fa03808
DS
1364}
1365
8ddbd9e0
FS
1366install_rpm() {
1367 local rpm="${1:?}"
1368 local file
1369
1370 if ! rpm -q "$rpm" >/dev/null; then
1371 derror "RPM $rpm is not installed"
1372 return 1
1373 fi
1374
1375 dinfo "Installing contents of RPM $rpm"
1376 while read -r file; do
fdd380dd 1377 install_package_file "$file"
8ddbd9e0
FS
1378 done < <(rpm -ql "$rpm")
1379}
1380
abf06267 1381install_suse_systemd() {
abf06267
FB
1382 local pkgs
1383
08abfd0b
FB
1384 dinfo "Install basic filesystem structure"
1385 install_rpm filesystem
1386
abf06267
FB
1387 dinfo "Install SUSE systemd"
1388
1389 pkgs=(
1390 systemd
03b1e10f 1391 systemd-boot
abf06267
FB
1392 systemd-container
1393 systemd-coredump
1394 systemd-experimental
33ce0a89 1395 systemd-homed
abf06267 1396 systemd-journal-remote
3adac701
FB
1397 # Since commit fb6f25d7b979134a, systemd-resolved, which is shipped by
1398 # systemd-network sub-package on openSUSE, has its own testsuite.
1399 systemd-network
abf06267
FB
1400 systemd-portable
1401 udev
1402 )
1403
1404 for p in "${pkgs[@]}"; do
1405 rpm -q "$p" &>/dev/null || continue
1406
8ddbd9e0 1407 install_rpm "$p"
abf06267
FB
1408 done
1409
fa2745a3 1410 dinfo "Install the data needed by the tests at runtime"
4e8172c8 1411 inst_recursive "${SOURCE_DIR}/testdata"
372d40fb 1412 inst_recursive "${SOURCE_DIR}/unit-tests/manual"
4e8172c8
FB
1413
1414 # On openSUSE, this directory is not created at package install, at least
1415 # for now.
abf06267
FB
1416 mkdir -p "$initdir/var/log/journal/remote"
1417}
1418
8ddbd9e0
FS
1419install_fedora_systemd() {
1420 local required_packages=(
1421 systemd
1422 systemd-container
1423 systemd-libs
1424 systemd-pam
1425 systemd-tests
1426 systemd-udev
1427 )
1428 local optional_packages=(
1429 systemd-boot-unsigned
1430 systemd-bootchart
1431 systemd-journal-remote
1432 systemd-networkd
1433 systemd-oomd-defaults
1434 systemd-resolved
1435 )
1436 local package
1437
1438 for package in "${required_packages[@]}"; do
1439 install_rpm "$package"
1440 done
1441
1442 for package in "${optional_packages[@]}"; do
1443 rpm -q "$package" >/dev/null || continue
1444 install_rpm "$package"
1445 done
1446}
1447
8fa03808 1448install_distro_systemd() {
1b8fcd9c 1449 dinfo "Install distro systemd"
8fa03808 1450
23f8e019 1451 if get_bool "$LOOKS_LIKE_DEBIAN"; then
8fa03808 1452 install_debian_systemd
abf06267
FB
1453 elif get_bool "$LOOKS_LIKE_SUSE"; then
1454 install_suse_systemd
8ddbd9e0
FS
1455 elif get_bool "$LOOKS_LIKE_FEDORA"; then
1456 install_fedora_systemd
8fa03808
DS
1457 else
1458 dfatal "NO_BUILD not supported for this distro"
1459 exit 1
1460 fi
1461}
1462
1463install_systemd() {
1b8fcd9c 1464 dinfo "Install systemd"
23f8e019 1465 if get_bool "$NO_BUILD"; then
8fa03808
DS
1466 install_distro_systemd
1467 else
1468 install_compiled_systemd
1469 fi
1470
ceea144e 1471 # Remove unneeded documentation
02d7e730 1472 rm -fr "${initdir:?}"/usr/share/{man,doc}
61b480b6 1473
ceea144e
FS
1474 # Enable debug logging in PID1
1475 mkdir -p "$initdir/etc/systemd/system.conf.d/"
1476 echo -ne "[Manager]\nLogLevel=debug\n" >"$initdir/etc/systemd/system.conf.d/10-log-level.conf"
bf6ef6b6 1477 if [[ -n "$TEST_SYSTEMD_LOG_LEVEL" ]]; then
ceea144e 1478 echo DefaultEnvironment=SYSTEMD_LOG_LEVEL="$TEST_SYSTEMD_LOG_LEVEL" >>"$initdir/etc/systemd/system.conf.d/99-log-level.conf"
bf6ef6b6 1479 fi
7f048f0e
FS
1480 # Enable debug logging for user instances as well
1481 mkdir -p "$initdir/etc/systemd/user.conf.d/"
1482 echo -ne "[Manager]\nLogLevel=debug\n" >"$initdir/etc/systemd/user.conf.d/10-log-level.conf"
ceea144e
FS
1483 # Store coredumps in journal
1484 mkdir -p "$initdir/etc/systemd/coredump.conf.d/"
1485 echo -ne "[Coredump]\nStorage=journal\n" >"$initdir/etc/systemd/coredump.conf.d/10-storage-journal.conf"
064a5c14 1486 # Propagate SYSTEMD_UNIT_PATH to user systemd managers
ceea144e
FS
1487 mkdir -p "$initdir/etc/systemd/system/user@.service.d/"
1488 echo -ne "[Service]\nPassEnvironment=SYSTEMD_UNIT_PATH\n" >"$initdir/etc/systemd/system/user@.service.d/99-SYSTEMD_UNIT_PATH.conf"
02d7e730 1489
b6fc5240
FS
1490 # When built with gcov, disable ProtectSystem= and ProtectHome= in the test
1491 # images, since it prevents gcov to write the coverage reports (*.gcda
93c3b698 1492 # files)
02d7e730
FS
1493 if get_bool "$IS_BUILT_WITH_COVERAGE"; then
1494 mkdir -p "$initdir/etc/systemd/system/service.d/"
ceea144e 1495 echo -ne "[Service]\nProtectSystem=no\nProtectHome=no\n" >"$initdir/etc/systemd/system/service.d/99-gcov-override.conf"
52db3601
FS
1496 # Similarly, set ReadWritePaths= to the $BUILD_DIR in the test image to make the coverage work with
1497 # units using DynamicUser=yes. Do this only for services with test- prefix and a couple of
1498 # known-to-use DynamicUser=yes services, as setting this system-wide has many undesirable
1499 # side-effects, as it creates its own namespace.
607be850 1500 for service in capsule@ test- systemd-journal-{gatewayd,upload}; do
52db3601
FS
1501 mkdir -p "$initdir/etc/systemd/system/$service.service.d/"
1502 echo -ne "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/$service.service.d/99-gcov-rwpaths-override.conf"
1503 done
e660c590
FS
1504 # Ditto, but for the user daemon
1505 mkdir -p "$initdir/etc/systemd/user/test-.service.d/"
ceea144e 1506 echo -ne "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/user/test-.service.d/99-gcov-rwpaths-override.conf"
3b2823a7
FS
1507 # Bind the $BUILD_DIR into nspawn containers that are executed using
1508 # machinectl. Unfortunately, the .nspawn files don't support drop-ins
1509 # so we have to inject the bind mount directly into
1510 # the systemd-nspawn@.service unit.
1511 cp "$initdir/usr/lib/systemd/system/systemd-nspawn@.service" "$initdir/etc/systemd/system/systemd-nspawn@.service"
1512 sed -ri "s/^ExecStart=.+$/& --bind=${BUILD_DIR//\//\\\/}/" "$initdir/etc/systemd/system/systemd-nspawn@.service"
786f6d81
FS
1513 # Pass the $BUILD_DIR as $COVERAGE_BUILD_DIR env variable to the system
1514 # manager, similarly to what we do with $ASAN_RT_PATH during sanitized
1515 # builds
1516 mkdir -p "$initdir/etc/systemd/system.conf.d/"
1517 echo -ne "[Manager]\nDefaultEnvironment=COVERAGE_BUILD_DIR=$BUILD_DIR\n" >"$initdir/etc/systemd/system.conf.d/99-COVERAGE_BUILD_DIR.conf"
02d7e730 1518 fi
6f73ef8b
FS
1519
1520 # If we're built with -Dportabled=false, tests with systemd-analyze
1521 # --profile will fail. Since we need just the profile (text) files, let's
1522 # copy them into the image if they don't exist there.
1523 local portable_dir="${initdir:?}${ROOTLIBDIR:?}/portable"
1524 if [[ ! -d "$portable_dir/profile/strict" ]]; then
1525 dinfo "Couldn't find portable profiles in the test image"
1526 dinfo "Copying them directly from the source tree"
1527 mkdir -p "$portable_dir"
1528 cp -frv "${SOURCE_DIR:?}/src/portable/profile" "$portable_dir"
1529 fi
889a9042
RC
1530}
1531
d7a4278d 1532get_ldpath() {
1b8fcd9c
FS
1533 local rpath
1534 rpath="$(objdump -p "${1:?}" 2>/dev/null | awk "/R(UN)?PATH/ { print \"$initdir\" \$2 }" | paste -sd :)"
5bb4503d
LP
1535
1536 if [ -z "$rpath" ] ; then
1b8fcd9c 1537 echo "$BUILD_DIR"
5bb4503d 1538 else
1b8fcd9c 1539 echo "$rpath"
5bb4503d 1540 fi
d7a4278d
FS
1541}
1542
889a9042 1543install_missing_libraries() {
1b8fcd9c 1544 dinfo "Install missing libraries"
889a9042 1545 # install possible missing libraries
26c2b302 1546 for i in "${initdir:?}"{,/usr}/{sbin,bin}/* "$initdir"{,/usr}/lib/systemd/{,tests/unit-tests/{,manual/,unsafe/}}*; do
1b8fcd9c 1547 LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath "$i")" inst_libs "$i"
889a9042 1548 done
b7fca1b0 1549
5f347d31
FS
1550 # Install libgcc_s.so if available, since it's dlopen()ed by libpthread
1551 # and might cause unexpected failures during pthread_exit()/pthread_cancel()
1552 # if not present
1553 # See: https://github.com/systemd/systemd/pull/23858
1554 while read -r libgcc_s; do
1555 [[ -e "$libgcc_s" ]] && inst_library "$libgcc_s"
1556 done < <(ldconfig -p | awk '/\/libgcc_s.so.1$/ { print $4 }')
1557
1b8fcd9c 1558 local lib path
b7fca1b0
LB
1559 # A number of dependencies is now optional via dlopen, so the install
1560 # script will not pick them up, since it looks at linkage.
1d98716e 1561 for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw xkbcommon p11-kit-1 libarchive libgcrypt libkmod; do
a9d34376 1562 ddebug "Searching for $lib via pkg-config"
1b8fcd9c
FS
1563 if pkg-config --exists "$lib"; then
1564 path="$(pkg-config --variable=libdir "$lib")"
a9d34376
LB
1565 if [ -z "${path}" ]; then
1566 ddebug "$lib.pc does not contain a libdir variable, skipping"
1567 continue
1568 fi
1569
1570 if ! [[ ${lib} =~ ^lib ]]; then
1571 lib="lib${lib}"
1572 fi
da035a3a
LB
1573 # p11-kit-1's .so doesn't have the API level in the name
1574 if [[ ${lib} =~ p11-kit-1$ ]]; then
1575 lib="libp11-kit"
1576 fi
a9d34376
LB
1577 # Some pkg-config files are broken and give out the wrong paths
1578 # (eg: libcryptsetup), so just ignore them
1579 inst_libs "${path}/${lib}.so" || true
1580 inst_library "${path}/${lib}.so" || true
1136175c
YW
1581
1582 if [[ "$lib" == "libxkbcommon" ]]; then
1583 install_x11_keymaps full
1584 fi
a9d34376
LB
1585 else
1586 ddebug "$lib.pc not found, skipping"
1587 continue
1588 fi
b7fca1b0 1589 done
3c5f7ec4
DDM
1590
1591 # Install extra openssl 3 stuff
1592 path="$(pkg-config --variable=libdir libcrypto)"
1593 inst_simple "${path}/ossl-modules/legacy.so" || true
1594 inst_simple "${path}/ossl-modules/fips.so" || true
1595 inst_simple "${path}/engines-3/afalg.so" || true
1596 inst_simple "${path}/engines-3/capi.so" || true
1597 inst_simple "${path}/engines-3/loader_attic.so" || true
1598 inst_simple "${path}/engines-3/padlock.so" || true
bf3598be
DDM
1599
1600 # Binaries from mtools depend on the gconv modules to translate between codepages. Because there's no
1601 # pkg-config file for these, we copy every gconv/ directory we can find in /usr/lib and /usr/lib64.
1602 # shellcheck disable=SC2046
1603 inst_recursive $(find /usr/lib* -name gconv 2>/dev/null)
889a9042
RC
1604}
1605
1506edca 1606cleanup_loopdev() {
1b8fcd9c 1607 if [ -n "${LOOPDEV:=}" ]; then
1506edca
ZJS
1608 ddebug "losetup -d $LOOPDEV"
1609 losetup -d "${LOOPDEV}"
2991fa41 1610 unset LOOPDEV
1506edca
ZJS
1611 fi
1612}
1613
b92c3df2 1614add_at_exit_handler cleanup_loopdev
1506edca 1615
889a9042 1616create_empty_image() {
00c26769 1617 if [[ -z "${IMAGE_NAME:=}" ]]; then
8c3534b5
ZJS
1618 echo "create_empty_image: \$IMAGE_NAME not set"
1619 exit 1
1620 fi
1621
98b27937 1622 # Partition sizes are in MiBs
82929336
FS
1623 local root_size=768
1624 local data_size=100
97bbb9cf
YW
1625 local esp_size=128
1626 local boot_size=128
1627 local total=
23f8e019 1628 if ! get_bool "$NO_BUILD"; then
63878c52 1629 if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then
00c26769 1630 root_size=$((root_size + 200))
63878c52
LB
1631 fi
1632 if meson configure "${BUILD_DIR:?}" | grep 'link-.*-shared' | awk '{ print $2 }' | grep -q 'false'; then
00c26769 1633 root_size=$((root_size + 200))
63878c52 1634 fi
02d7e730 1635 if get_bool "$IS_BUILT_WITH_COVERAGE"; then
00c26769 1636 root_size=$((root_size + 250))
c82dc15b 1637 fi
82929336
FS
1638 if get_bool "$IS_BUILT_WITH_ASAN"; then
1639 root_size=$((root_size * 2))
1640 fi
853401a6 1641 fi
82929336 1642
0334afe4
FS
1643 if [[ "${IMAGE_ADDITIONAL_ROOT_SIZE:-0}" -gt 0 ]]; then
1644 root_size=$((root_size + IMAGE_ADDITIONAL_ROOT_SIZE))
1645 fi
1646 if [[ "${IMAGE_ADDITIONAL_DATA_SIZE:-0}" -gt 0 ]]; then
1647 data_size=$((data_size + IMAGE_ADDITIONAL_DATA_SIZE))
14697c41 1648 fi
80c53fe7 1649
97bbb9cf
YW
1650 total=$((root_size + data_size + esp_size + boot_size))
1651
1652 echo "Setting up ${IMAGE_PUBLIC:?} (${total} MB)"
1b8fcd9c 1653 rm -f "${IMAGE_PRIVATE:?}" "$IMAGE_PUBLIC"
ec43f686 1654
889a9042 1655 # Create the blank file to use as a root filesystem
97bbb9cf 1656 truncate -s "${total}M" "$IMAGE_PUBLIC"
ec43f686 1657
00c26769
FS
1658 LOOPDEV="$(losetup --show -P -f "$IMAGE_PUBLIC")"
1659 [[ -b "$LOOPDEV" ]] || return 1
98b27937 1660 # Create two partitions - a root one and a data one (utilized by some tests)
edbced8a 1661 sfdisk "$LOOPDEV" <<EOF
98b27937 1662label: gpt
97bbb9cf 1663type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B name=esp size=${esp_size}M
a0b50e4d 1664type=$("${SYSTEMD_ID128:?}" show root -Pu) name=root size=${root_size}M bootable
97bbb9cf 1665type=BC13C2FF-59E6-4262-A352-B275FD6F7172 name=boot size=${boot_size}M
98b27937 1666type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=data
889a9042
RC
1667EOF
1668
053edc5b
EV
1669 udevadm settle
1670
97bbb9cf
YW
1671 if ! mkfs -t vfat "${LOOPDEV}p1"; then
1672 dfatal "Failed to mkfs -t vfat ${LOOPDEV}p1"
1673 exit 1
1674 fi
1675
bac05644 1676 local label=(-L systemd_boot)
331fb4ca 1677 # mkfs.reiserfs doesn't know -L. so, use --label instead
bac05644 1678 [[ "$FSTYPE" == "reiserfs" ]] && label=(--label systemd_boot)
97bbb9cf
YW
1679 if ! mkfs -t "${FSTYPE}" "${label[@]}" "${LOOPDEV}p2" -q; then
1680 dfatal "Failed to mkfs -t ${FSTYPE} ${label[*]} ${LOOPDEV}p2 -q"
1681 exit 1
1682 fi
1683
1684 local label=(-L xbootldr)
1685 [[ "$FSTYPE" == "reiserfs" ]] && label=(--label xbootldr)
1686 if ! mkfs -t "${FSTYPE}" "${label[@]}" "${LOOPDEV}p3" -q; then
1687 dfatal "Failed to mkfs -t ${FSTYPE} ${label[*]} ${LOOPDEV}p3 -q"
331fb4ca
EV
1688 exit 1
1689 fi
889a9042
RC
1690}
1691
8c3534b5 1692mount_initdir() {
1b8fcd9c
FS
1693 if [ -z "${LOOPDEV:=}" ]; then
1694 [ -e "${IMAGE_PRIVATE:?}" ] && image="$IMAGE_PRIVATE" || image="${IMAGE_PUBLIC:?}"
1695 LOOPDEV="$(losetup --show -P -f "$image")"
8c3534b5 1696 [ -b "$LOOPDEV" ] || return 1
8c3534b5 1697
1506edca
ZJS
1698 udevadm settle
1699 fi
ec4cab49 1700
1b8fcd9c
FS
1701 if ! mountpoint -q "${initdir:?}"; then
1702 mkdir -p "$initdir"
97bbb9cf 1703 mount "${LOOPDEV}p2" "$initdir"
1506edca 1704 TEST_SETUP_CLEANUP_ROOTDIR=1
8c3534b5 1705 fi
8c3534b5
ZJS
1706}
1707
ec43f686
ZJS
1708cleanup_initdir() {
1709 # only umount if create_empty_image_rootdir() was called to mount it
9caab7b5
FS
1710 if get_bool "$TEST_SETUP_CLEANUP_ROOTDIR"; then
1711 _umount_dir "${initdir:?}"
1712 fi
ec43f686
ZJS
1713}
1714
1715umount_loopback() {
1716 # unmount the loopback device from all places. Otherwise we risk file
1717 # system corruption.
1b8fcd9c 1718 for device in $(losetup -l | awk '$6=="'"${IMAGE_PUBLIC:?}"'" {print $1}'); do
ec43f686
ZJS
1719 ddebug "Unmounting all uses of $device"
1720 mount | awk '/^'"${device}"'p/{print $1}' | xargs --no-run-if-empty umount -v
1721 done
1722}
1723
8c3534b5
ZJS
1724create_empty_image_rootdir() {
1725 create_empty_image
1726 mount_initdir
1727}
1728
0d6e61d6
EV
1729check_asan_reports() {
1730 local ret=0
1b8fcd9c 1731 local root="${1:?}"
88c98cb2 1732 local log report
0d6e61d6 1733
23f8e019 1734 if get_bool "$IS_BUILT_WITH_ASAN"; then
0d6e61d6
EV
1735 ls -l "$root"
1736 if [[ -e "$root/systemd.asan.log.1" ]]; then
1737 cat "$root/systemd.asan.log.1"
1b8fcd9c 1738 ret=$((ret+1))
7e11a95e 1739 fi
998445fd 1740
88c98cb2
FS
1741 for log in pid1 journald; do
1742 report="$(find "$root" -name "systemd-$log.*san.log*" -exec cat {} \;)"
1743 if [[ -n "$report" ]]; then
1744 printf "%s\n" "$report"
1745 # shellcheck disable=SC2015
1746 [[ "$log" == journald ]] && cat "$root/systemd-journald.out" || :
1747 ret=$((ret+1))
1748 fi
1749 done
ed4f303f 1750
71116990 1751 # May 08 13:23:31 H testleak[2907148]: SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).
1b8fcd9c 1752 pids="$(
71116990
ZJS
1753 "$JOURNALCTL" -D "$root/var/log/journal" --grep 'SUMMARY: .*Sanitizer:' |
1754 grep -v -E 'dbus-daemon|dbus-broker-launch' |
1755 sed -r -n 's/.* .+\[([0-9]+)\]: SUMMARY:.*/\1/p'
1b8fcd9c 1756 )"
71116990 1757
1b8fcd9c
FS
1758 if [[ -n "$pids" ]]; then
1759 ret=$((ret+1))
ed4f303f 1760 for pid in $pids; do
1b8fcd9c 1761 "$JOURNALCTL" -D "$root/var/log/journal" _PID="$pid" --no-pager
ed4f303f 1762 done
d56db495 1763 fi
7e11a95e
EV
1764 fi
1765
889a9042
RC
1766 return $ret
1767}
1768
c82dc15b
LB
1769check_coverage_reports() {
1770 local root="${1:?}"
1771
1772 if get_bool "$NO_BUILD"; then
1773 return 0
1774 fi
02d7e730 1775 if ! get_bool "$IS_BUILT_WITH_COVERAGE"; then
c82dc15b
LB
1776 return 0
1777 fi
1778
1779 if [ -n "${ARTIFACT_DIRECTORY}" ]; then
1780 dest="${ARTIFACT_DIRECTORY}/${testname:?}.coverage-info"
1781 else
1782 dest="${TESTDIR:?}/coverage-info"
1783 fi
1784
7fdd6e15
FS
1785 if [[ ! -e "${TESTDIR:?}/coverage-base" ]]; then
1786 # This shouldn't happen, as the report is generated during the setup
1787 # phase (test_setup()).
1788 derror "Missing base coverage report"
1789 return 1
1790 fi
1791
daeb95a1
FS
1792 # Create a coverage report that will later be uploaded. Remove info about system
1793 # libraries/headers and generated files, as we don't really care about them.
7fdd6e15 1794 lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
35382a9d
FS
1795 if [[ -f "$dest" ]]; then
1796 # If the destination report file already exists, don't overwrite it, but
7fdd6e15
FS
1797 # merge it with the already present one - this usually happens when
1798 # running both "parts" of a test in one run (the qemu and the nspawn part).
35382a9d 1799 lcov --add-tracefile "${dest}" --add-tracefile "${dest}.new" -o "${dest}"
35382a9d 1800 else
7fdd6e15
FS
1801 # If there's no prior coverage report, merge the new one with the base
1802 # report we did during the setup phase (see test_setup()).
1803 lcov --add-tracefile "${TESTDIR:?}/coverage-base" --add-tracefile "${dest}.new" -o "${dest}"
35382a9d 1804 fi
daeb95a1 1805 lcov --remove "$dest" -o "$dest" '/usr/include/*' '/usr/lib/*' "${BUILD_DIR:?}/*"
7fdd6e15 1806 rm -f "${dest}.new"
c82dc15b 1807
d282e57e
FS
1808 # If the test logs contain lines like:
1809 #
1810 # ...systemd-resolved[735885]: profiling:/systemd-meson-build/src/shared/libsystemd-shared-250.a.p/base-filesystem.c.gcda:Cannot open
1811 #
1812 # it means we're possibly missing some coverage since gcov can't write the stats,
1813 # usually due to the sandbox being too restrictive (e.g. ProtectSystem=yes,
1814 # ProtectHome=yes) or the $BUILD_DIR being inaccessible to non-root users - see
1815 # `setfacl` stuff in install_compiled_systemd().
7fdd6e15
FS
1816 #
1817 # Also, a note: some tests, like TEST-46, overmount /home with tmpfs, which
1818 # means if your build dir is under /home/your-user (which is usually the
94d82b59 1819 # case) you might get bogus errors and missing coverage.
1b2e3b8b
FS
1820 if ! get_bool "${IGNORE_MISSING_COVERAGE:=}" && \
1821 "${JOURNALCTL:?}" -q --no-pager -D "${root:?}/var/log/journal" --grep "profiling:.+?gcda:[Cc]annot open"; then
d282e57e
FS
1822 derror "Detected possibly missing coverage, check the journal"
1823 return 1
1824 fi
1825
c82dc15b
LB
1826 return 0
1827}
1828
8943daf8 1829save_journal() {
09bdb9f1
FS
1830 local source_dir="${1:?}"
1831 local state="${2:?}"
d3b8e384
DS
1832 # Default to always saving journal
1833 local save="yes"
09bdb9f1 1834 local dest_dir dest_name dest
d3b8e384 1835
09bdb9f1 1836 if [[ "${TEST_SAVE_JOURNAL:-}" == "no" ]]; then
d3b8e384 1837 save="no"
09bdb9f1 1838 elif [[ "${TEST_SAVE_JOURNAL:-}" == "fail" && "$state" -eq 0 ]]; then
d3b8e384
DS
1839 save="no"
1840 fi
1841
09bdb9f1
FS
1842 if [[ -n "${ARTIFACT_DIRECTORY:-}" ]]; then
1843 dest_dir="$ARTIFACT_DIRECTORY"
1844 dest_name="${testname:?}.journal"
f9eb2d51 1845 else
09bdb9f1
FS
1846 dest_dir="${TESTDIR:?}"
1847 dest_name="system.journal"
f9eb2d51 1848 fi
8943daf8 1849
fa6f37c0
FS
1850 # Show messages from the testsuite-XX.service or messages with priority "warning" and higher
1851 echo " --- $source_dir ---"
bce0fa7d 1852 "$JOURNALCTL" --all --no-pager --no-hostname -o short-monotonic -D "$source_dir" \
77baca26
FS
1853 _SYSTEMD_UNIT="testsuite-${TESTID:?}.service" + SYSLOG_IDENTIFIER="testsuite-$TESTID.sh" + \
1854 PRIORITY=4 + PRIORITY=3 + PRIORITY=2 + PRIORITY=1 + PRIORITY=0
f1416431 1855
09bdb9f1
FS
1856 if get_bool "$save"; then
1857 # If we don't have systemd-journal-remote copy all journals from /var/log/journal/
1858 # to $dest_dir/journals/ as is, otherwise merge all journals into a single .journal
1859 # file
1860 if [[ -z "${SYSTEMD_JOURNAL_REMOTE:-}" ]]; then
1861 dest="$dest_dir/journals"
1862 mkdir -p "$dest"
1863 cp -a "$source_dir/*" "$dest/"
1864 else
1865 dest="$dest_dir/$dest_name"
1866 "$SYSTEMD_JOURNAL_REMOTE" -o "$dest" --getter="$JOURNALCTL -o export -D $source_dir"
f1416431
ZJS
1867 fi
1868
09bdb9f1
FS
1869 if [[ -n "${SUDO_USER:-}" ]]; then
1870 setfacl -R -m "user:$SUDO_USER:r-X" "$dest"
1871 fi
d3b8e384 1872
09bdb9f1
FS
1873 # we want to print this sometime later, so save this in a variable
1874 JOURNAL_LIST="$(ls -lR "$dest")"
a83a7d1e
YW
1875 fi
1876
09bdb9f1 1877 rm -rf "${source_dir:?}"/*
8943daf8
ZJS
1878}
1879
7bf20e48 1880check_result_common() {
1b8fcd9c 1881 local workspace="${1:?}"
7bf20e48
ZJS
1882 local ret
1883
1884 if [ -s "$workspace/failed" ]; then
c0d44092 1885 # Non-empty …/failed has highest priority
7bf20e48 1886 cp -a "$workspace/failed" "${TESTDIR:?}/"
a83a7d1e
YW
1887 if [ -n "${SUDO_USER}" ]; then
1888 setfacl -m "user:${SUDO_USER:?}:r-X" "${TESTDIR:?}/"failed
1889 fi
7bf20e48 1890 ret=1
faca95e1
FS
1891 elif get_bool "$TIMED_OUT"; then
1892 echo "(timeout)" >"${TESTDIR:?}/failed"
1893 ret=2
7bf20e48
ZJS
1894 elif [ -e "$workspace/testok" ]; then
1895 # …/testok always counts (but with lower priority than …/failed)
1896 ret=0
1897 elif [ -e "$workspace/skipped" ]; then
1898 # …/skipped always counts (a message is expected)
1899 echo "${TESTNAME:?} was skipped:"
1900 cat "$workspace/skipped"
1901 ret=0
7bf20e48 1902 else
c0d44092
ZJS
1903 echo "(failed; see logs)" >"${TESTDIR:?}/failed"
1904 ret=3
7bf20e48
ZJS
1905 fi
1906
c0d44092 1907 check_asan_reports "$workspace" || ret=4
7bf20e48 1908
c82dc15b
LB
1909 check_coverage_reports "$workspace" || ret=5
1910
d3b8e384
DS
1911 save_journal "$workspace/var/log/journal" $ret
1912
1b8fcd9c
FS
1913 if [ -d "${ARTIFACT_DIRECTORY}" ] && [ -f "$workspace/strace.out" ]; then
1914 cp "$workspace/strace.out" "${ARTIFACT_DIRECTORY}/"
1915 fi
7bf20e48 1916
c0d44092
ZJS
1917 if [ ${ret:?} != 0 ] && [ -f "$TESTDIR/failed" ]; then
1918 echo -n "${TESTNAME:?}: "
1919 cat "$TESTDIR/failed"
1920 fi
7bf20e48
ZJS
1921 echo "${JOURNAL_LIST:-"No journals were saved"}"
1922
c0d44092 1923 return ${ret:?}
7bf20e48
ZJS
1924}
1925
1926check_result_nspawn() {
1927 local workspace="${1:?}"
cb16b72e 1928 local ret=0
7bf20e48 1929
35d2d2e6 1930 # Run a test-specific checks if defined by check_result_nspawn_hook()
6695c41c 1931 if declare -F check_result_nspawn_hook >/dev/null; then
4b9a0c3a 1932 if ! check_result_nspawn_hook "${workspace}"; then
6695c41c
FS
1933 derror "check_result_nspawn_hook() returned with EC > 0"
1934 ret=4
1935 fi
1936 fi
1937
cb16b72e 1938 check_result_common "${workspace}" || ret=$?
35d2d2e6 1939
1b8fcd9c 1940 _umount_dir "${initdir:?}"
7bf20e48 1941
0d6e61d6
EV
1942 return $ret
1943}
1944
054ee249
MP
1945# can be overridden in specific test
1946check_result_qemu() {
cb16b72e 1947 local ret=0
8c3534b5 1948 mount_initdir
7bf20e48 1949
35d2d2e6 1950 # Run a test-specific checks if defined by check_result_qemu_hook()
6695c41c 1951 if declare -F check_result_qemu_hook >/dev/null; then
4b9a0c3a 1952 if ! check_result_qemu_hook "${initdir:?}"; then
6695c41c
FS
1953 derror "check_result_qemu_hook() returned with EC > 0"
1954 ret=4
1955 fi
1956 fi
1957
cb16b72e 1958 check_result_common "${initdir:?}" || ret=$?
35d2d2e6 1959
4b9a0c3a
FS
1960 _umount_dir "${initdir:?}"
1961
054ee249
MP
1962 return $ret
1963}
1964
fa1fdd30
LB
1965check_result_nspawn_unittests() {
1966 local workspace="${1:?}"
1967 local ret=1
1968
1969 [[ -e "$workspace/testok" ]] && ret=0
1970
1971 if [[ -s "$workspace/failed" ]]; then
1972 ret=$((ret + 1))
1973 echo "=== Failed test log ==="
1974 cat "$workspace/failed"
1975 else
1976 if [[ -s "$workspace/skipped" ]]; then
1977 echo "=== Skipped test log =="
1978 cat "$workspace/skipped"
1979 # We might have only skipped tests - that should not fail the job
1980 ret=0
1981 fi
1982 if [[ -s "$workspace/testok" ]]; then
1983 echo "=== Passed tests ==="
1984 cat "$workspace/testok"
1985 fi
1986 fi
1987
23f8e019 1988 get_bool "${TIMED_OUT:=}" && ret=1
0b5fe54f 1989 check_coverage_reports "$workspace" || ret=5
d3b8e384
DS
1990
1991 save_journal "$workspace/var/log/journal" $ret
fa316d55 1992 echo "${JOURNAL_LIST:-"No journals were saved"}"
d3b8e384 1993
fa1fdd30
LB
1994 _umount_dir "${initdir:?}"
1995
fa1fdd30
LB
1996 return $ret
1997}
1998
1999check_result_qemu_unittests() {
2000 local ret=1
2001
2002 mount_initdir
2003 [[ -e "${initdir:?}/testok" ]] && ret=0
2004
2005 if [[ -s "$initdir/failed" ]]; then
2006 ret=$((ret + 1))
2007 echo "=== Failed test log ==="
2008 cat "$initdir/failed"
2009 else
2010 if [[ -s "$initdir/skipped" ]]; then
2011 echo "=== Skipped test log =="
2012 cat "$initdir/skipped"
2013 # We might have only skipped tests - that should not fail the job
2014 ret=0
2015 fi
2016 if [[ -s "$initdir/testok" ]]; then
2017 echo "=== Passed tests ==="
2018 cat "$initdir/testok"
2019 fi
2020 fi
2021
23f8e019 2022 get_bool "${TIMED_OUT:=}" && ret=1
0b5fe54f 2023 check_coverage_reports "$initdir" || ret=5
d3b8e384
DS
2024
2025 save_journal "$initdir/var/log/journal" $ret
fa316d55 2026 echo "${JOURNAL_LIST:-"No journals were saved"}"
d3b8e384 2027
fa1fdd30
LB
2028 _umount_dir "$initdir"
2029
fa1fdd30
LB
2030 return $ret
2031}
2032
889a9042 2033create_rc_local() {
1b8fcd9c
FS
2034 dinfo "Create rc.local"
2035 mkdir -p "${initdir:?}/etc/rc.d"
2036 cat >"$initdir/etc/rc.d/rc.local" <<EOF
ff12a795 2037#!/usr/bin/env bash
889a9042
RC
2038exit 0
2039EOF
1b8fcd9c 2040 chmod 0755 "$initdir/etc/rc.d/rc.local"
889a9042
RC
2041}
2042
2043install_execs() {
1b8fcd9c
FS
2044 ddebug "Install executables from the service files"
2045
2046 local pkg_config_path="${BUILD_DIR:?}/src/core/"
2047 local systemunitdir userunitdir exe
2048 systemunitdir="$(PKG_CONFIG_PATH="$pkg_config_path" pkg-config --variable=systemdsystemunitdir systemd)"
2049 userunitdir="$(PKG_CONFIG_PATH="$pkg_config_path" pkg-config --variable=systemduserunitdir systemd)"
2050 while read -r exe; do
2051 # some {rc,halt}.local scripts and programs are okay to not exist, the rest should
2052 # also, plymouth is pulled in by rescue.service, but even there the exit code
2053 # is ignored; as it's not present on some distros, don't fail if it doesn't exist
2054 dinfo "Attempting to install $exe (based on unit file reference)"
2055 inst "$exe" || [ "${exe%.local}" != "$exe" ] || [ "${exe%systemd-update-done}" != "$exe" ] || [ "${exe##*/}" == "plymouth" ]
2056 done < <(sed -r -n 's|^Exec[a-zA-Z]*=[@+!-]*([^ ]+).*|\1|gp' "${initdir:?}"/{"$systemunitdir","$userunitdir"}/*.service | sort -u)
889a9042
RC
2057}
2058
2059generate_module_dependencies() {
1b8fcd9c
FS
2060 dinfo "Generate modules dependencies"
2061 if [[ -d "${initdir:?}/lib/modules/${KERNEL_VER:?}" ]] && \
2062 ! depmod -a -b "$initdir" "$KERNEL_VER"; then
889a9042
RC
2063 dfatal "\"depmod -a $KERNEL_VER\" failed."
2064 exit 1
2065 fi
2066}
2067
2068install_depmod_files() {
1b8fcd9c
FS
2069 dinfo "Install depmod files"
2070 inst "/lib/modules/${KERNEL_VER:?}/modules.order"
2071 inst "/lib/modules/$KERNEL_VER/modules.builtin"
889a9042
RC
2072}
2073
2074install_plymouth() {
1b8fcd9c 2075 dinfo "Install plymouth"
889a9042
RC
2076 # install plymouth, if found... else remove plymouth service files
2077 # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
2078 # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
2079 # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
39e17536 2080 # image_install plymouth plymouthd
889a9042 2081 # else
1b8fcd9c 2082 rm -f "${initdir:?}"/{usr/lib,lib,etc}/systemd/system/plymouth* "$initdir"/{usr/lib,lib,etc}/systemd/system/*/plymouth*
889a9042
RC
2083 # fi
2084}
2085
d93857ae 2086install_haveged() {
ce380c2f 2087 # If haveged is installed, it's probably included in initrd and needs to be
d93857ae
FB
2088 # installed in the image too.
2089 if [ -x /usr/sbin/haveged ]; then
2090 dinfo "Install haveged files"
2091 inst /usr/sbin/haveged
ce380c2f 2092 for u in /usr/lib/systemd/system/haveged*; do
1c3f490f 2093 inst "$u"
ce380c2f 2094 done
d93857ae
FB
2095 fi
2096}
2097
889a9042 2098install_ld_so_conf() {
1b8fcd9c
FS
2099 dinfo "Install /etc/ld.so.conf*"
2100 cp -a /etc/ld.so.conf* "${initdir:?}/etc"
889a9042
RC
2101 ldconfig -r "$initdir"
2102}
2103
d0ac89a1 2104install_testuser() {
1b8fcd9c 2105 dinfo "Set up a test user"
d0ac89a1 2106 # create unprivileged user for user manager tests
1b8fcd9c
FS
2107 mkdir -p "${initdir:?}/etc/sysusers.d"
2108 cat >"$initdir/etc/sysusers.d/testuser.conf" <<EOF
8cc42169 2109u testuser 4711 "Test User" /home/testuser /bin/bash
d0ac89a1
ZJS
2110EOF
2111
1b8fcd9c
FS
2112 mkdir -p "$initdir/home/testuser"
2113 chmod 0700 "$initdir/home/testuser"
2114 chown 4711:4711 "$initdir/home/testuser"
d0ac89a1
ZJS
2115}
2116
889a9042 2117install_config_files() {
1b8fcd9c 2118 dinfo "Install config files"
65dd488f 2119 inst /etc/sysconfig/init || :
889a9042
RC
2120 inst /etc/passwd
2121 inst /etc/shadow
2aa5a13a 2122 inst_any /etc/login.defs /usr/etc/login.defs
889a9042
RC
2123 inst /etc/group
2124 inst /etc/shells
9e173292 2125 inst_any /etc/nsswitch.conf /usr/etc/nsswitch.conf
65dd488f 2126 inst /etc/pam.conf || :
ae6c5987 2127 inst_any /etc/os-release /usr/lib/os-release
889a9042
RC
2128 inst /etc/localtime
2129 # we want an empty environment
1b8fcd9c
FS
2130 : >"${initdir:?}/etc/environment"
2131 : >"$initdir/etc/machine-id"
2132 : >"$initdir/etc/resolv.conf"
7eeeab20 2133
889a9042 2134 # set the hostname
ea0d33e2 2135 echo 'H' >"$initdir/etc/hostname"
a455e75a
ZJS
2136
2137 # let's set up just one image with the traditional verbose output
1b8fcd9c
FS
2138 if [ "${IMAGE_NAME:?}" != "basic" ]; then
2139 mkdir -p "$initdir/etc/systemd/system.conf.d"
2140 echo -e '[Manager]\nStatusUnitFormat=name' >"$initdir/etc/systemd/system.conf.d/status.conf"
a455e75a 2141 fi
889a9042
RC
2142}
2143
2144install_basic_tools() {
1b8fcd9c 2145 dinfo "Install basic tools"
39e17536
FS
2146 image_install "${BASICTOOLS[@]}"
2147 image_install -o sushell
7d023341 2148 # in Debian ldconfig is just a shell script wrapper around ldconfig.real
39e17536 2149 image_install -o ldconfig.real
889a9042
RC
2150}
2151
2152install_debug_tools() {
1b8fcd9c 2153 dinfo "Install debug tools"
f7d47cc8 2154 image_install -o "${DEBUGTOOLS[@]}"
c81a46b9 2155
23f8e019 2156 if get_bool "$INTERACTIVE_DEBUG"; then
c81a46b9 2157 # Set default TERM from vt220 to linux, so at least basic key shortcuts work
1b8fcd9c
FS
2158 local getty_override="${initdir:?}/etc/systemd/system/serial-getty@.service.d"
2159 mkdir -p "$getty_override"
2160 echo -e "[Service]\nEnvironment=TERM=linux" >"$getty_override/default-TERM.conf"
17082e8a 2161 echo 'export TERM=linux' >>"$initdir/etc/profile"
c81a46b9 2162
17082e8a
FS
2163 if command -v resize >/dev/null; then
2164 image_install resize
2165 echo "resize" >>"$initdir/etc/profile"
2166 fi
5c08efee
FS
2167
2168 # Sometimes we might end up with plymouthd still running (especially
2169 # with the initrd -> asan_wrapper -> systemd transition), which will eat
2170 # our inputs and make debugging via tty impossible. Let's fix this by
2171 # killing plymouthd explicitly for the interactive sessions.
2172 # Note: we can't use pkill/pidof/etc. here due to a bug in libasan, see:
2173 # - https://github.com/llvm/llvm-project/issues/49223
2174 # - https://bugzilla.redhat.com/show_bug.cgi?id=2098125
2175 local plymouth_unit="${initdir:?}/etc/systemd/system/kill-plymouth.service"
2176 cat >"$plymouth_unit" <<EOF
2177[Unit]
2178After=multi-user.target
2179
2180[Service]
2181ExecStart=sh -c 'killall --verbose plymouthd || :'
2182
2183[Install]
2184WantedBy=multi-user.target
2185EOF
2186 "${SYSTEMCTL:?}" enable --root "${initdir:?}" kill-plymouth.service
c81a46b9 2187 fi
889a9042
RC
2188}
2189
2190install_libnss() {
1b8fcd9c 2191 dinfo "Install libnss"
889a9042 2192 # install libnss_files for login
0f194705
FS
2193 local NSS_LIBS
2194 mapfile -t NSS_LIBS < <(LD_DEBUG=files getent passwd 2>&1 >/dev/null | sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}')
90782fde
FS
2195 if [[ ${#NSS_LIBS[@]} -gt 0 ]]; then
2196 image_install "${NSS_LIBS[@]}"
2197 fi
889a9042
RC
2198}
2199
2200install_dbus() {
1b8fcd9c
FS
2201 dinfo "Install dbus"
2202 inst "${ROOTLIBDIR:?}/system/dbus.socket"
a978c9f2 2203
a49ad4c4 2204 # Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
1b8fcd9c
FS
2205 if [ -f "$ROOTLIBDIR/system/dbus-broker.service" ]; then
2206 inst "$ROOTLIBDIR/system/dbus-broker.service"
908665f4
LP
2207 inst /usr/bin/dbus-broker
2208 inst /usr/bin/dbus-broker-launch
ec6c7bac 2209 image_install -o {/etc,/usr/lib}/systemd/system/dbus.service
1b8fcd9c 2210 elif [ -f "$ROOTLIBDIR/system/dbus-daemon.service" ]; then
908665f4 2211 # Fedora rawhide replaced dbus.service with dbus-daemon.service
1b8fcd9c 2212 inst "$ROOTLIBDIR/system/dbus-daemon.service"
a978c9f2 2213 # Alias symlink
ec6c7bac 2214 image_install -o {/etc,/usr/lib}/systemd/system/dbus.service
a978c9f2 2215 else
1b8fcd9c 2216 inst "$ROOTLIBDIR/system/dbus.service"
a978c9f2 2217 fi
889a9042 2218
96af59aa
FS
2219 while read -r file; do
2220 inst "$file"
2221 done < <(find /etc/dbus-1 /usr/share/dbus-1 -xtype f 2>/dev/null)
bdfd515a
ZJS
2222
2223 # setup policy for Type=dbus test
1b8fcd9c
FS
2224 mkdir -p "${initdir:?}/etc/dbus-1/system.d"
2225 cat >"$initdir/etc/dbus-1/system.d/systemd.test.ExecStopPost.conf" <<EOF
bdfd515a
ZJS
2226<?xml version="1.0"?>
2227<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
41d6f3bf 2228 "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
bdfd515a
ZJS
2229<busconfig>
2230 <policy user="root">
2231 <allow own="systemd.test.ExecStopPost"/>
2232 </policy>
2233</busconfig>
2234EOF
c78d1821
FS
2235
2236 # If we run without KVM, bump the service start timeout
2237 if ! get_bool "$QEMU_KVM"; then
2238 cat >"$initdir/etc/dbus-1/system.d/service.timeout.conf" <<EOF
2239<?xml version="1.0"?>
2240<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
2241 "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
2242<busconfig>
72f6d0e5 2243 <limit name="service_start_timeout">120000</limit>
c78d1821
FS
2244</busconfig>
2245EOF
e0cbb739
FS
2246 # Bump the client-side timeout in sd-bus as well
2247 mkdir -p "$initdir/etc/systemd/system.conf.d"
72f6d0e5 2248 echo -e '[Manager]\nDefaultEnvironment=SYSTEMD_BUS_TIMEOUT=120' >"$initdir/etc/systemd/system.conf.d/bus-timeout.conf"
c78d1821 2249 fi
889a9042
RC
2250}
2251
a49ad4c4 2252install_user_dbus() {
1b8fcd9c 2253 dinfo "Install user dbus"
53a1c944 2254 local userunitdir
1b8fcd9c
FS
2255 if ! userunitdir="$(pkg-config --variable=systemduserunitdir systemd)"; then
2256 dwarn "WARNING! Cannot determine userunitdir from pkg-config, assuming /usr/lib/systemd/user"
2257 userunitdir=/usr/lib/systemd/user
53a1c944
LB
2258 fi
2259
1b8fcd9c
FS
2260 inst "$userunitdir/dbus.socket"
2261 inst_symlink "$userunitdir/sockets.target.wants/dbus.socket" || inst_symlink /etc/systemd/user/sockets.target.wants/dbus.socket
a49ad4c4
FB
2262
2263 # Append the After= dependency on dbus in case it isn't already set up
1b8fcd9c
FS
2264 mkdir -p "${initdir:?}/etc/systemd/system/user@.service.d/"
2265 cat >"$initdir/etc/systemd/system/user@.service.d/dbus.conf" <<EOF
a49ad4c4
FB
2266[Unit]
2267After=dbus.service
2268EOF
2269
2270 # Newer Fedora versions use dbus-broker by default. Let's install it if it's available.
1b8fcd9c
FS
2271 if [ -f "$userunitdir/dbus-broker.service" ]; then
2272 inst "$userunitdir/dbus-broker.service"
ec6c7bac 2273 image_install -o {/etc,/usr/lib}/systemd/user/dbus.service
1b8fcd9c 2274 elif [ -f "${ROOTLIBDIR:?}/system/dbus-daemon.service" ]; then
a49ad4c4 2275 # Fedora rawhide replaced dbus.service with dbus-daemon.service
1b8fcd9c 2276 inst "$userunitdir/dbus-daemon.service"
a49ad4c4 2277 # Alias symlink
ec6c7bac 2278 image_install -o {/etc,/usr/lib}/systemd/user/dbus.service
a49ad4c4 2279 else
1b8fcd9c 2280 inst "$userunitdir/dbus.service"
a49ad4c4
FB
2281 fi
2282}
2283
889a9042 2284install_pam() {
96af59aa
FS
2285 dinfo "Install PAM"
2286 local paths=()
2287
23f8e019 2288 if get_bool "$LOOKS_LIKE_DEBIAN" && type -p dpkg-architecture &>/dev/null; then
96af59aa 2289 paths+=("/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security")
818567fc 2290 else
96af59aa 2291 paths+=(/lib*/security)
818567fc 2292 fi
96af59aa 2293
eef72224 2294 for d in /etc/pam.d /{usr/,}etc/security /usr/{etc,lib}/pam.d; do
96af59aa 2295 [ -d "$d" ] && paths+=("$d")
889a9042 2296 done
417491f1 2297
96af59aa
FS
2298 while read -r file; do
2299 inst "$file"
2300 done < <(find "${paths[@]}" -xtype f)
2301
d5172c79
EV
2302 # pam_unix depends on unix_chkpwd.
2303 # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html
39e17536 2304 image_install -o unix_chkpwd
d5172c79 2305
e14b866b 2306 # set empty root password for easy debugging
1b8fcd9c 2307 sed -i 's/^root:x:/root::/' "${initdir:?}/etc/passwd"
138f7619
FB
2308
2309 # And make sure pam_unix will accept it by making sure that
2310 # the PAM module has the nullok option.
2311 for d in /etc/pam.d /usr/{etc,lib}/pam.d; do
2312 [ -d "$initdir/$d" ] || continue
2313 sed -i '/^auth.*pam_unix.so/s/$/ nullok/' "$initdir/$d"/*
2314 done
889a9042
RC
2315}
2316
4ce68ea9
YW
2317install_locales() {
2318 # install only C.UTF-8 and English locales
2319 dinfo "Install locales"
2320
2321 if command -v meson >/dev/null \
2322 && (meson configure "${BUILD_DIR:?}" | grep 'localegen-path */') \
2323 || get_bool "$LOOKS_LIKE_DEBIAN"; then
2324 # locale-gen support
2325 image_install -o locale-gen localedef
2326 inst /etc/locale.gen || :
2327 inst /usr/share/i18n/SUPPORTED || :
2328 inst_recursive /usr/share/i18n/charmaps
2329 inst_recursive /usr/share/i18n/locales
0c416ea0
FS
2330 inst_recursive /usr/share/locale/en*
2331 inst_recursive /usr/share/locale/de*
2332 image_install /usr/share/locale/locale.alias
2333 # locale-gen might either generate each locale separately or merge them
2334 # into a single archive
2335 if ! (inst_recursive /usr/lib/locale/C.*8 /usr/lib/locale/en_*8 ||
2336 image_install /usr/lib/locale/locale-archive); then
2337 dfatal "Failed to install required locales"
2338 exit 1
2339 fi
2340 else
2341 inst_recursive /usr/lib/locale/C.*8 /usr/lib/locale/en_*8
4ce68ea9 2342 fi
4ce68ea9
YW
2343}
2344
1c3f490f 2345# shellcheck disable=SC2120
889a9042 2346install_keymaps() {
569c6fd1
YW
2347 local i p
2348 local -a prefix=(
1edad893
FS
2349 "/usr/lib"
2350 "/usr/share"
569c6fd1 2351 )
ad931fee 2352
569c6fd1
YW
2353 dinfo "Install console keymaps"
2354
569c6fd1
YW
2355 if (( $# == 0 )); then
2356 for p in "${prefix[@]}"; do
2357 # The first three paths may be deprecated.
2358 # It seems now the last three paths are used by many distributions.
2359 for i in \
1edad893
FS
2360 "$p"/kbd/keymaps/include/* \
2361 "$p"/kbd/keymaps/i386/include/* \
2362 "$p"/kbd/keymaps/i386/qwerty/us.* \
2363 "$p"/kbd/keymaps/legacy/include/* \
2364 "$p"/kbd/keymaps/legacy/i386/qwerty/us.* \
2365 "$p"/kbd/keymaps/xkb/us*; do
569c6fd1
YW
2366 [[ -f "$i" ]] || continue
2367 inst "$i"
2368 done
2369 done
2370 else
2371 # When it takes any argument, then install more keymaps.
2372 for p in "${prefix[@]}"; do
2373 for i in \
1edad893
FS
2374 "$p"/kbd/keymaps/include/* \
2375 "$p"/kbd/keymaps/i386/*/* \
2376 "$p"/kbd/keymaps/legacy/i386/*/* \
2377 "$p"/kbd/keymaps/xkb/*; do
569c6fd1
YW
2378 [[ -f "$i" ]] || continue
2379 inst "$i"
2380 done
ad931fee
YW
2381 done
2382 fi
889a9042
RC
2383}
2384
1136175c
YW
2385install_x11_keymaps() {
2386 dinfo "Install x11 keymaps"
2387
2388 if (( $# == 0 )); then
2389 # Install only keymap list.
2390 inst /usr/share/X11/xkb/rules/base.lst
2391 else
2392 # When it takes any argument, then install all keymaps.
2393 inst_recursive /usr/share/X11/xkb
2394 fi
2395}
2396
7d10ec1c 2397install_zoneinfo() {
1b8fcd9c 2398 dinfo "Install time zones"
f4c40fd7
ZJS
2399 inst_any /usr/share/zoneinfo/Asia/Seoul
2400 inst_any /usr/share/zoneinfo/Asia/Vladivostok
2401 inst_any /usr/share/zoneinfo/Australia/Sydney
2402 inst_any /usr/share/zoneinfo/Europe/Berlin
129cb6e2 2403 inst_any /usr/share/zoneinfo/Europe/Dublin
f4c40fd7
ZJS
2404 inst_any /usr/share/zoneinfo/Europe/Kiev
2405 inst_any /usr/share/zoneinfo/Pacific/Auckland
2406 inst_any /usr/share/zoneinfo/Pacific/Honolulu
2407 inst_any /usr/share/zoneinfo/CET
2408 inst_any /usr/share/zoneinfo/EET
2409 inst_any /usr/share/zoneinfo/UTC
7d10ec1c
YW
2410}
2411
889a9042 2412install_fonts() {
1b8fcd9c 2413 dinfo "Install system fonts"
889a9042 2414 for i in \
ba0ff9fc
FB
2415 /usr/{lib,share}/kbd/consolefonts/eurlatgr* \
2416 /usr/{lib,share}/kbd/consolefonts/latarcyrheb-sun16*; do
1b8fcd9c
FS
2417 [[ -f "$i" ]] || continue
2418 inst "$i"
889a9042
RC
2419 done
2420}
2421
2422install_terminfo() {
1b8fcd9c
FS
2423 dinfo "Install terminfo files"
2424 local terminfodir
2425 for terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
2426 [ -f "${terminfodir}/l/linux" ] && break
889a9042 2427 done
39e17536 2428 image_install -o "${terminfodir}/l/linux"
889a9042
RC
2429}
2430
a49ad4c4
FB
2431has_user_dbus_socket() {
2432 if [ -f /usr/lib/systemd/user/dbus.socket ] || [ -f /etc/systemd/user/dbus.socket ]; then
2433 return 0
2434 else
2435 echo "Per-user instances are not supported. Skipping..."
2436 return 1
2437 fi
2438}
2439
48f3bc5c
LN
2440setup_nspawn_root_hook() { :;}
2441
889a9042 2442setup_nspawn_root() {
8c3534b5
ZJS
2443 if [ -z "${initdir}" ]; then
2444 dfatal "\$initdir not defined"
2445 exit 1
2446 fi
ec43f686 2447
1b8fcd9c 2448 rm -rf "${TESTDIR:?}/unprivileged-nspawn-root"
693ad298 2449
23f8e019 2450 if get_bool "$RUN_IN_UNPRIVILEGED_CONTAINER"; then
ec43f686 2451 ddebug "cp -ar $initdir $TESTDIR/unprivileged-nspawn-root"
1b8fcd9c 2452 cp -ar "$initdir" "$TESTDIR/unprivileged-nspawn-root"
746fbd9c 2453 fi
48f3bc5c
LN
2454
2455 setup_nspawn_root_hook
889a9042
RC
2456}
2457
0d6e798a 2458setup_basic_dirs() {
1b8fcd9c
FS
2459 mkdir -p "${initdir:?}/run"
2460 mkdir -p "$initdir/etc/systemd/system"
2461 mkdir -p "$initdir/var/log/journal"
2462
889a9042 2463
1b8fcd9c 2464 for d in usr/bin usr/sbin bin etc lib "${libdir:?}" sbin tmp usr var var/log var/tmp dev proc sys sysroot root run run/lock run/initramfs; do
898720b7
HH
2465 if [ -L "/$d" ]; then
2466 inst_symlink "/$d"
2467 else
0d6e798a 2468 inst_dir "/$d"
898720b7
HH
2469 fi
2470 done
2471
2472 ln -sfn /run "$initdir/var/run"
2473 ln -sfn /run/lock "$initdir/var/lock"
2474}
2475
51fa8591
ZJS
2476mask_supporting_services() {
2477 # mask some services that we do not want to run in these tests
7776b225
FS
2478 ln -fsv /dev/null "${initdir:?}/etc/systemd/system/systemd-hwdb-update.service"
2479 ln -fsv /dev/null "$initdir/etc/systemd/system/systemd-journal-catalog-update.service"
2480 ln -fsv /dev/null "$initdir/etc/systemd/system/systemd-networkd.service"
2481 ln -fsv /dev/null "$initdir/etc/systemd/system/systemd-networkd.socket"
2482 ln -fsv /dev/null "$initdir/etc/systemd/system/systemd-resolved.service"
51fa8591
ZJS
2483}
2484
898720b7 2485inst_libs() {
96af59aa
FS
2486 local bin="${1:?}"
2487 local so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
2488 local file line
898720b7 2489
96af59aa
FS
2490 while read -r line; do
2491 [[ "$line" = 'not a dynamic executable' ]] && break
ff254eea
ZJS
2492 # Ignore errors about our own stuff missing. This is most likely caused
2493 # by ldd attempting to use the unprefixed RPATH.
04bce24d 2494 [[ "$line" =~ (libsystemd|libudev).*\ not\ found ]] && continue
898720b7 2495
96af59aa
FS
2496 if [[ "$line" =~ not\ found ]]; then
2497 dfatal "Missing a shared library required by $bin."
2498 dfatal "Run \"ldd $bin\" to find out what it is."
2499 dfatal "$line"
39e17536 2500 dfatal "Cannot create a test image."
898720b7
HH
2501 exit 1
2502 fi
04bce24d
FS
2503
2504 if [[ "$line" =~ $so_regex ]]; then
2505 file="${BASH_REMATCH[1]}"
2506 [[ -e "${initdir:?}/$file" ]] && continue
2507 inst_library "$file"
2508 fi
96af59aa 2509 done < <(LC_ALL=C ldd "$bin" 2>/dev/null)
898720b7
HH
2510}
2511
2512import_testdir() {
1506edca 2513 # make sure we don't get a stale LOOPDEV value from old times
1b8fcd9c
FS
2514 local _LOOPDEV="${LOOPDEV:=}"
2515 # We don't want shellcheck to follow & check the $STATEFILE
2516 # shellcheck source=/dev/null
2517 [[ -e "$STATEFILE" ]] && . "$STATEFILE"
2518 LOOPDEV="$_LOOPDEV"
3f50fff5
FS
2519 if [[ ! -d "$TESTDIR" ]]; then
2520 if [[ -z "$TESTDIR" ]]; then
0c566150 2521 TESTDIR="$(mktemp --tmpdir=$WORKDIR -d -t systemd-test.XXXXXX)"
3f50fff5
FS
2522 else
2523 mkdir -p "$TESTDIR"
2524 fi
2525
1b8fcd9c 2526 cat >"$STATEFILE" <<EOF
8c3534b5 2527TESTDIR="$TESTDIR"
8c3534b5 2528EOF
898720b7
HH
2529 export TESTDIR
2530 fi
e8945092 2531
1b8fcd9c
FS
2532 IMAGE_PRIVATE="${TESTDIR}/${IMAGE_NAME:?}.img"
2533 IMAGE_PUBLIC="${IMAGESTATEDIR:?}/${IMAGE_NAME}.img"
898720b7
HH
2534}
2535
889a9042 2536import_initdir() {
1b8fcd9c
FS
2537 initdir="${TESTDIR:?}/root"
2538 mkdir -p "$initdir"
889a9042
RC
2539 export initdir
2540}
2541
f7237408
FS
2542get_cgroup_hierarchy() {
2543 case "$(stat -c '%T' -f /sys/fs/cgroup)" in
2544 cgroup2fs)
2545 echo "unified"
2546 ;;
2547 tmpfs)
2548 if [[ -d /sys/fs/cgroup/unified && "$(stat -c '%T' -f /sys/fs/cgroup/unified)" == cgroup2fs ]]; then
2549 echo "hybrid"
2550 else
2551 echo "legacy"
2552 fi
2553 ;;
2554 *)
2555 dfatal "Failed to determine host's cgroup hierarchy"
2556 exit 1
2557 esac
2558}
2559
898720b7
HH
2560## @brief Converts numeric logging level to the first letter of level name.
2561#
2562# @param lvl Numeric logging level in range from 1 to 6.
2563# @retval 1 if @a lvl is out of range.
2564# @retval 0 if @a lvl is correct.
2565# @result Echoes first letter of level name.
2566_lvl2char() {
2567 case "$1" in
2568 1) echo F;;
2569 2) echo E;;
2570 3) echo W;;
2571 4) echo I;;
2572 5) echo D;;
2573 6) echo T;;
2574 *) return 1;;
2575 esac
2576}
2577
2578## @brief Internal helper function for _do_dlog()
2579#
2580# @param lvl Numeric logging level.
2581# @param msg Message.
2582# @retval 0 It's always returned, even if logging failed.
2583#
2584# @note This function is not supposed to be called manually. Please use
2585# dtrace(), ddebug(), or others instead which wrap this one.
2586#
2587# This function calls _do_dlog() either with parameter msg, or if
2588# none is given, it will read standard input and will use every line as
2589# a message.
2590#
2591# This enables:
2592# dwarn "This is a warning"
2593# echo "This is a warning" | dwarn
1b8fcd9c 2594LOG_LEVEL="${LOG_LEVEL:-4}"
898720b7
HH
2595
2596dlog() {
1b8fcd9c
FS
2597 local lvl lvlc
2598
898720b7 2599 [ -z "$LOG_LEVEL" ] && return 0
1b8fcd9c
FS
2600 lvl="${1:?}"; shift
2601 [ "$lvl" -le "$LOG_LEVEL" ] || return 0
2602 lvlc="$(_lvl2char "$lvl")" || return 0
898720b7
HH
2603
2604 if [ $# -ge 1 ]; then
2605 echo "$lvlc: $*"
2606 else
1b8fcd9c 2607 while read -r line; do
898720b7
HH
2608 echo "$lvlc: " "$line"
2609 done
2610 fi
2611}
2612
2613## @brief Logs message at TRACE level (6)
2614#
2615# @param msg Message.
2616# @retval 0 It's always returned, even if logging failed.
2617dtrace() {
2618 set +x
2619 dlog 6 "$@"
23f8e019 2620 if get_bool "${debug:=}"; then
1b8fcd9c
FS
2621 set -x
2622 fi
898720b7
HH
2623}
2624
2625## @brief Logs message at DEBUG level (5)
2626#
2627# @param msg Message.
2628# @retval 0 It's always returned, even if logging failed.
2629ddebug() {
898720b7 2630 dlog 5 "$@"
898720b7
HH
2631}
2632
2633## @brief Logs message at INFO level (4)
2634#
2635# @param msg Message.
2636# @retval 0 It's always returned, even if logging failed.
2637dinfo() {
2638 set +x
2639 dlog 4 "$@"
23f8e019 2640 if get_bool "${debug:=}"; then
1b8fcd9c
FS
2641 set -x
2642 fi
898720b7
HH
2643}
2644
2645## @brief Logs message at WARN level (3)
2646#
2647# @param msg Message.
2648# @retval 0 It's always returned, even if logging failed.
2649dwarn() {
2650 set +x
2651 dlog 3 "$@"
23f8e019 2652 if get_bool "${debug:=}"; then
1b8fcd9c
FS
2653 set -x
2654 fi
898720b7
HH
2655}
2656
2657## @brief Logs message at ERROR level (2)
2658#
2659# @param msg Message.
2660# @retval 0 It's always returned, even if logging failed.
2661derror() {
898720b7 2662 dlog 2 "$@"
898720b7
HH
2663}
2664
2665## @brief Logs message at FATAL level (1)
2666#
2667# @param msg Message.
2668# @retval 0 It's always returned, even if logging failed.
2669dfatal() {
2670 set +x
2671 dlog 1 "$@"
23f8e019 2672 if get_bool "${debug:=}"; then
1b8fcd9c
FS
2673 set -x
2674 fi
898720b7
HH
2675}
2676
2677
2678# Generic substring function. If $2 is in $1, return 0.
c049acb2 2679strstr() { [ "${1#*"$2"*}" != "$1" ]; }
898720b7
HH
2680
2681# normalize_path <path>
2682# Prints the normalized path, where it removes any duplicated
2683# and trailing slashes.
2684# Example:
2685# $ normalize_path ///test/test//
2686# /test/test
2687normalize_path() {
2688 shopt -q -s extglob
2689 set -- "${1//+(\/)//}"
2690 shopt -q -u extglob
2691 echo "${1%/}"
2692}
2693
2694# convert_abs_rel <from> <to>
2695# Prints the relative path, when creating a symlink to <to> from <from>.
2696# Example:
2697# $ convert_abs_rel /usr/bin/test /bin/test-2
2698# ../../bin/test-2
2699# $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test
2700convert_abs_rel() {
2701 local __current __absolute __abssize __cursize __newpath
2702 local -i __i __level
2703
1b8fcd9c 2704 set -- "$(normalize_path "${1:?}")" "$(normalize_path "${2:?}")"
898720b7
HH
2705
2706 # corner case #1 - self looping link
2707 [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; }
2708
2709 # corner case #2 - own dir link
2710 [[ "${1%/*}" == "$2" ]] && { echo "."; return; }
2711
1b8fcd9c
FS
2712 IFS="/" read -ra __current <<< "$1"
2713 IFS="/" read -ra __absolute <<< "$2"
898720b7
HH
2714
2715 __abssize=${#__absolute[@]}
2716 __cursize=${#__current[@]}
2717
4627fb80 2718 while [[ "${__absolute[__level]}" == "${__current[__level]}" ]]; do
898720b7
HH
2719 (( __level++ ))
2720 if (( __level > __abssize || __level > __cursize ))
2721 then
2722 break
2723 fi
2724 done
2725
4627fb80 2726 for ((__i = __level; __i < __cursize-1; __i++)); do
898720b7
HH
2727 if ((__i > __level))
2728 then
2729 __newpath=$__newpath"/"
2730 fi
2731 __newpath=$__newpath".."
2732 done
2733
4627fb80 2734 for ((__i = __level; __i < __abssize; __i++)); do
898720b7
HH
2735 if [[ -n $__newpath ]]
2736 then
2737 __newpath=$__newpath"/"
2738 fi
2739 __newpath=$__newpath${__absolute[__i]}
2740 done
2741
2742 echo "$__newpath"
2743}
2744
2745
2746# Install a directory, keeping symlinks as on the original system.
2747# Example: if /lib points to /lib64 on the host, "inst_dir /lib/file"
2748# will create ${initdir}/lib64, ${initdir}/lib64/file,
2749# and a symlink ${initdir}/lib -> lib64.
2750inst_dir() {
1b8fcd9c
FS
2751 local dir="${1:?}"
2752 local part="${dir%/*}"
2753 local file
898720b7 2754
1b8fcd9c
FS
2755 [[ -e "${initdir:?}/${dir}" ]] && return 0 # already there
2756
2757 while [[ "$part" != "${part%/*}" ]] && ! [[ -e "${initdir}/${part}" ]]; do
2758 dir="$part $dir"
2759 part="${part%/*}"
898720b7
HH
2760 done
2761
2762 # iterate over parent directories
1b8fcd9c
FS
2763 for file in $dir; do
2764 [[ -e "${initdir}/$file" ]] && continue
2765 if [[ -L $file ]]; then
2766 inst_symlink "$file"
898720b7
HH
2767 else
2768 # create directory
1b8fcd9c
FS
2769 mkdir -m 0755 "${initdir}/$file" || return 1
2770 [[ -e "$file" ]] && chmod --reference="$file" "${initdir}/$file"
2771 chmod u+w "${initdir}/$file"
898720b7
HH
2772 fi
2773 done
2774}
2775
2776# $1 = file to copy to ramdisk
2777# $2 (optional) Name for the file on the ramdisk
2778# Location of the image dir is assumed to be $initdir
2779# We never overwrite the target if it exists.
2780inst_simple() {
1b8fcd9c 2781 [[ -f "${1:?}" ]] || return 1
898720b7
HH
2782 strstr "$1" "/" || return 1
2783
1b8fcd9c
FS
2784 local src="$1"
2785 local target="${2:-$1}"
2786 if ! [[ -d ${initdir:?}/$target ]]; then
898720b7
HH
2787 [[ -e ${initdir}/$target ]] && return 0
2788 [[ -L ${initdir}/$target ]] && return 0
2789 [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}"
2790 fi
2791 # install checksum files also
1b8fcd9c
FS
2792 if [[ -e "${src%/*}/.${src##*/}.hmac" ]]; then
2793 inst "${src%/*}/.${src##*/}.hmac" "${target%/*}/.${target##*/}.hmac"
898720b7 2794 fi
1b8fcd9c 2795 ddebug "Installing $src"
a6695a43 2796 cp --sparse=always --force --dereference --preserve=all "$src" "${initdir}/$target"
898720b7
HH
2797}
2798
2799# find symlinks linked to given library file
2800# $1 = library file
2801# Function searches for symlinks by stripping version numbers appended to
2802# library filename, checks if it points to the same target and finally
2803# prints the list of symlinks to stdout.
2804#
2805# Example:
2806# rev_lib_symlinks libfoo.so.8.1
2807# output: libfoo.so.8 libfoo.so
2808# (Only if libfoo.so.8 and libfoo.so exists on host system.)
2809rev_lib_symlinks() {
1b8fcd9c
FS
2810 local fn="${1:?}"
2811 local links=""
2812 local orig
2813 orig="$(readlink -f "$1")"
898720b7 2814
1b8fcd9c 2815 [[ "${fn}" =~ .*\.so\..* ]] || return 1
898720b7 2816
1b8fcd9c 2817 until [[ "${fn##*.}" == so ]]; do
898720b7 2818 fn="${fn%.*}"
1b8fcd9c 2819 [[ -L "${fn}" && "$(readlink -f "${fn}")" == "${orig}" ]] && links+=" ${fn}"
898720b7
HH
2820 done
2821
2822 echo "${links}"
2823}
2824
2825# Same as above, but specialized to handle dynamic libraries.
2826# It handles making symlinks according to how the original library
2827# is referenced.
2828inst_library() {
1b8fcd9c
FS
2829 local src="${1:?}"
2830 local dest="${2:-$1}"
2831 local reallib symlink
2832
898720b7 2833 strstr "$1" "/" || return 1
1b8fcd9c
FS
2834 [[ -e ${initdir:?}/$dest ]] && return 0
2835 if [[ -L $src ]]; then
898720b7 2836 # install checksum files also
1b8fcd9c
FS
2837 if [[ -e "${src%/*}/.${src##*/}.hmac" ]]; then
2838 inst "${src%/*}/.${src##*/}.hmac" "${dest%/*}/.${dest##*/}.hmac"
898720b7 2839 fi
1b8fcd9c
FS
2840 reallib="$(readlink -f "$src")"
2841 inst_simple "$reallib" "$reallib"
2842 inst_dir "${dest%/*}"
2843 [[ -d "${dest%/*}" ]] && dest="$(readlink -f "${dest%/*}")/${dest##*/}"
134d4f1b 2844 ddebug "Creating symlink $reallib -> $dest"
1b8fcd9c 2845 ln -sfn -- "$(convert_abs_rel "${dest}" "${reallib}")" "${initdir}/${dest}"
898720b7 2846 else
1b8fcd9c 2847 inst_simple "$src" "$dest"
898720b7
HH
2848 fi
2849
2850 # Create additional symlinks. See rev_symlinks description.
1b8fcd9c
FS
2851 for symlink in $(rev_lib_symlinks "$src") ${reallib:+$(rev_lib_symlinks "$reallib")}; do
2852 if [[ ! -e "$initdir/$symlink" ]]; then
2853 ddebug "Creating extra symlink: $symlink"
2854 inst_symlink "$symlink"
2855 fi
898720b7
HH
2856 done
2857}
2858
2859# find a binary. If we were not passed the full path directly,
2860# search in the usual places to find the binary.
2861find_binary() {
1b8fcd9c
FS
2862 local bin="${1:?}"
2863 if [[ -z ${bin##/*} ]]; then
2864 if [[ -x "$bin" ]] || { strstr "$bin" ".so" && ldd "$bin" &>/dev/null; }; then
2865 echo "$bin"
898720b7
HH
2866 return 0
2867 fi
2868 fi
2869
1b8fcd9c 2870 type -P "$bin"
898720b7
HH
2871}
2872
2873# Same as above, but specialized to install binary executables.
2874# Install binary executable, and all shared library dependencies, if any.
2875inst_binary() {
1b8fcd9c
FS
2876 local bin="${1:?}"
2877 local path target
3cdb93d0
FS
2878
2879 # In certain cases we might attempt to install a binary which is already
2880 # present in the test image, yet it's missing from the host system.
2881 # In such cases, let's check if the binary indeed exists in the image
ff254eea 2882 # before doing any other checks. If it does, immediately return with
3cdb93d0 2883 # success.
1b8fcd9c
FS
2884 if [[ $# -eq 1 ]]; then
2885 for path in "" bin sbin usr/bin usr/sbin; do
2886 [[ -e "${initdir:?}${path:+/$path}/${bin}" ]] && return 0
2887 done
2888 fi
3cdb93d0 2889
1b8fcd9c
FS
2890 bin="$(find_binary "$bin")" || return 1
2891 target="${2:-$bin}"
2892 [[ -e "${initdir:?}/$target" ]] && return 0
2893 [[ -L "$bin" ]] && inst_symlink "$bin" "$target" && return 0
2894
2895 local file line
2896 local so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
3917534d
FS
2897 # DSOs provided by systemd
2898 local systemd_so_regex='/(libudev|libsystemd.*|.+[\-_]systemd([\-_].+)?|libnss_(mymachines|myhostname|resolve)).so'
2899 local wrap_binary=0
2b5e7860 2900 local enable_lsan=0
898720b7 2901 # I love bash!
1b8fcd9c
FS
2902 while read -r line; do
2903 [[ "$line" = 'not a dynamic executable' ]] && break
898720b7 2904
ff254eea
ZJS
2905 # Ignore errors about our own stuff missing. This is most likely caused
2906 # by ldd attempting to use the unprefixed RPATH.
2907 [[ "$line" =~ libsystemd.*\ not\ found ]] && continue
2908
3917534d
FS
2909 # We're built with ASan and the target binary loads one of the systemd's
2910 # DSOs, so we need to tweak the environment before executing the binary
2911 if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$line" =~ $systemd_so_regex ]]; then
2912 wrap_binary=1
2913 fi
2914
1b8fcd9c
FS
2915 if [[ "$line" =~ $so_regex ]]; then
2916 file="${BASH_REMATCH[1]}"
2917 [[ -e "${initdir}/$file" ]] && continue
2918 inst_library "$file"
898720b7
HH
2919 continue
2920 fi
2921
1b8fcd9c
FS
2922 if [[ "$line" =~ not\ found ]]; then
2923 dfatal "Missing a shared library required by $bin."
2924 dfatal "Run \"ldd $bin\" to find out what it is."
2925 dfatal "$line"
39e17536 2926 dfatal "Cannot create a test image."
898720b7
HH
2927 exit 1
2928 fi
1b8fcd9c 2929 done < <(LC_ALL=C ldd "$bin" 2>/dev/null)
3917534d
FS
2930
2931 # Same as above, but we need to wrap certain libraries unconditionally
2932 #
52c1fb68 2933 # chgrp, chown, getent, login, setfacl, su, useradd, userdel
2de77dbf 2934 # - dlopen() (not only) systemd's PAM modules
c7bf1959 2935 # ls, mkfs.*, mksquashfs, mkswap, setpriv, stat
2de77dbf
FS
2936 # - pull in nss_systemd with certain options (like ls -l) when
2937 # nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux)
e912bef8 2938 # delv, dig - pull in nss_resolve if `resolve` is in nsswitch.conf
3917534d 2939 # tar - called by machinectl in TEST-25
52c1fb68 2940 bin_rx='/(agetty|chgrp|chown|curl|delv|dig|getfacl|getent|id|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|setfacl|setpriv|stat|su|tar|useradd|userdel)$'
e912bef8 2941 if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$bin" =~ $bin_rx ]]; then
3917534d 2942 wrap_binary=1
2b5e7860
FS
2943 # Ugh, so we want to disable LSan in most cases for the wrapped binaries, since
2944 # we don't care about memory leaks in such binaries. However, in certain cases
2945 # the external binary is the only interface for the systemd code, like for
2946 # the systemd NSS modules, where we want to detect memory leaks. So let's
2947 # do another check to decide if we want to enable LSan for given binary.
2948 if [[ "$bin" =~ /getent$ ]]; then
2949 enable_lsan=1
2950 fi
3917534d
FS
2951 fi
2952
b727d7e0
FS
2953 # If the target binary is built with ASan support, we don't need to wrap
2954 # it, as it should handle everything by itself
2955 if get_bool "$wrap_binary" && ! is_built_with_asan "$bin"; then
3917534d
FS
2956 dinfo "Creating ASan-compatible wrapper for binary '$target'"
2957 # Install the target binary with a ".orig" suffix
2958 inst_simple "$bin" "${target}.orig"
2959 # Create a simple shell wrapper in place of the target binary, which
2960 # sets necessary ASan-related env variables and then exec()s the
2961 # suffixed target binary
2962 cat >"$initdir/$target" <<EOF
2963#!/bin/bash
2964# Preload the ASan runtime DSO, otherwise ASAn will complain
2965export LD_PRELOAD="$ASAN_RT_PATH"
2966# Disable LSan to speed things up, since we don't care about leak reports
2967# from 'external' binaries
2b5e7860 2968export ASAN_OPTIONS=detect_leaks=$enable_lsan
3917534d
FS
2969# Set argv[0] to the original binary name without the ".orig" suffix
2970exec -a "\$0" -- "${target}.orig" "\$@"
2971EOF
2972 chmod +x "$initdir/$target"
2973 else
2974 inst_simple "$bin" "$target"
2975 fi
898720b7
HH
2976}
2977
2978# same as above, except for shell scripts.
2979# If your shell script does not start with shebang, it is not a shell script.
2980inst_script() {
1b8fcd9c
FS
2981 local bin line shebang_regex
2982 bin="$(find_binary "${1:?}")" || return 1
898720b7 2983 shift
1b8fcd9c
FS
2984
2985 read -r -n 80 line <"$bin"
898720b7 2986 # If debug is set, clean unprintable chars to prevent messing up the term
23f8e019 2987 get_bool "${debug:=}" && line="$(echo -n "$line" | tr -c -d '[:print:][:space:]')"
1b8fcd9c
FS
2988 shebang_regex='(#! *)(/[^ ]+).*'
2989 [[ "$line" =~ $shebang_regex ]] || return 1
2990 inst "${BASH_REMATCH[2]}" && inst_simple "$bin" "$@"
898720b7
HH
2991}
2992
2993# same as above, but specialized for symlinks
2994inst_symlink() {
1b8fcd9c
FS
2995 local src="${1:?}"
2996 local target="${2:-$src}"
2997 local realsrc
2998
2999 strstr "$src" "/" || return 1
3000 [[ -L "$src" ]] || return 1
3001 [[ -L "${initdir:?}/$target" ]] && return 0
3002 realsrc="$(readlink -f "$src")"
3003 if ! [[ -e "$initdir/$realsrc" ]]; then
3004 if [[ -d "$realsrc" ]]; then
3005 inst_dir "$realsrc"
898720b7 3006 else
1b8fcd9c 3007 inst "$realsrc"
898720b7
HH
3008 fi
3009 fi
1b8fcd9c
FS
3010 [[ ! -e "$initdir/${target%/*}" ]] && inst_dir "${target%/*}"
3011 [[ -d "${target%/*}" ]] && target="$(readlink -f "${target%/*}")/${target##*/}"
3012 ln -sfn -- "$(convert_abs_rel "${target}" "${realsrc}")" "$initdir/$target"
898720b7
HH
3013}
3014
3015# attempt to install any programs specified in a udev rule
3016inst_rule_programs() {
1b8fcd9c
FS
3017 local rule="${1:?}"
3018 local prog bin
898720b7 3019
29bff80b 3020 sed -rn 's/^.*?PROGRAM==?"([^ "]+).*$/\1/p' "$rule" | while read -r prog; do
1b8fcd9c
FS
3021 if [ -x "/lib/udev/$prog" ]; then
3022 bin="/lib/udev/$prog"
3023 else
3024 if ! bin="$(find_binary "$prog")"; then
3025 dinfo "Skipping program $prog used in udev rule $(basename "$rule") as it cannot be found"
3026 continue
898720b7 3027 fi
1b8fcd9c 3028 fi
898720b7 3029
1b8fcd9c 3030 #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
39e17536 3031 image_install "$bin"
1b8fcd9c 3032 done
898720b7
HH
3033}
3034
3035# udev rules always get installed in the same place, so
3036# create a function to install them to make life simpler.
3037inst_rules() {
1b8fcd9c
FS
3038 local target=/etc/udev/rules.d
3039 local found rule
898720b7
HH
3040
3041 inst_dir "/lib/udev/rules.d"
1b8fcd9c
FS
3042 inst_dir "$target"
3043 for rule in "$@"; do
898720b7
HH
3044 if [ "${rule#/}" = "$rule" ]; then
3045 for r in /lib/udev/rules.d /etc/udev/rules.d; do
1b8fcd9c
FS
3046 if [[ -f "$r/$rule" ]]; then
3047 found="$r/$rule"
3048 inst_simple "$found"
3049 inst_rule_programs "$found"
898720b7
HH
3050 fi
3051 done
3052 fi
1b8fcd9c
FS
3053 for r in '' ./; do
3054 if [[ -f "${r}${rule}" ]]; then
3055 found="${r}${rule}"
3056 inst_simple "$found" "$target/${found##*/}"
3057 inst_rule_programs "$found"
898720b7
HH
3058 fi
3059 done
1b8fcd9c
FS
3060 [[ $found ]] || dinfo "Skipping udev rule: $rule"
3061 found=
898720b7
HH
3062 done
3063}
3064
3065# general purpose installation function
3066# Same args as above.
3067inst() {
898720b7
HH
3068 case $# in
3069 1) ;;
1b8fcd9c
FS
3070 2)
3071 [[ ! "$initdir" && -d "$2" ]] && export initdir="$2"
3072 [[ "$initdir" = "$2" ]] && set "$1"
3073 ;;
3074 3)
3075 [[ -z "$initdir" ]] && export initdir="$2"
3076 set "$1" "$3"
3077 ;;
3078 *)
3079 dfatal "inst only takes 1 or 2 or 3 arguments"
3080 exit 1
3081 ;;
898720b7 3082 esac
1b8fcd9c
FS
3083
3084 local fun
3085 for fun in inst_symlink inst_script inst_binary inst_simple; do
3086 "$fun" "$@" && return 0
898720b7 3087 done
7074c047
FS
3088
3089 dwarn "Failed to install '$1'"
898720b7
HH
3090 return 1
3091}
3092
3093# install any of listed files
3094#
3095# If first argument is '-d' and second some destination path, first accessible
3096# source is installed into this path, otherwise it will installed in the same
3097# path as source. If none of listed files was installed, function return 1.
3098# On first successful installation it returns with 0 status.
3099#
3100# Example:
3101#
3102# inst_any -d /bin/foo /bin/bar /bin/baz
3103#
3104# Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
32e27670 3105# initrd.
898720b7 3106inst_any() {
1b8fcd9c 3107 local dest file
898720b7 3108
1b8fcd9c 3109 [[ "${1:?}" = '-d' ]] && dest="${2:?}" && shift 2
898720b7 3110
1b8fcd9c
FS
3111 for file in "$@"; do
3112 if [[ -e "$file" ]]; then
3113 [[ -n "$dest" ]] && inst "$file" "$dest" && return 0
3114 inst "$file" && return 0
898720b7
HH
3115 fi
3116 done
3117
3118 return 1
3119}
3120
da0465dc
YW
3121inst_recursive() {
3122 local p item
3123
3124 for p in "$@"; do
eb5d7730
FS
3125 # Make sure the source exists, as the process substitution below
3126 # suppresses errors
3127 stat "$p" >/dev/null || return 1
3128
da0465dc
YW
3129 while read -r item; do
3130 if [[ -d "$item" ]]; then
3131 inst_dir "$item"
3132 elif [[ -f "$item" ]]; then
3133 inst_simple "$item"
3134 fi
3135 done < <(find "$p" 2>/dev/null)
3136 done
3137}
3138
39e17536
FS
3139# image_install [-o ] <file> [<file> ... ]
3140# Install <file> to the test image
898720b7 3141# -o optionally install the <file> and don't fail, if it is not there
39e17536 3142image_install() {
1b8fcd9c
FS
3143 local optional=no
3144 local prog="${1:?}"
3145
3146 if [[ "$prog" = '-o' ]]; then
3147 optional=yes
898720b7
HH
3148 shift
3149 fi
1b8fcd9c
FS
3150
3151 for prog in "$@"; do
3152 if ! inst "$prog" ; then
23f8e019 3153 if get_bool "$optional"; then
1b8fcd9c 3154 dinfo "Skipping program $prog as it cannot be found and is" \
898720b7
HH
3155 "flagged to be optional"
3156 else
1b8fcd9c 3157 dfatal "Failed to install $prog"
898720b7
HH
3158 exit 1
3159 fi
3160 fi
898720b7
HH
3161 done
3162}
3163
0d6e798a
HH
3164# Install a single kernel module along with any firmware it may require.
3165# $1 = full path to kernel module to install
3166install_kmod_with_fw() {
94009c27 3167 local module="${1:?}"
0d6e798a 3168 # no need to go further if the module is already installed
c049acb2 3169 [[ -e "${initdir:?}/lib/modules/${KERNEL_VER:?}/${module##*"/lib/modules/$KERNEL_VER/"}" ]] && return 0
94009c27 3170 [[ -e "$initdir/.kernelmodseen/${module##*/}" ]] && return 0
0d6e798a 3171
94009c27 3172 [ -d "$initdir/.kernelmodseen" ] && : >"$initdir/.kernelmodseen/${module##*/}"
0d6e798a 3173
c049acb2 3174 inst_simple "$module" "/lib/modules/$KERNEL_VER/${module##*"/lib/modules/$KERNEL_VER/"}" || return $?
0d6e798a 3175
94009c27
FS
3176 local modname="${module##*/}"
3177 local fwdir found fw
3178 modname="${modname%.ko*}"
0d6e798a 3179
94009c27
FS
3180 while read -r fw; do
3181 found=
3182 for fwdir in /lib/firmware/updates /lib/firmware; do
3183 if [[ -d "$fwdir" && -f "$fwdir/$fw" ]]; then
3184 inst_simple "$fwdir/$fw" "/lib/firmware/$fw"
3185 found=yes
0d6e798a
HH
3186 fi
3187 done
23f8e019 3188 if ! get_bool "$found"; then
94009c27
FS
3189 if ! grep -qe "\<${modname//-/_}\>" /proc/modules; then
3190 dinfo "Possible missing firmware \"${fw}\" for kernel module" \
3191 "\"${modname}.ko\""
0d6e798a 3192 else
94009c27
FS
3193 dwarn "Possible missing firmware \"${fw}\" for kernel module" \
3194 "\"${modname}.ko\""
0d6e798a
HH
3195 fi
3196 fi
94009c27 3197 done < <(modinfo -k "$KERNEL_VER" -F firmware "$module" 2>/dev/null)
0d6e798a
HH
3198 return 0
3199}
3200
3201# Do something with all the dependencies of a kernel module.
3202# Note that kernel modules depend on themselves using the technique we use
3203# $1 = function to call for each dependency we find
3204# It will be passed the full path to the found kernel module
3205# $2 = module to get dependencies for
3206# rest of args = arguments to modprobe
0d6e798a 3207for_each_kmod_dep() {
94009c27
FS
3208 local func="${1:?}"
3209 local kmod="${2:?}"
3210 local found=0
3211 local cmd modpath
0d6e798a 3212 shift 2
0d6e798a 3213
94009c27
FS
3214 while read -r cmd modpath _; do
3215 [[ "$cmd" = insmod ]] || continue
3216 "$func" "$modpath" || return $?
3217 found=1
3218 done < <(modprobe "$@" --ignore-install --show-depends "$kmod")
0d6e798a 3219
23f8e019 3220 ! get_bool "$found" && return 1
94009c27 3221 return 0
0d6e798a
HH
3222}
3223
3224# instmods [-c] <kernel module> [<kernel module> ... ]
3225# instmods [-c] <kernel subsystem>
3226# install kernel modules along with all their dependencies.
3227# <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage"
94009c27 3228# FIXME(?): dracutdevs/dracut@f4e38c0da8d6bf3764c1ad753d9d52aef63050e5
0d6e798a 3229instmods() {
94009c27
FS
3230 local check=no
3231 if [[ $# -ge 0 && "$1" = '-c' ]]; then
3232 check=yes
0d6e798a
HH
3233 shift
3234 fi
3235
94009c27
FS
3236 inst1mod() {
3237 local mod="${1:?}"
3238 local ret=0
3239 local mod_dir="/lib/modules/${KERNEL_VER:?}/"
3240
3241 case "$mod" in
0d6e798a 3242 =*)
94009c27
FS
3243 if [ -f "${mod_dir}/modules.${mod#=}" ]; then
3244 (
3245 [[ "$mpargs" ]] && echo "$mpargs"
3246 cat "${mod_dir}/modules.${mod#=}"
3247 ) | instmods
0d6e798a 3248 else
94009c27
FS
3249 (
3250 [[ "$mpargs" ]] && echo "$mpargs"
84817bfd 3251 find "$mod_dir" -path "*/${mod#=}/*" -name "*.ko*" -type f -printf '%f\n'
94009c27 3252 ) | instmods
0d6e798a
HH
3253 fi
3254 ;;
94009c27
FS
3255 --*)
3256 mpargs+=" $mod"
3257 ;;
3258 i2o_scsi)
3259 # Do not load this diagnostic-only module
3260 return
3261 ;;
0d6e798a 3262 *)
94009c27 3263 mod=${mod##*/}
0d6e798a
HH
3264 # if we are already installed, skip this module and go on
3265 # to the next one.
94009c27 3266 [[ -f "${initdir:?}/.kernelmodseen/${mod%.ko}.ko" ]] && return
0d6e798a
HH
3267
3268 # We use '-d' option in modprobe only if modules prefix path
3269 # differs from default '/'. This allows us to use Dracut with
3270 # old version of modprobe which doesn't have '-d' option.
94009c27
FS
3271 local mod_dirname=${mod_dir%%/lib/modules/*}
3272 [[ -n ${mod_dirname} ]] && mod_dirname="-d ${mod_dirname}/"
0d6e798a
HH
3273
3274 # ok, load the module, all its dependencies, and any firmware
3275 # it may require
94009c27
FS
3276 for_each_kmod_dep install_kmod_with_fw "$mod" \
3277 --set-version "$KERNEL_VER" \
3278 ${mod_dirname:+"$mod_dirname"} \
3279 ${mpargs:+"$mpargs"}
3280 ((ret+=$?))
0d6e798a
HH
3281 ;;
3282 esac
7bf20e48 3283 return "$ret"
0d6e798a
HH
3284 }
3285
94009c27
FS
3286 local mod mpargs
3287
3288 if [[ $# -eq 0 ]]; then # filenames from stdin
3289 while read -r mod; do
3290 if ! inst1mod "${mod%.ko*}" && [ "$check" = "yes" ]; then
3291 dfatal "Failed to install $mod"
3292 return 1
3293 fi
0d6e798a 3294 done
94009c27 3295 fi
0d6e798a 3296
94009c27
FS
3297 for mod in "$@"; do # filenames as arguments
3298 if ! inst1mod "${mod%.ko*}" && [ "$check" = "yes" ]; then
3299 dfatal "Failed to install $mod"
3300 return 1
3301 fi
3302 done
3303
3304 return 0
0d6e798a 3305}
898720b7 3306
9e19a8b0 3307_umount_dir() {
1b8fcd9c
FS
3308 local mountpoint="${1:?}"
3309 if mountpoint -q "$mountpoint"; then
3310 ddebug "umount $mountpoint"
3311 umount "$mountpoint"
9e19a8b0
DS
3312 fi
3313}
3314
ec4cab49
DS
3315# can be overridden in specific test
3316test_setup_cleanup() {
ec43f686 3317 cleanup_initdir
ec4cab49
DS
3318}
3319
3320_test_cleanup() {
f85bc044
DS
3321 # (post-test) cleanup should always ignore failure and cleanup as much as possible
3322 (
3323 set +e
1b8fcd9c
FS
3324 [[ -n "$initdir" ]] && _umount_dir "$initdir"
3325 [[ -n "$IMAGE_PUBLIC" ]] && rm -vf "$IMAGE_PUBLIC"
21be71ee 3326 # If multiple setups/cleans are ran in parallel, this can cause a race
1c3f490f 3327 if [[ -n "$IMAGESTATEDIR" && $TEST_PARALLELIZE -ne 1 ]]; then
21be71ee
LB
3328 rm -vf "${IMAGESTATEDIR}/default.img"
3329 fi
1b8fcd9c
FS
3330 [[ -n "$TESTDIR" ]] && rm -vfr "$TESTDIR"
3331 [[ -n "$STATEFILE" ]] && rm -vf "$STATEFILE"
65dd488f 3332 ) || :
ec4cab49
DS
3333}
3334
054ee249
MP
3335# can be overridden in specific test
3336test_cleanup() {
ec4cab49 3337 _test_cleanup
054ee249
MP
3338}
3339
693ad298
ZJS
3340test_cleanup_again() {
3341 [ -n "$TESTDIR" ] || return
3342 rm -rf "$TESTDIR/unprivileged-nspawn-root"
1b8fcd9c 3343 [[ -n "$initdir" ]] && _umount_dir "$initdir"
a82cf490
LB
3344 # Test specific images are not reused, so delete them or we run out of disk space
3345 if [[ -n "$IMAGE_PUBLIC" ]] && [ "$(basename "$IMAGE_PUBLIC")" != "default.img" ]; then
3346 rm -vf "$IMAGE_PUBLIC"
3347 fi
3348 if [[ -n "$IMAGE_PRIVATE" ]] && [ "$(basename "$IMAGE_PRIVATE")" != "default.img" ]; then
3349 rm -vf "$IMAGE_PRIVATE"
3350 fi
693ad298
ZJS
3351}
3352
8c3534b5 3353test_create_image() {
70ce817c
ZJS
3354 create_empty_image_rootdir
3355
3356 # Create what will eventually be our root filesystem onto an overlay
3357 (
3358 LOG_LEVEL=5
70ce817c 3359 setup_basic_environment
70ce817c 3360 )
8c3534b5
ZJS
3361}
3362
3363test_setup() {
58bcbad8
FS
3364 if ! get_bool "$NO_BUILD" && \
3365 get_bool "${TEST_REQUIRE_INSTALL_TESTS:?}" && \
3366 command -v meson >/dev/null && \
3367 [[ "$(meson configure "${BUILD_DIR:?}" | grep install-tests | awk '{ print $2 }')" != "true" ]]; then
20f938ff 3368 dfatal "$BUILD_DIR needs to be built with -Dinstall-tests=true"
8c3534b5
ZJS
3369 exit 1
3370 fi
3371
1b8fcd9c
FS
3372 if [ -e "${IMAGE_PRIVATE:?}" ]; then
3373 echo "Reusing existing image $IMAGE_PRIVATE → $(realpath "$IMAGE_PRIVATE")"
8c3534b5 3374 mount_initdir
2991fa41 3375 else
1b8fcd9c 3376 if [ ! -e "${IMAGE_PUBLIC:?}" ]; then
d9e606e8 3377 # default.img is the base that every test uses and optionally appends to
1b8fcd9c 3378 if [ ! -e "${IMAGESTATEDIR:?}/default.img" ] || [ -n "${TEST_FORCE_NEWIMAGE:=}" ]; then
d9e606e8
LB
3379 # Create the backing public image, but then completely unmount
3380 # it and drop the loopback device responsible for it, since we're
3381 # going to symlink/copy the image and mount it again from
3382 # elsewhere.
1b8fcd9c 3383 local image_old="${IMAGE_PUBLIC}"
d9e606e8
LB
3384 if [ -z "${TEST_FORCE_NEWIMAGE}" ]; then
3385 IMAGE_PUBLIC="${IMAGESTATEDIR}/default.img"
3386 fi
3387 test_create_image
3388 test_setup_cleanup
3389 umount_loopback
3390 cleanup_loopdev
3391 IMAGE_PUBLIC="${image_old}"
3392 fi
23f8e019 3393 if [ "${IMAGE_NAME:?}" != "default" ] && ! get_bool "${TEST_FORCE_NEWIMAGE}"; then
d9e606e8
LB
3394 cp -v "$(realpath "${IMAGESTATEDIR}/default.img")" "$IMAGE_PUBLIC"
3395 fi
3396 fi
3397
23f8e019
FS
3398 local hook_defined
3399 declare -f -F test_append_files >/dev/null && hook_defined=yes || hook_defined=no
2991fa41 3400
1b8fcd9c 3401 echo "Reusing existing cached image $IMAGE_PUBLIC → $(realpath "$IMAGE_PUBLIC")"
23f8e019 3402 if get_bool "$TEST_PARALLELIZE" || get_bool "$hook_defined"; then
1b8fcd9c 3403 cp -v -- "$(realpath "$IMAGE_PUBLIC")" "$IMAGE_PRIVATE"
7a57256c 3404 else
1b8fcd9c 3405 ln -sv -- "$(realpath "$IMAGE_PUBLIC")" "$IMAGE_PRIVATE"
7a57256c 3406 fi
2991fa41 3407
8c3534b5 3408 mount_initdir
508a7f04
FS
3409
3410 if get_bool "${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED}"; then
eb70d945
FS
3411 dinfo "Masking supporting services"
3412 mask_supporting_services
3413 fi
3414
7fdd6e15
FS
3415 if get_bool "$IS_BUILT_WITH_COVERAGE"; then
3416 # Do an initial coverage capture, to make sure the final report includes
3417 # files that the tests didn't touch at all
3418 lcov --initial --capture --directory "${initdir}/${BUILD_DIR:?}" --output-file "${TESTDIR:?}/coverage-base"
3419 fi
3420
23f8e019 3421 if get_bool "$hook_defined"; then
1b8fcd9c 3422 test_append_files "${initdir:?}"
d9e606e8 3423 fi
8c3534b5
ZJS
3424 fi
3425
70ce817c
ZJS
3426 setup_nspawn_root
3427}
3428
054ee249 3429test_run() {
1b8fcd9c 3430 local test_id="${1:?}"
4962ed9f 3431 mount_initdir
4962ed9f 3432
23f8e019 3433 if ! get_bool "${TEST_NO_QEMU:=}"; then
1b8fcd9c 3434 if run_qemu "$test_id"; then
3e8caa34 3435 check_result_qemu || { echo "qemu test failed"; return 1; }
054ee249 3436 else
3e8caa34 3437 dwarn "can't run qemu, skipping"
054ee249
MP
3438 fi
3439 fi
23f8e019 3440 if ! get_bool "${TEST_NO_NSPAWN:=}"; then
ec43f686 3441 mount_initdir
1b8fcd9c 3442 if run_nspawn "${initdir:?}" "$test_id"; then
ec43f686 3443 check_result_nspawn "$initdir" || { echo "nspawn-root test failed"; return 1; }
054ee249
MP
3444 else
3445 dwarn "can't run systemd-nspawn, skipping"
3446 fi
746fbd9c 3447
23f8e019 3448 if get_bool "${RUN_IN_UNPRIVILEGED_CONTAINER:=}"; then
ec43f686 3449 dir="$TESTDIR/unprivileged-nspawn-root"
1b8fcd9c 3450 if NSPAWN_ARGUMENTS="-U --private-network ${NSPAWN_ARGUMENTS:-}" run_nspawn "$dir" "$test_id"; then
ec43f686 3451 check_result_nspawn "$dir" || { echo "unprivileged-nspawn-root test failed"; return 1; }
746fbd9c
EV
3452 else
3453 dwarn "can't run systemd-nspawn, skipping"
3454 fi
d56db495 3455 fi
054ee249
MP
3456 fi
3457 return 0
3458}
3459
898720b7 3460do_test() {
33a5e20f
HH
3461 if [[ $UID != "0" ]]; then
3462 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2
3463 exit 0
3464 fi
3465
23f8e019 3466 if get_bool "${TEST_NO_QEMU:=}" && get_bool "${TEST_NO_NSPAWN:=}"; then
3e8caa34 3467 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: both qemu and nspawn disabled" >&2
aeac20fc
LB
3468 exit 0
3469 fi
3470
23f8e019 3471 if get_bool "${TEST_QEMU_ONLY:=}" && ! get_bool "$TEST_NO_NSPAWN"; then
3e8caa34 3472 echo "TEST: $TEST_DESCRIPTION [SKIPPED]: qemu-only tests requested" >&2
51d56d3b
LB
3473 exit 0
3474 fi
3475
23f8e019 3476 if get_bool "${TEST_PREFER_NSPAWN:=}" && ! get_bool "$TEST_NO_NSPAWN"; then
eb3785f3
LB
3477 TEST_NO_QEMU=1
3478 fi
3479
cc5549ca 3480 # Detect lib paths
1b8fcd9c 3481 [[ "$libdir" ]] || for libdir in /lib64 /lib; do
898720b7
HH
3482 [[ -d $libdir ]] && libdirs+=" $libdir" && break
3483 done
3484
1b8fcd9c 3485 [[ "$usrlibdir" ]] || for usrlibdir in /usr/lib64 /usr/lib; do
898720b7
HH
3486 [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
3487 done
3488
0c566150 3489 mkdir -p "$WORKDIR"
22077c9c
MP
3490 mkdir -p "$STATEDIR"
3491
898720b7 3492 import_testdir
889a9042 3493 import_initdir
898720b7 3494
954c77c2
ZJS
3495 if [ -n "${SUDO_USER}" ]; then
3496 ddebug "Making ${TESTDIR:?} readable for ${SUDO_USER} (acquired from sudo)"
3497 setfacl -m "user:${SUDO_USER:?}:r-X" "${TESTDIR:?}"
954c77c2
ZJS
3498 fi
3499
1b8fcd9c 3500 testname="$(basename "$PWD")"
8af10ca3 3501
898720b7
HH
3502 while (($# > 0)); do
3503 case $1 in
3504 --run)
8af10ca3 3505 echo "${testname} RUN: $TEST_DESCRIPTION"
c4cd6205 3506 test_run "$TESTID"
0013fac2 3507 ret=$?
1b8fcd9c 3508 if [ $ret -eq 0 ]; then
8af10ca3 3509 echo "${testname} RUN: $TEST_DESCRIPTION [OK]"
898720b7 3510 else
8af10ca3 3511 echo "${testname} RUN: $TEST_DESCRIPTION [FAILED]"
898720b7 3512 fi
23f8e019
FS
3513 exit $ret
3514 ;;
898720b7 3515 --setup)
8af10ca3 3516 echo "${testname} SETUP: $TEST_DESCRIPTION"
898720b7 3517 test_setup
ec4cab49 3518 test_setup_cleanup
818567fc 3519 ;;
693ad298 3520 --clean)
8af10ca3 3521 echo "${testname} CLEANUP: $TEST_DESCRIPTION"
898720b7 3522 test_cleanup
818567fc 3523 ;;
693ad298 3524 --clean-again)
8af10ca3 3525 echo "${testname} CLEANUP AGAIN: $TEST_DESCRIPTION"
693ad298
ZJS
3526 test_cleanup_again
3527 ;;
898720b7 3528 --all)
818567fc 3529 ret=0
8af10ca3 3530 echo -n "${testname}: $TEST_DESCRIPTION "
0761da38
LB
3531 # Do not use a subshell, otherwise cleanup variables (LOOPDEV) will be lost
3532 # and loop devices will leak
3533 test_setup </dev/null >"$TESTLOG" 2>&1 || ret=$?
3534 if [ $ret -eq 0 ]; then
3535 test_setup_cleanup </dev/null >>"$TESTLOG" 2>&1 || ret=$?
3536 fi
3537 if [ $ret -eq 0 ]; then
c4cd6205 3538 test_run "$TESTID" </dev/null >>"$TESTLOG" 2>&1 || ret=$?
0761da38 3539 fi
f85bc044 3540 test_cleanup
898720b7 3541 if [ $ret -eq 0 ]; then
49e83429
LB
3542 # $TESTLOG is in $STATEDIR, so clean it up only on success
3543 [[ -n "$STATEDIR" ]] && rm -vfr "$STATEDIR"
898720b7
HH
3544 echo "[OK]"
3545 else
3546 echo "[FAILED]"
22077c9c 3547 echo "see $TESTLOG"
898720b7 3548 fi
23f8e019
FS
3549 exit $ret
3550 ;;
3551 *)
3552 break
3553 ;;
898720b7
HH
3554 esac
3555 shift
3556 done
3557}