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