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