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