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