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