#!/usr/bin/env bash
-# shellcheck disable=SC2031
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# shellcheck disable=SC2030,SC2031
# ex: ts=8 sw=4 sts=4 et filetype=sh tw=180
# Note: the shellcheck line above disables warning for variables which were
# modified in a subshell. In our case this behavior is expected, but
[[ "$ID" = "arch" || " $ID_LIKE " = *" arch "* ]] && LOOKS_LIKE_ARCH=yes || LOOKS_LIKE_ARCH=""
[[ " $ID_LIKE " = *" suse "* ]] && LOOKS_LIKE_SUSE=yes || LOOKS_LIKE_SUSE=""
KERNEL_VER="${KERNEL_VER-$(uname -r)}"
-QEMU_TIMEOUT="${QEMU_TIMEOUT:-infinity}"
-NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-infinity}"
+QEMU_TIMEOUT="${QEMU_TIMEOUT:-1800}"
+NSPAWN_TIMEOUT="${NSPAWN_TIMEOUT:-1800}"
TIMED_OUT= # will be 1 after run_* if *_TIMEOUT is set and test timed out
[[ "$LOOKS_LIKE_SUSE" ]] && FSTYPE="${FSTYPE:-btrfs}" || FSTYPE="${FSTYPE:-ext4}"
UNIFIED_CGROUP_HIERARCHY="${UNIFIED_CGROUP_HIERARCHY:-default}"
EFI_MOUNT="${EFI_MOUNT:-$(bootctl -x 2>/dev/null || echo /boot)}"
-QEMU_MEM="${QEMU_MEM:-512M}"
# Note that defining a different IMAGE_NAME in a test setup script will only result
# in default.img being copied and renamed. It can then be extended by defining
# a test_append_files() function. The $1 parameter will be the root directory.
STRIP_BINARIES="${STRIP_BINARIES:-yes}"
TEST_REQUIRE_INSTALL_TESTS="${TEST_REQUIRE_INSTALL_TESTS:-1}"
TEST_PARALLELIZE="${TEST_PARALLELIZE:-0}"
+TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED="${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED:-1}"
LOOPDEV=
# Simple wrapper to unify boolean checks.
fi
}
-# Decide if we can (and want to) run QEMU with KVM acceleration.
+# Since in Bash we can have only one handler per signal, let's overcome this
+# limitation by having one global handler for the EXIT signal which executes
+# all registered handlers
+_AT_EXIT_HANDLERS=()
+_at_exit() {
+ set +e
+
+ # Run the EXIT handlers in reverse order
+ for ((i = ${#_AT_EXIT_HANDLERS[@]} - 1; i >= 0; i--)); do
+ ddebug "Running EXIT handler '${_AT_EXIT_HANDLERS[$i]}'"
+ "${_AT_EXIT_HANDLERS[$i]}"
+ done
+}
+
+trap _at_exit EXIT
+
+add_at_exit_handler() {
+ local handler="${1?}"
+
+ if [[ "$(type -t "$handler")" != "function" ]]; then
+ dfatal "'$handler' is not a function"
+ exit 1
+ fi
+
+ _AT_EXIT_HANDLERS+=("$handler")
+}
+
+# Decide if we can (and want to) run qemu with KVM acceleration.
# Check if nested KVM is explicitly enabled (TEST_NESTED_KVM). If not,
# check if it's not explicitly disabled (TEST_NO_KVM) and we're not already
# running under KVM. If these conditions are met, enable KVM (and possibly
# nested KVM), otherwise disable it.
-if get_bool "${TEST_NESTED_KVM:=}" || (! get_bool "${TEST_NO_KVM:=}" && [[ "$(systemd-detect-virt -v)" != kvm ]]); then
+if get_bool "${TEST_NESTED_KVM:=}" || (! get_bool "${TEST_NO_KVM:=}" && ! systemd-detect-virt -qv); then
QEMU_KVM=yes
else
QEMU_KVM=no
export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR TOOLS_DIR
# note that find-build-dir.sh will return $BUILD_DIR if provided, else it will try to find it
-if ! BUILD_DIR="$("$TOOLS_DIR"/find-build-dir.sh)"; then
- if get_bool "${NO_BUILD:=}"; then
- BUILD_DIR="$SOURCE_DIR"
- else
- echo "ERROR: no build found, please set BUILD_DIR or use NO_BUILD" >&2
- exit 1
- fi
+if get_bool "${NO_BUILD:=}"; then
+ BUILD_DIR="$SOURCE_DIR"
+elif ! BUILD_DIR="$("$TOOLS_DIR"/find-build-dir.sh)"; then
+ echo "ERROR: no build found, please set BUILD_DIR or use NO_BUILD" >&2
+ exit 1
fi
PATH_TO_INIT="$ROOTLIBDIR/systemd"
SYSTEMD_JOURNALD="${SYSTEMD_JOURNALD:-$(command -v "$BUILD_DIR/systemd-journald" || command -v "$ROOTLIBDIR/systemd-journald")}"
-SYSTEMD_JOURNAL_REMOTE="${SYSTEMD_JOURNAL_REMOTE:-$(command -v "$BUILD_DIR/systemd-journal-remote" || command -v "$ROOTLIBDIR/systemd-journal-remote")}"
+SYSTEMD_JOURNAL_REMOTE="${SYSTEMD_JOURNAL_REMOTE:-$(command -v "$BUILD_DIR/systemd-journal-remote" || command -v "$ROOTLIBDIR/systemd-journal-remote" || echo "")}"
SYSTEMD="${SYSTEMD:-$(command -v "$BUILD_DIR/systemd" || command -v "$ROOTLIBDIR/systemd")}"
SYSTEMD_NSPAWN="${SYSTEMD_NSPAWN:-$(command -v "$BUILD_DIR/systemd-nspawn" || command -v systemd-nspawn)}"
JOURNALCTL="${JOURNALCTL:-$(command -v "$BUILD_DIR/journalctl" || command -v journalctl)}"
+SYSTEMCTL="${SYSTEMCTL:-$(command -v "$BUILD_DIR/systemctl" || command -v systemctl)}"
TESTFILE="${BASH_SOURCE[1]}"
if [ -z "$TESTFILE" ]; then
base64
basename
bash
- busybox
capsh
cat
chmod
chown
+ chroot
cmp
cryptsetup
cut
echo
env
false
+ flock
getconf
getent
getfacl
head
ionice
ip
+ jq
+ killall
+ ldd
ln
loadkeys
login
+ losetup
lz4cat
mkfifo
mktemp
mv
nc
nproc
+ pkill
readlink
rev
rm
rmdir
+ rmmod
sed
seq
setfattr
sfdisk
sh
sleep
- socat
stat
su
sulogin
umount
uname
unshare
+ useradd
+ userdel
+ wc
xargs
xzcat
)
stty
tty
vi
+ /usr/libexec/vi
)
is_built_with_asan() {
+ local _bin="${1:?}"
+
if ! type -P objdump >/dev/null; then
ddebug "Failed to find objdump. Assuming systemd hasn't been built with ASAN."
return 1
# Borrowed from https://github.com/google/oss-fuzz/blob/cd9acd02f9d3f6e80011cc1e9549be526ce5f270/infra/base-images/base-runner/bad_build_check#L182
local _asan_calls
- _asan_calls="$(objdump -dC "$SYSTEMD_JOURNALD" | grep -E "callq?\s+[0-9a-f]+\s+<__asan" -c)"
+ _asan_calls="$(objdump -dC "$_bin" | grep -E "(callq?|brasl?|bl)\s.+__asan" -c)"
if ((_asan_calls < 1000)); then
return 1
else
fi
}
-IS_BUILT_WITH_ASAN=$(is_built_with_asan && echo yes || echo no)
+is_built_with_coverage() {
+ if get_bool "${NO_BUILD:=}" || ! command -v meson >/dev/null; then
+ return 1
+ fi
+
+ meson configure "${BUILD_DIR:?}" | grep 'b_coverage' | awk '{ print $2 }' | grep -q 'true'
+}
+
+IS_BUILT_WITH_ASAN=$(is_built_with_asan "$SYSTEMD_JOURNALD" && echo yes || echo no)
+IS_BUILT_WITH_COVERAGE=$(is_built_with_coverage && echo yes || echo no)
if get_bool "$IS_BUILT_WITH_ASAN"; then
STRIP_BINARIES=no
SKIP_INITRD="${SKIP_INITRD:-yes}"
PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
- QEMU_MEM="2048M"
+ QEMU_MEM="${QEMU_MEM:-2G}"
QEMU_SMP="${QEMU_SMP:-4}"
# We need to correctly distinguish between gcc's and clang's ASan DSOs.
esac
if [[ ! -e "$QEMU_BIN" ]]; then
- echo "Could not find a suitable QEMU binary" >&2
+ echo "Could not find a suitable qemu binary" >&2
return 1
fi
}
printf "%s\n%s\n" "$1" "$qemu_ver" | sort -V -C
}
-# Return 0 if QEMU did run (then you must check the result state/logs for actual
-# success), or 1 if QEMU is not available.
+# Return 0 if qemu did run (then you must check the result state/logs for actual
+# success), or 1 if qemu is not available.
run_qemu() {
+ # If the test provided its own initrd, use it (e.g. TEST-24)
+ if [[ -z "$INITRD" && -f "${TESTDIR:?}/initrd.img" ]]; then
+ INITRD="$TESTDIR/initrd.img"
+ fi
+
if [ -f /etc/machine-id ]; then
read -r MACHINE_ID </etc/machine-id
[ -z "$INITRD" ] && [ -e "$EFI_MOUNT/$MACHINE_ID/$KERNEL_VER/initrd" ] \
[ "$ARCH" ] || ARCH=$(uname -m)
case $ARCH in
ppc64*)
- KERNEL_BIN="/boot/vmlinux-$KERNEL_VER"
- CONSOLE=hvc0
- ;;
+ # Ubuntu ppc64* calls the kernel binary as vmlinux-*, RHEL/CentOS
+ # uses the "standard" vmlinuz- prefix
+ [[ -e "/boot/vmlinux-$KERNEL_VER" ]] && KERNEL_BIN="/boot/vmlinux-$KERNEL_VER" || KERNEL_BIN="/boot/vmlinuz-$KERNEL_VER"
+ CONSOLE=hvc0
+ ;;
*)
- KERNEL_BIN="/boot/vmlinuz-$KERNEL_VER"
- ;;
+ KERNEL_BIN="/boot/vmlinuz-$KERNEL_VER"
+ ;;
esac
fi
fi
fi
kernel_params+=(
- "root=/dev/sda1"
+ "root=LABEL=systemd_boot"
"rw"
"raid=noautodetect"
"rd.luks=0"
"loglevel=2"
"init=$PATH_TO_INIT"
"console=$CONSOLE"
- "selinux=0"
"SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-$1.units:/usr/lib/systemd/tests/testdata/units:"
"systemd.unit=testsuite.target"
"systemd.wants=testsuite-$1.service"
)
if ! get_bool "$INTERACTIVE_DEBUG"; then
- kernel_params+=("systemd.wants=end.service")
+ kernel_params+=(
+ "oops=panic"
+ "panic=1"
+ "softlockup_panic=1"
+ "systemd.wants=end.service"
+ )
fi
[ -e "$IMAGE_PRIVATE" ] && image="$IMAGE_PRIVATE" || image="$IMAGE_PUBLIC"
qemu_options+=(
-smp "$QEMU_SMP"
-net none
- -m "$QEMU_MEM"
+ -m "${QEMU_MEM:-768M}"
-nographic
-kernel "$KERNEL_BIN"
-drive "format=raw,cache=unsafe,file=$image"
+ -device "virtio-rng-pci,max-bytes=1024,period=1000"
)
if [[ -n "${QEMU_OPTIONS:=}" ]]; then
if [[ -n "${KERNEL_APPEND:=}" ]]; then
local user_kernel_append
- read -ra user_kernel_append <<< "$KERNEL_APPEND"
+ readarray user_kernel_append <<< "$KERNEL_APPEND"
kernel_params+=("${user_kernel_append[@]}")
fi
derror "Test timed out after ${QEMU_TIMEOUT}s"
TIMED_OUT=1
else
- [ "$rc" != 0 ] && derror "QEMU failed with exit code $rc"
+ [ "$rc" != 0 ] && derror "qemu failed with exit code $rc"
fi
return 0
}
"--kill-signal=SIGKILL"
"--directory=${1:?}"
"--setenv=SYSTEMD_UNIT_PATH=/usr/lib/systemd/tests/testdata/testsuite-$2.units:/usr/lib/systemd/tests/testdata/units:"
+ "--machine=TEST-$TESTID"
)
local kernel_params=(
"$PATH_TO_INIT"
if [[ -n "${KERNEL_APPEND:=}" ]]; then
local user_kernel_append
- read -ra user_kernel_append <<< "$KERNEL_APPEND"
+ readarray user_kernel_append <<< "$KERNEL_APPEND"
kernel_params+=("${user_kernel_append[@]}")
fi
oldinitdir="$initdir"
rm -rfv "$TESTDIR/minimal"
export initdir="$TESTDIR/minimal"
- mkdir -p "$initdir/usr/lib/systemd/system" "$initdir/usr/lib/extension-release.d" "$initdir/etc" "$initdir/var/tmp" "$initdir/opt"
+ # app0 will use TemporaryFileSystem=/var/lib, app1 will need the mount point in the base image
+ mkdir -p "$initdir/usr/lib/systemd/system" "$initdir/usr/lib/extension-release.d" "$initdir/etc" "$initdir/var/tmp" "$initdir/opt" "$initdir/var/lib/app1"
setup_basic_dirs
install_basic_tools
+ install_ld_so_conf
# Shellcheck treats [[ -v VAR ]] as an assignment to avoid a different
# issue, thus falsely triggering SC2030 in this case
# See: koalaman/shellcheck#1409
touch "$initdir/etc/machine-id" "$initdir/etc/resolv.conf"
touch "$initdir/opt/some_file"
echo MARKER=1 >>"$initdir/usr/lib/os-release"
- echo -e "[Service]\nExecStartPre=cat /usr/lib/os-release\nExecStart=sleep 120" >"$initdir/usr/lib/systemd/system/app0.service"
- cp "$initdir/usr/lib/systemd/system/app0.service" "$initdir/usr/lib/systemd/system/app0-foo.service"
+ echo "PORTABLE_PREFIXES=app0 minimal minimal-app0" >>"$initdir/usr/lib/os-release"
+ cat >"$initdir/usr/lib/systemd/system/minimal-app0.service" <<EOF
+[Service]
+ExecStartPre=cat /usr/lib/os-release
+ExecStart=sleep 120
+EOF
+ cp "$initdir/usr/lib/systemd/system/minimal-app0.service" "$initdir/usr/lib/systemd/system/minimal-app0-foo.service"
- mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_0.raw"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_0.raw" -noappend
veritysetup format "$oldinitdir/usr/share/minimal_0.raw" "$oldinitdir/usr/share/minimal_0.verity" | \
grep '^Root hash:' | cut -f2 | tr -d '\n' >"$oldinitdir/usr/share/minimal_0.roothash"
sed -i "s/MARKER=1/MARKER=2/g" "$initdir/usr/lib/os-release"
- rm "$initdir/usr/lib/systemd/system/app0-foo.service"
- cp "$initdir/usr/lib/systemd/system/app0.service" "$initdir/usr/lib/systemd/system/app0-bar.service"
+ rm "$initdir/usr/lib/systemd/system/minimal-app0-foo.service"
+ cp "$initdir/usr/lib/systemd/system/minimal-app0.service" "$initdir/usr/lib/systemd/system/minimal-app0-bar.service"
- mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_1.raw"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/minimal_1.raw" -noappend
veritysetup format "$oldinitdir/usr/share/minimal_1.raw" "$oldinitdir/usr/share/minimal_1.verity" | \
grep '^Root hash:' | cut -f2 | tr -d '\n' >"$oldinitdir/usr/share/minimal_1.roothash"
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/script0.sh
+TemporaryFileSystem=/var/lib
+StateDirectory=app0
+RuntimeDirectory=app0
EOF
cat >"$initdir/opt/script0.sh" <<EOF
#!/bin/bash
set -e
test -e /usr/lib/os-release
+echo bar > \${STATE_DIRECTORY}/foo
cat /usr/lib/extension-release.d/extension-release.app0
EOF
chmod +x "$initdir/opt/script0.sh"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
- mksquashfs "$initdir" "$oldinitdir/usr/share/app0.raw"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/app0.raw" -noappend
export initdir="$TESTDIR/app1"
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
- grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app1"
- echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app1"
+ grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
+ ( echo "${version_id}"
+ echo "SYSEXT_SCOPE=portable"
+ echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
+ setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/script1.sh
+StateDirectory=app1
+RuntimeDirectory=app1
EOF
cat >"$initdir/opt/script1.sh" <<EOF
#!/bin/bash
set -e
test -e /usr/lib/os-release
-cat /usr/lib/extension-release.d/extension-release.app1
+echo baz > \${STATE_DIRECTORY}/foo
+cat /usr/lib/extension-release.d/extension-release.app2
EOF
chmod +x "$initdir/opt/script1.sh"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"
- mksquashfs "$initdir" "$oldinitdir/usr/share/app1.raw"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/app1.raw" -noappend
+
+ export initdir="$TESTDIR/app-nodistro"
+ mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
+ ( echo "ID=_any"
+ echo "ARCHITECTURE=_any" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-nodistro"
+ echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/app-nodistro.raw" -noappend
)
}
install_pam
install_dbus
install_fonts
+ install_locales
install_keymaps
+ install_x11_keymaps
install_terminfo
install_execs
install_fs_tools
install_modules
install_plymouth
+ install_haveged
install_debug_tools
install_ld_so_conf
install_testuser
has_user_dbus_socket && install_user_dbus
setup_selinux
strip_binaries
+ instmods veth
install_depmod_files
generate_module_dependencies
if get_bool "$IS_BUILT_WITH_ASAN"; then
mkdir -p "$initdir/usr/lib/systemd/tests/testdata/units/basic.target.wants"
ln -sf ../autorelabel.service "$initdir/usr/lib/systemd/tests/testdata/units/basic.target.wants/"
- dracut_install "${fixfiles_tools[@]}"
- dracut_install fixfiles
- dracut_install sestatus
+ image_install "${fixfiles_tools[@]}"
+ image_install fixfiles
+ image_install sestatus
}
install_valgrind() {
local valgrind_bins valgrind_libs valgrind_dbg_and_supp
- valgrind_bins="$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/')"
- dracut_install "$valgrind_bins"
+ readarray -t valgrind_bins < <(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/')
+ image_install "${valgrind_bins[@]}"
- valgrind_libs="$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}')"
- dracut_install "$valgrind_libs"
+ readarray -t valgrind_libs < <(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}')
+ image_install "${valgrind_libs[@]}"
- valgrind_dbg_and_supp="$(
+ readarray -t valgrind_dbg_and_supp < <(
strace -e open valgrind /bin/true 2>&1 >/dev/null |
perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }'
- )"
- dracut_install "$valgrind_dbg_and_supp"
+ )
+ image_install "${valgrind_dbg_and_supp[@]}"
}
create_valgrind_wrapper() {
#!/usr/bin/env bash
mount -t proc proc /proc
-exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@"
+exec valgrind --leak-check=full --track-fds=yes --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@"
EOF
chmod 0755 "$valgrind_wrapper"
}
# clang: install llvm-symbolizer to generate useful reports
# See: https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
- [[ "$ASAN_COMPILER" == "clang" ]] && dracut_install "llvm-symbolizer"
+ [[ "$ASAN_COMPILER" == "clang" ]] && image_install "llvm-symbolizer"
cat >"$asan_wrapper" <<EOF
#!/usr/bin/env bash
DEFAULT_UBSAN_OPTIONS=${UBSAN_OPTIONS:-print_stacktrace=1:print_summary=1:halt_on_error=1}
DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS"
+# Create a simple environment file which can be included by systemd services
+# that need it (i.e. services that utilize DynamicUser=true and bash, etc.)
+cat >/usr/lib/systemd/systemd-asan-env <<INNER_EOF
+LD_PRELOAD=$ASAN_RT_PATH
+ASAN_OPTIONS=$DEFAULT_ASAN_OPTIONS
+LSAN_OPTIONS=detect_leaks=0
+UBSAN_OPTIONS=$DEFAULT_UBSAN_OPTIONS
+INNER_EOF
+
# As right now bash is the PID 1, we can't expect PATH to have a sane value.
# Let's make one to prevent unexpected "<bin> not found" issues in the future
export PATH="/sbin:/bin:/usr/sbin:/usr/bin"
mount -t sysfs sysfs /sys
mount -o remount,rw /
-# A lot of services (most notably dbus) won't start without preloading libasan
-# See https://github.com/systemd/systemd/issues/5004
-DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT LD_PRELOAD=$ASAN_RT_PATH"
+DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT ASAN_RT_PATH=$ASAN_RT_PATH"
if [[ "$ASAN_COMPILER" == "clang" ]]; then
# Let's add the ASan DSO's path to the dynamic linker's cache. This is pretty
mkdir -p /etc/systemd/system/systemd-journal-flush.service.d
printf "[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-journal-flush.service.d/timeout.conf
-# D-Bus has troubles during system shutdown causing it to fail. Although it's
-# harmless, it causes unnecessary noise in the logs, so let's disable LSan's
-# at_exit check just for the dbus.service
-mkdir -p /etc/systemd/system/dbus.service.d
-printf "[Service]\nEnvironment=ASAN_OPTIONS=leak_check_at_exit=false\n" >/etc/systemd/system/dbus.service.d/disable-lsan.conf
-
-# Some utilities run via IMPORT/RUN/PROGRAM udev directives fail because
-# they're uninstrumented (like dmsetup). Let's add a simple rule which sets
-# LD_PRELOAD to the ASan RT library to fix this.
-mkdir -p /etc/udev/rules.d
-cat >/etc/udev/rules.d/00-set-LD_PRELOAD.rules <<INNER_EOF
-SUBSYSTEM=="block", ENV{LD_PRELOAD}="$ASAN_RT_PATH"
-INNER_EOF
-chmod 0644 /etc/udev/rules.d/00-set-LD_PRELOAD.rules
-
-# The 'mount' utility doesn't behave well under libasan, causing unexpected
-# fails during boot and subsequent test results check:
-# bash-5.0# mount -o remount,rw -v /
-# mount: /dev/sda1 mounted on /.
-# bash-5.0# echo \$?
-# 1
-# Let's workaround this by clearing the previously set LD_PRELOAD env variable,
-# so the libasan library is not loaded for this particular service
-unset_ld_preload() {
- local _dropin_dir="/etc/systemd/system/\$1.service.d"
- mkdir -p "\$_dropin_dir"
- printf "[Service]\nUnsetEnvironment=LD_PRELOAD\n" >"\$_dropin_dir/unset_ld_preload.conf"
-}
-
-unset_ld_preload systemd-remount-fs
-unset_ld_preload testsuite-
-
export ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS
exec "$ROOTLIBDIR/systemd" "\$@"
EOF
install_fs_tools() {
dinfo "Install fsck"
- dracut_install /sbin/fsck*
- dracut_install -o /bin/fsck*
+ image_install /sbin/fsck*
+ image_install -o /bin/fsck*
# fskc.reiserfs calls reiserfsck. so, install it
- dracut_install -o reiserfsck
+ image_install -o reiserfsck
# we use mkfs in system-repart tests
- dracut_install /sbin/mkfs.ext4
- dracut_install /sbin/mkfs.vfat
+ image_install /sbin/mkfs.ext4
+ image_install /sbin/mkfs.vfat
}
install_modules() {
instmods vfat
instmods nls_ascii =nls
instmods dummy
+ # for TEST-35-LOGIN
+ instmods scsi_debug uinput
if get_bool "$LOOKS_LIKE_SUSE"; then
instmods ext4
install_dmevent() {
instmods dm_crypt =crypto
inst_binary dmeventd
+ image_install "${ROOTLIBDIR:?}"/system/dm-event.{service,socket}
if get_bool "$LOOKS_LIKE_DEBIAN"; then
# dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu
# and since buster/bionic 95-dm-notify.rules
fi
}
+install_multipath() {
+ instmods "=md" multipath
+ image_install kpartx /lib/udev/kpartx_id lsmod mpathpersist multipath multipathd partx
+ image_install "${ROOTLIBDIR:?}"/system/multipathd.{service,socket}
+ if get_bool "$LOOKS_LIKE_DEBIAN"; then
+ inst_rules 56-dm-parts.rules 56-dm-mpath.rules 60-multipath.rules 68-del-part-nodes.rules 95-kpartx.rules
+ else
+ inst_rules 11-dm-mpath.rules 11-dm-parts.rules 62-multipath.rules 66-kpartx.rules 68-del-part-nodes.rules
+ fi
+ mkdir -p "${initdir:?}/etc/multipath"
+
+ local file
+ while read -r file; do
+ # Install libraries required by the given library
+ inst_libs "$file"
+ # Install the library itself and create necessary symlinks
+ inst_library "$file"
+ done < <(find /lib*/multipath -type f)
+}
+
+install_lvm() {
+ image_install lvm
+ image_install "${ROOTLIBDIR:?}"/system/lvm2-lvmpolld.{service,socket}
+ image_install "${ROOTLIBDIR:?}"/system/{blk-availability,lvm2-monitor}.service
+ image_install -o "/lib/tmpfiles.d/lvm2.conf"
+ if get_bool "$LOOKS_LIKE_DEBIAN"; then
+ inst_rules 56-lvm.rules 69-lvm-metad.rules
+ else
+ # Support the new udev autoactivation introduced in lvm 2.03.14
+ # https://sourceware.org/git/?p=lvm2.git;a=commit;h=67722b312390cdab29c076c912e14bd739c5c0f6
+ # Static autoactivation (via lvm2-activation-generator) was dropped
+ # in lvm 2.03.15
+ # https://sourceware.org/git/?p=lvm2.git;a=commit;h=ee8fb0310c53ed003a43b324c99cdfd891dd1a7c
+ if [[ -f /lib/udev/rules.d/69-dm-lvm.rules ]]; then
+ inst_rules 11-dm-lvm.rules 69-dm-lvm.rules
+ else
+ image_install "${ROOTLIBDIR:?}"/system-generators/lvm2-activation-generator
+ image_install "${ROOTLIBDIR:?}"/system/lvm2-pvscan@.service
+ inst_rules 11-dm-lvm.rules 69-dm-lvm-metad.rules
+ fi
+ fi
+ mkdir -p "${initdir:?}/etc/lvm"
+}
+
+install_btrfs() {
+ instmods btrfs
+ # Not all utilities provided by btrfs-progs are listed here; extend the list
+ # if necessary
+ image_install btrfs btrfstune mkfs.btrfs
+ inst_rules 64-btrfs-dm.rules
+}
+
+install_iscsi() {
+ # Install both client and server side stuff by default
+ local inst="${1:-}"
+ local file
+
+ # Install client-side stuff ("initiator" in iSCSI jargon) - Open-iSCSI in this case
+ # (open-iscsi on Debian, iscsi-initiator-utils on Fedora, etc.)
+ if [[ -z "$inst" || "$inst" =~ (client|initiator) ]]; then
+ image_install iscsi-iname iscsiadm iscsid iscsistart
+ image_install -o "${ROOTLIBDIR:?}"/system/iscsi-{init,onboot,shutdown}.service
+ image_install "${ROOTLIBDIR:?}"/system/iscsid.{service,socket}
+ image_install "${ROOTLIBDIR:?}"/system/iscsi.service
+ mkdir -p "${initdir:?}"/var/lib/iscsi/{ifaces,isns,nodes,send_targets,slp,static}
+ mkdir -p "${initdir:?}/etc/iscsi"
+ echo "iscsid.startup = /bin/systemctl start iscsid.socket" >"${initdir:?}/etc/iscsi/iscsid.conf"
+ # Since open-iscsi 2.1.2 [0] the initiator name should be generated via
+ # a one-time service instead of distro package's post-install scripts.
+ # However, some distros still use this approach even after this patch,
+ # so prefer the already existing initiatorname.iscsi file if it exists.
+ #
+ # [0] https://github.com/open-iscsi/open-iscsi/commit/f37d5b653f9f251845db3f29b1a3dcb90ec89731
+ if [[ ! -e /etc/iscsi/initiatorname.iscsi ]]; then
+ image_install "${ROOTLIBDIR:?}"/system/iscsi-init.service
+ if get_bool "$IS_BUILT_WITH_ASAN"; then
+ # The iscsi-init.service calls `sh` which might, in certain circumstances,
+ # pull in instrumented systemd NSS modules causing `sh` to fail. Let's mitigate
+ # this by pulling in an env file crafted by `create_asan_wrapper()` that
+ # (among others) pre-loads ASan's DSO.
+ mkdir -p "${initdir:?}/etc/systemd/system/iscsi-init.service.d/"
+ printf "[Service]\nEnvironmentFile=/usr/lib/systemd/systemd-asan-env" >"${initdir:?}/etc/systemd/system/iscsi-init.service.d/asan-env.conf"
+ fi
+ else
+ inst_simple "/etc/iscsi/initiatorname.iscsi"
+ fi
+ fi
+
+ # Install server-side stuff ("target" in iSCSI jargon) - TGT in this case
+ # (tgt on Debian, scsi-target-utils on Fedora, etc.)
+ if [[ -z "$inst" || "$inst" =~ (server|target) ]]; then
+ image_install tgt-admin tgt-setup-lun tgtadm tgtd tgtimg
+ image_install -o /etc/sysconfig/tgtd
+ image_install "${ROOTLIBDIR:?}"/system/tgtd.service
+ mkdir -p "${initdir:?}/etc/tgt"
+ touch "${initdir:?}"/etc/tgt/{tgtd,targets}.conf
+ # Install perl modules required by tgt-admin
+ #
+ # Forgive me father for I have sinned. The monstrosity below appends
+ # a perl snippet to the `tgt-admin` perl script on the fly, which
+ # dumps a list of files (perl modules) required by `tgt-admin` at
+ # the runtime plus any DSOs loaded via DynaLoader. This list is then
+ # passed to `inst_simple` which installs the necessary files into the image
+ #
+ # shellcheck disable=SC2016
+ while read -r file; do
+ inst_simple "$file"
+ 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 }')
+ fi
+}
+
+install_mdadm() {
+ local unit
+ local mdadm_units=(
+ system/mdadm-grow-continue@.service
+ system/mdadm-last-resort@.service
+ system/mdadm-last-resort@.timer
+ system/mdmon@.service
+ system/mdmonitor-oneshot.service
+ system/mdmonitor-oneshot.timer
+ system/mdmonitor.service
+ system-shutdown/mdadm.shutdown
+ )
+
+ image_install mdadm mdmon
+ inst_rules 01-md-raid-creating.rules 63-md-raid-arrays.rules 64-md-raid-assembly.rules 69-md-clustered-confirm-device.rules
+ # Fedora/CentOS/RHEL ships this rule file
+ [[ -f /lib/udev/rules.d/65-md-incremental.rules ]] && inst_rules 65-md-incremental.rules
+
+ for unit in "${mdadm_units[@]}"; do
+ image_install "${ROOTLIBDIR:?}/$unit"
+ done
+}
+
install_compiled_systemd() {
dinfo "Install compiled systemd"
exit 1
fi
(set -x; DESTDIR="$initdir" "$ninja_bin" -C "$BUILD_DIR" install)
+
+ # If we are doing coverage runs, copy over the binary notes files, as lcov expects to
+ # find them in the same directory as the runtime data counts
+ if get_bool "$IS_BUILT_WITH_COVERAGE"; then
+ mkdir -p "${initdir}/${BUILD_DIR:?}/"
+ rsync -am --include='*/' --include='*.gcno' --exclude='*' "${BUILD_DIR:?}/" "${initdir}/${BUILD_DIR:?}/"
+ # Set effective & default ACLs for the build dir so unprivileged
+ # processes can write gcda files with coverage stats
+ setfacl -R -m 'd:o:rwX' -m 'o:rwX' "${initdir}/${BUILD_DIR:?}/"
+ fi
}
install_debian_systemd() {
done < <(grep -E '^Package:' "${SOURCE_DIR}/debian/control" | cut -d ':' -f 2)
}
+install_suse_systemd() {
+ local testsdir=/usr/lib/systemd/tests
+ local pkgs
+
+ dinfo "Install SUSE systemd"
+
+ pkgs=(
+ systemd
+ systemd-container
+ systemd-coredump
+ systemd-experimental
+ systemd-journal-remote
+ systemd-portable
+ udev
+ )
+
+ for p in "${pkgs[@]}"; do
+ rpm -q "$p" &>/dev/null || continue
+
+ ddebug "Install files from package $p"
+ while read -r f; do
+ [ -e "$f" ] || continue
+ [ -d "$f" ] && continue
+ inst "$f"
+ done < <(rpm -ql "$p")
+ done
+
+ # we only need testsdata dir as well as the unit tests (for
+ # TEST-02-UNITTESTS) in the image.
+ dinfo "Install unit tests and testdata directory"
+
+ mkdir -p "$initdir/$testsdir"
+ cp "$testsdir"/test-* "$initdir/$testsdir/"
+ cp -a "$testsdir/testdata" "$initdir/$testsdir/"
+
+ # On openSUSE, these dirs are not created at package install for now on.
+ mkdir -p "$initdir/var/log/journal/remote"
+}
+
install_distro_systemd() {
dinfo "Install distro systemd"
if get_bool "$LOOKS_LIKE_DEBIAN"; then
install_debian_systemd
+ elif get_bool "$LOOKS_LIKE_SUSE"; then
+ install_suse_systemd
else
dfatal "NO_BUILD not supported for this distro"
exit 1
fi
# remove unneeded documentation
- rm -fr "$initdir"/usr/share/{man,doc}
-
- get_bool "$LOOKS_LIKE_SUSE" && setup_suse
+ rm -fr "${initdir:?}"/usr/share/{man,doc}
# enable debug logging in PID1
echo LogLevel=debug >>"$initdir/etc/systemd/system.conf"
+ if [[ -n "$TEST_SYSTEMD_LOG_LEVEL" ]]; then
+ echo DefaultEnvironment=SYSTEMD_LOG_LEVEL="$TEST_SYSTEMD_LOG_LEVEL" >>"$initdir/etc/systemd/system.conf"
+ fi
# store coredumps in journal
echo Storage=journal >>"$initdir/etc/systemd/coredump.conf"
+ # Propagate SYSTEMD_UNIT_PATH to user systemd managers
+ mkdir "$initdir/etc/systemd/system/user@.service.d/"
+ echo -e "[Service]\nPassEnvironment=SYSTEMD_UNIT_PATH\n" >"$initdir/etc/systemd/system/user@.service.d/override.conf"
+
+ # When built with gcov, disable ProtectSystem= and ProtectHome= in the test
+ # images, since it prevents gcov to write the coverage reports (*.gcda
+ # files)
+ if get_bool "$IS_BUILT_WITH_COVERAGE"; then
+ mkdir -p "$initdir/etc/systemd/system/service.d/"
+ echo -e "[Service]\nProtectSystem=no\nProtectHome=no\n" >"$initdir/etc/systemd/system/service.d/99-gcov-override.conf"
+ # Similarly, set ReadWritePaths= to the $BUILD_DIR in the test image
+ # to make the coverage work with units utilizing DynamicUser=yes. Do
+ # this only for services from TEST-20, as setting this system-wide
+ # has many undesirable side-effects
+ mkdir -p "$initdir/etc/systemd/system/test20-.service.d/"
+ echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/test20-.service.d/99-gcov-rwpaths-override.conf"
+ fi
+
+ # If we're built with -Dportabled=false, tests with systemd-analyze
+ # --profile will fail. Since we need just the profile (text) files, let's
+ # copy them into the image if they don't exist there.
+ local portable_dir="${initdir:?}${ROOTLIBDIR:?}/portable"
+ if [[ ! -d "$portable_dir/profile/strict" ]]; then
+ dinfo "Couldn't find portable profiles in the test image"
+ dinfo "Copying them directly from the source tree"
+ mkdir -p "$portable_dir"
+ cp -frv "${SOURCE_DIR:?}/src/portable/profile" "$portable_dir"
+ fi
}
get_ldpath() {
LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath "$i")" inst_libs "$i"
done
+ # Install libgcc_s.so if available, since it's dlopen()ed by libpthread
+ # and might cause unexpected failures during pthread_exit()/pthread_cancel()
+ # if not present
+ # See: https://github.com/systemd/systemd/pull/23858
+ while read -r libgcc_s; do
+ [[ -e "$libgcc_s" ]] && inst_library "$libgcc_s"
+ done < <(ldconfig -p | awk '/\/libgcc_s.so.1$/ { print $4 }')
+
local lib path
# A number of dependencies is now optional via dlopen, so the install
# script will not pick them up, since it looks at linkage.
- for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu libfido2 libbpf; do
+ for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw xkbcommon; do
ddebug "Searching for $lib via pkg-config"
if pkg-config --exists "$lib"; then
path="$(pkg-config --variable=libdir "$lib")"
# (eg: libcryptsetup), so just ignore them
inst_libs "${path}/${lib}.so" || true
inst_library "${path}/${lib}.so" || true
+
+ if [[ "$lib" == "libxkbcommon" ]]; then
+ install_x11_keymaps full
+ fi
else
ddebug "$lib.pc not found, skipping"
continue
fi
done
+
+ # Install extra openssl 3 stuff
+ path="$(pkg-config --variable=libdir libcrypto)"
+ inst_simple "${path}/ossl-modules/legacy.so" || true
+ inst_simple "${path}/ossl-modules/fips.so" || true
+ inst_simple "${path}/engines-3/afalg.so" || true
+ inst_simple "${path}/engines-3/capi.so" || true
+ inst_simple "${path}/engines-3/loader_attic.so" || true
+ inst_simple "${path}/engines-3/padlock.so" || true
}
cleanup_loopdev() {
fi
}
-trap cleanup_loopdev EXIT INT QUIT PIPE
+add_at_exit_handler cleanup_loopdev
create_empty_image() {
if [ -z "${IMAGE_NAME:=}" ]; then
exit 1
fi
- local size=500
+ # Partition sizes are in MiBs
+ local root_size=1000
+ local data_size=50
if ! get_bool "$NO_BUILD"; then
if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then
- size=$((size+=200))
+ root_size=$((root_size+=200))
fi
if meson configure "${BUILD_DIR:?}" | grep 'link-.*-shared' | awk '{ print $2 }' | grep -q 'false'; then
- size=$((size+=200))
+ root_size=$((root_size+=200))
+ fi
+ if get_bool "$IS_BUILT_WITH_COVERAGE"; then
+ root_size=$((root_size+=250))
fi
fi
if ! get_bool "$STRIP_BINARIES"; then
- size=$((4 * size))
+ root_size=$((4 * root_size))
+ data_size=$((2 * data_size))
fi
- echo "Setting up ${IMAGE_PUBLIC:?} (${size} MB)"
+ echo "Setting up ${IMAGE_PUBLIC:?} (${root_size} MB)"
rm -f "${IMAGE_PRIVATE:?}" "$IMAGE_PUBLIC"
# Create the blank file to use as a root filesystem
- truncate -s "${size}M" "$IMAGE_PUBLIC"
+ truncate -s "${root_size}M" "$IMAGE_PUBLIC"
LOOPDEV=$(losetup --show -P -f "$IMAGE_PUBLIC")
[ -b "$LOOPDEV" ] || return 1
+ # Create two partitions - a root one and a data one (utilized by some tests)
sfdisk "$LOOPDEV" <<EOF
-,$((size - 50))M
-,
+label: gpt
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=root size=$((root_size - data_size))M bootable
+type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=data
EOF
udevadm settle
- local label=(-L systemd)
+ local label=(-L systemd_boot)
# mkfs.reiserfs doesn't know -L. so, use --label instead
- [[ "$FSTYPE" == "reiserfs" ]] && label=(--label systemd)
+ [[ "$FSTYPE" == "reiserfs" ]] && label=(--label systemd_boot)
if ! mkfs -t "${FSTYPE}" "${label[@]}" "${LOOPDEV}p1" -q; then
dfatal "Failed to mkfs -t ${FSTYPE}"
exit 1
cleanup_initdir() {
# only umount if create_empty_image_rootdir() was called to mount it
- get_bool "$TEST_SETUP_CLEANUP_ROOTDIR" && _umount_dir "${initdir:?}"
+ if get_bool "$TEST_SETUP_CLEANUP_ROOTDIR"; then
+ _umount_dir "${initdir:?}"
+ fi
}
umount_loopback() {
BEGIN {
%services_to_ignore = (
"dbus-daemon" => undef,
+ "dbus-broker-launch" => undef,
);
}
print $2 if /\s(\S*)\[(\d+)\]:\s*SUMMARY:\s+\w+Sanitizer/ && !exists $services_to_ignore{$1}'
return $ret
}
+check_coverage_reports() {
+ local root="${1:?}"
+
+ if get_bool "$NO_BUILD"; then
+ return 0
+ fi
+ if ! get_bool "$IS_BUILT_WITH_COVERAGE"; then
+ return 0
+ fi
+
+ if [ -n "${ARTIFACT_DIRECTORY}" ]; then
+ dest="${ARTIFACT_DIRECTORY}/${testname:?}.coverage-info"
+ else
+ dest="${TESTDIR:?}/coverage-info"
+ fi
+
+ # Create a coverage report that will later be uploaded. Remove info about
+ # system libraries/headers, as we don't really care about them.
+ if [[ -f "$dest" ]]; then
+ # If the destination report file already exists, don't overwrite it, but
+ # dump the new report in a temporary file and then merge it with the already
+ # present one - this usually happens when running both "parts" of a test
+ # in one run (the qemu and the nspawn part).
+ lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
+ lcov --remove "${dest}.new" -o "${dest}.new" '/usr/include/*' '/usr/lib/*'
+ lcov --add-tracefile "${dest}" --add-tracefile "${dest}.new" -o "${dest}"
+ rm -f "${dest}.new"
+ else
+ lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}"
+ lcov --remove "${dest}" -o "${dest}" '/usr/include/*' '/usr/lib/*'
+ fi
+
+ # If the test logs contain lines like:
+ #
+ # ...systemd-resolved[735885]: profiling:/systemd-meson-build/src/shared/libsystemd-shared-250.a.p/base-filesystem.c.gcda:Cannot open
+ #
+ # it means we're possibly missing some coverage since gcov can't write the stats,
+ # usually due to the sandbox being too restrictive (e.g. ProtectSystem=yes,
+ # ProtectHome=yes) or the $BUILD_DIR being inaccessible to non-root users - see
+ # `setfacl` stuff in install_compiled_systemd().
+ if ! get_bool "${IGNORE_MISSING_COVERAGE:=}" && \
+ "${JOURNALCTL:?}" -q --no-pager -D "${root:?}/var/log/journal" --grep "profiling:.+?gcda:[Cc]annot open"; then
+ derror "Detected possibly missing coverage, check the journal"
+ return 1
+ fi
+
+ return 0
+}
+
save_journal() {
# Default to always saving journal
local save="yes"
for j in "${1:?}"/*; do
if get_bool "$save"; then
- "$SYSTEMD_JOURNAL_REMOTE" -o "$dest" --getter="$JOURNALCTL -o export -D $j"
+ if [ "$SYSTEMD_JOURNAL_REMOTE" = "" ]; then
+ cp -a "$j" "$dest"
+ else
+ "$SYSTEMD_JOURNAL_REMOTE" -o "$dest" --getter="$JOURNALCTL -o export -D $j"
+ fi
fi
if [ -n "${TEST_SHOW_JOURNAL}" ]; then
setfacl -m "user:${SUDO_USER:?}:r-X" "${TESTDIR:?}/"failed
fi
ret=1
+ elif get_bool "$TIMED_OUT"; then
+ echo "(timeout)" >"${TESTDIR:?}/failed"
+ ret=2
elif [ -e "$workspace/testok" ]; then
# …/testok always counts (but with lower priority than …/failed)
ret=0
echo "${TESTNAME:?} was skipped:"
cat "$workspace/skipped"
ret=0
- elif get_bool "$TIMED_OUT"; then
- echo "(timeout)" >"${TESTDIR:?}/failed"
- ret=2
else
echo "(failed; see logs)" >"${TESTDIR:?}/failed"
ret=3
check_asan_reports "$workspace" || ret=4
+ check_coverage_reports "$workspace" || ret=5
+
save_journal "$workspace/var/log/journal" $ret
if [ -d "${ARTIFACT_DIRECTORY}" ] && [ -f "$workspace/strace.out" ]; then
local workspace="${1:?}"
local ret
- check_result_common "${workspace}"
- ret=$?
-
- # Run additional test-specific checks if defined by check_result_nspawn_hook()
+ # Run a test-specific checks if defined by check_result_nspawn_hook()
if declare -F check_result_nspawn_hook >/dev/null; then
- if ! check_result_nspawn_hook; then
+ if ! check_result_nspawn_hook "${workspace}"; then
derror "check_result_nspawn_hook() returned with EC > 0"
ret=4
fi
fi
+ check_result_common "${workspace}"
+ ret=$?
+
_umount_dir "${initdir:?}"
return $ret
local ret
mount_initdir
- check_result_common "${initdir:?}"
- ret=$?
-
- _umount_dir "${initdir:?}"
-
- # Run additional test-specific checks if defined by check_result_qemu_hook()
+ # Run a test-specific checks if defined by check_result_qemu_hook()
if declare -F check_result_qemu_hook >/dev/null; then
- if ! check_result_qemu_hook; then
+ if ! check_result_qemu_hook "${initdir:?}"; then
derror "check_result_qemu_hook() returned with EC > 0"
ret=4
fi
fi
+ check_result_common "${initdir:?}"
+ ret=$?
+
+ _umount_dir "${initdir:?}"
+
return $ret
}
fi
get_bool "${TIMED_OUT:=}" && ret=1
+ check_coverage_reports "$workspace" || ret=5
save_journal "$workspace/var/log/journal" $ret
fi
get_bool "${TIMED_OUT:=}" && ret=1
+ check_coverage_reports "$initdir" || ret=5
save_journal "$initdir/var/log/journal" $ret
# if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then
# PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \
# /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir
- # dracut_install plymouth plymouthd
+ # image_install plymouth plymouthd
# else
rm -f "${initdir:?}"/{usr/lib,lib,etc}/systemd/system/plymouth* "$initdir"/{usr/lib,lib,etc}/systemd/system/*/plymouth*
# fi
}
+install_haveged() {
+ # If haveged is installed, it's probably included in initrd and needs to be
+ # installed in the image too.
+ if [ -x /usr/sbin/haveged ]; then
+ dinfo "Install haveged files"
+ inst /usr/sbin/haveged
+ for u in /usr/lib/systemd/system/haveged*; do
+ inst "$u"
+ done
+ fi
+}
+
install_ld_so_conf() {
dinfo "Install /etc/ld.so.conf*"
cp -a /etc/ld.so.conf* "${initdir:?}/etc"
install_basic_tools() {
dinfo "Install basic tools"
- dracut_install "${BASICTOOLS[@]}"
- dracut_install -o sushell
+ image_install "${BASICTOOLS[@]}"
+ image_install -o sushell
# in Debian ldconfig is just a shell script wrapper around ldconfig.real
- dracut_install -o ldconfig.real
+ image_install -o ldconfig.real
}
install_debug_tools() {
dinfo "Install debug tools"
- dracut_install "${DEBUGTOOLS[@]}"
+ image_install -o "${DEBUGTOOLS[@]}"
if get_bool "$INTERACTIVE_DEBUG"; then
# Set default TERM from vt220 to linux, so at least basic key shortcuts work
local getty_override="${initdir:?}/etc/systemd/system/serial-getty@.service.d"
mkdir -p "$getty_override"
echo -e "[Service]\nEnvironment=TERM=linux" >"$getty_override/default-TERM.conf"
+ echo 'export TERM=linux' >>"$initdir/etc/profile"
- cat >"$initdir/etc/motd" <<EOF
-To adjust the terminal size use:
- export COLUMNS=xx
- export LINES=yy
-or
- stty cols xx rows yy
+ if command -v resize >/dev/null; then
+ image_install resize
+ echo "resize" >>"$initdir/etc/profile"
+ fi
+
+ # Sometimes we might end up with plymouthd still running (especially
+ # with the initrd -> asan_wrapper -> systemd transition), which will eat
+ # our inputs and make debugging via tty impossible. Let's fix this by
+ # killing plymouthd explicitly for the interactive sessions.
+ # Note: we can't use pkill/pidof/etc. here due to a bug in libasan, see:
+ # - https://github.com/llvm/llvm-project/issues/49223
+ # - https://bugzilla.redhat.com/show_bug.cgi?id=2098125
+ local plymouth_unit="${initdir:?}/etc/systemd/system/kill-plymouth.service"
+ cat >"$plymouth_unit" <<EOF
+[Unit]
+After=multi-user.target
+
+[Service]
+ExecStart=sh -c 'killall --verbose plymouthd || :'
+
+[Install]
+WantedBy=multi-user.target
EOF
+ "${SYSTEMCTL:?}" enable --root "${initdir:?}" kill-plymouth.service
fi
}
# install libnss_files for login
local NSS_LIBS
mapfile -t NSS_LIBS < <(LD_DEBUG=files getent passwd 2>&1 >/dev/null | sed -n '/calling init: .*libnss_/ {s!^.* /!/!; p}')
- dracut_install "${NSS_LIBS[@]}"
+ if [[ ${#NSS_LIBS[@]} -gt 0 ]]; then
+ image_install "${NSS_LIBS[@]}"
+ fi
}
install_dbus() {
cat >"$initdir/etc/dbus-1/system.d/systemd.test.ExecStopPost.conf" <<EOF
<?xml version="1.0"?>
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
- "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="systemd.test.ExecStopPost"/>
</policy>
</busconfig>
EOF
+
+ # If we run without KVM, bump the service start timeout
+ if ! get_bool "$QEMU_KVM"; then
+ cat >"$initdir/etc/dbus-1/system.d/service.timeout.conf" <<EOF
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <limit name="service_start_timeout">60000</limit>
+</busconfig>
+EOF
+ # Bump the client-side timeout in sd-bus as well
+ mkdir -p "$initdir/etc/systemd/system.conf.d"
+ echo -e '[Manager]\nDefaultEnvironment=SYSTEMD_BUS_TIMEOUT=60' >"$initdir/etc/systemd/system.conf.d/bus-timeout.conf"
+ fi
}
install_user_dbus() {
paths+=(/lib*/security)
fi
- for d in /etc/pam.d /etc/security /usr/lib/pam.d; do
+ for d in /etc/pam.d /{usr/,}etc/security /usr/{etc,lib}/pam.d; do
[ -d "$d" ] && paths+=("$d")
done
# pam_unix depends on unix_chkpwd.
# see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html
- dracut_install -o unix_chkpwd
+ image_install -o unix_chkpwd
# set empty root password for easy debugging
sed -i 's/^root:x:/root::/' "${initdir:?}/etc/passwd"
+
+ # And make sure pam_unix will accept it by making sure that
+ # the PAM module has the nullok option.
+ for d in /etc/pam.d /usr/{etc,lib}/pam.d; do
+ [ -d "$initdir/$d" ] || continue
+ sed -i '/^auth.*pam_unix.so/s/$/ nullok/' "$initdir/$d"/*
+ done
}
+install_locales() {
+ # install only C.UTF-8 and English locales
+ dinfo "Install locales"
+
+ if command -v meson >/dev/null \
+ && (meson configure "${BUILD_DIR:?}" | grep 'localegen-path */') \
+ || get_bool "$LOOKS_LIKE_DEBIAN"; then
+ # locale-gen support
+ image_install -o locale-gen localedef
+ inst /etc/locale.gen || :
+ inst /usr/share/i18n/SUPPORTED || :
+ inst_recursive /usr/share/i18n/charmaps
+ inst_recursive /usr/share/i18n/locales
+ inst_recursive /usr/share/locale/en*
+ inst_recursive /usr/share/locale/de*
+ image_install /usr/share/locale/locale.alias
+ # locale-gen might either generate each locale separately or merge them
+ # into a single archive
+ if ! (inst_recursive /usr/lib/locale/C.*8 /usr/lib/locale/en_*8 ||
+ image_install /usr/lib/locale/locale-archive); then
+ dfatal "Failed to install required locales"
+ exit 1
+ fi
+ else
+ inst_recursive /usr/lib/locale/C.*8 /usr/lib/locale/en_*8
+ fi
+}
+
+# shellcheck disable=SC2120
install_keymaps() {
- dinfo "Install keymaps"
- # The first three paths may be deprecated.
- # It seems now the last two paths are used by many distributions.
- for i in \
- /usr/lib/kbd/keymaps/include/* \
- /usr/lib/kbd/keymaps/i386/include/* \
- /usr/lib/kbd/keymaps/i386/qwerty/us.* \
- /usr/lib/kbd/keymaps/legacy/include/* \
- /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do
- [[ -f "$i" ]] || continue
- inst "$i"
- done
+ local i p
+ local -a prefix=(
+ "/usr/lib"
+ "/usr/share"
+ )
- # When it takes any argument, then install more keymaps.
- if [[ $# -gt 1 ]]; then
- for i in \
- /usr/lib/kbd/keymaps/i386/*/* \
- /usr/lib/kbd/keymaps/legacy/i386/*/*; do
- [[ -f "$i" ]] || continue
- inst "$i"
+ dinfo "Install console keymaps"
+
+ if command -v meson >/dev/null \
+ && [[ "$(meson configure "${BUILD_DIR:?}" | grep 'split-usr' | awk '{ print $2 }')" == "true" ]] \
+ || [[ ! -L /lib ]]; then
+ prefix+=(
+ "/lib"
+ )
+ fi
+
+ if (( $# == 0 )); then
+ for p in "${prefix[@]}"; do
+ # The first three paths may be deprecated.
+ # It seems now the last three paths are used by many distributions.
+ for i in \
+ "$p"/kbd/keymaps/include/* \
+ "$p"/kbd/keymaps/i386/include/* \
+ "$p"/kbd/keymaps/i386/qwerty/us.* \
+ "$p"/kbd/keymaps/legacy/include/* \
+ "$p"/kbd/keymaps/legacy/i386/qwerty/us.* \
+ "$p"/kbd/keymaps/xkb/us*; do
+ [[ -f "$i" ]] || continue
+ inst "$i"
+ done
+ done
+ else
+ # When it takes any argument, then install more keymaps.
+ for p in "${prefix[@]}"; do
+ for i in \
+ "$p"/kbd/keymaps/include/* \
+ "$p"/kbd/keymaps/i386/*/* \
+ "$p"/kbd/keymaps/legacy/i386/*/* \
+ "$p"/kbd/keymaps/xkb/*; do
+ [[ -f "$i" ]] || continue
+ inst "$i"
+ done
done
fi
}
+install_x11_keymaps() {
+ dinfo "Install x11 keymaps"
+
+ if (( $# == 0 )); then
+ # Install only keymap list.
+ inst /usr/share/X11/xkb/rules/base.lst
+ else
+ # When it takes any argument, then install all keymaps.
+ inst_recursive /usr/share/X11/xkb
+ fi
+}
+
install_zoneinfo() {
dinfo "Install time zones"
inst_any /usr/share/zoneinfo/Asia/Seoul
for terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do
[ -f "${terminfodir}/l/linux" ] && break
done
- dracut_install -o "${terminfodir}/l/linux"
+ image_install -o "${terminfodir}/l/linux"
}
has_user_dbus_socket() {
fi
}
+setup_nspawn_root_hook() { :;}
+
setup_nspawn_root() {
if [ -z "${initdir}" ]; then
dfatal "\$initdir not defined"
ddebug "cp -ar $initdir $TESTDIR/unprivileged-nspawn-root"
cp -ar "$initdir" "$TESTDIR/unprivileged-nspawn-root"
fi
+
+ setup_nspawn_root_hook
}
setup_basic_dirs() {
while read -r line; do
[[ "$line" = 'not a dynamic executable' ]] && break
+ # Ignore errors about our own stuff missing. This is most likely caused
+ # by ldd attempting to use the unprefixed RPATH.
+ [[ "$line" =~ libsystemd.*\ not\ found ]] && continue
if [[ "$line" =~ $so_regex ]]; then
file="${BASH_REMATCH[1]}"
dfatal "Missing a shared library required by $bin."
dfatal "Run \"ldd $bin\" to find out what it is."
dfatal "$line"
- dfatal "dracut cannot create an initrd."
+ dfatal "Cannot create a test image."
exit 1
fi
done < <(LC_ALL=C ldd "$bin" 2>/dev/null)
export initdir
}
+get_cgroup_hierarchy() {
+ case "$(stat -c '%T' -f /sys/fs/cgroup)" in
+ cgroup2fs)
+ echo "unified"
+ ;;
+ tmpfs)
+ if [[ -d /sys/fs/cgroup/unified && "$(stat -c '%T' -f /sys/fs/cgroup/unified)" == cgroup2fs ]]; then
+ echo "hybrid"
+ else
+ echo "legacy"
+ fi
+ ;;
+ *)
+ dfatal "Failed to determine host's cgroup hierarchy"
+ exit 1
+ esac
+}
+
## @brief Converts numeric logging level to the first letter of level name.
#
# @param lvl Numeric logging level in range from 1 to 6.
# Generic substring function. If $2 is in $1, return 0.
-strstr() { [ "${1#*$2*}" != "$1" ]; }
+strstr() { [ "${1#*"$2"*}" != "$1" ]; }
# normalize_path <path>
# Prints the normalized path, where it removes any duplicated
__abssize=${#__absolute[@]}
__cursize=${#__current[@]}
- while [[ "${__absolute[__level]}" == "${__current[__level]}" ]]
- do
+ while [[ "${__absolute[__level]}" == "${__current[__level]}" ]]; do
(( __level++ ))
if (( __level > __abssize || __level > __cursize ))
then
fi
done
- for ((__i = __level; __i < __cursize-1; __i++))
- do
+ for ((__i = __level; __i < __cursize-1; __i++)); do
if ((__i > __level))
then
__newpath=$__newpath"/"
__newpath=$__newpath".."
done
- for ((__i = __level; __i < __abssize; __i++))
- do
+ for ((__i = __level; __i < __abssize; __i++)); do
if [[ -n $__newpath ]]
then
__newpath=$__newpath"/"
inst_simple "$reallib" "$reallib"
inst_dir "${dest%/*}"
[[ -d "${dest%/*}" ]] && dest="$(readlink -f "${dest%/*}")/${dest##*/}"
+ ddebug "Creating symlink $reallib -> $dest"
ln -sfn -- "$(convert_abs_rel "${dest}" "${reallib}")" "${initdir}/${dest}"
else
inst_simple "$src" "$dest"
# In certain cases we might attempt to install a binary which is already
# present in the test image, yet it's missing from the host system.
# In such cases, let's check if the binary indeed exists in the image
- # before doing any other chcecks. If it does, immediately return with
+ # before doing any other checks. If it does, immediately return with
# success.
if [[ $# -eq 1 ]]; then
for path in "" bin sbin usr/bin usr/sbin; do
local file line
local so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
+ # DSOs provided by systemd
+ local systemd_so_regex='/(libudev|libsystemd.*|.+[\-_]systemd([\-_].+)?|libnss_(mymachines|myhostname|resolve)).so'
+ local wrap_binary=0
# I love bash!
while read -r line; do
[[ "$line" = 'not a dynamic executable' ]] && break
+ # Ignore errors about our own stuff missing. This is most likely caused
+ # by ldd attempting to use the unprefixed RPATH.
+ [[ "$line" =~ libsystemd.*\ not\ found ]] && continue
+
+ # We're built with ASan and the target binary loads one of the systemd's
+ # DSOs, so we need to tweak the environment before executing the binary
+ if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$line" =~ $systemd_so_regex ]]; then
+ wrap_binary=1
+ fi
+
if [[ "$line" =~ $so_regex ]]; then
file="${BASH_REMATCH[1]}"
[[ -e "${initdir}/$file" ]] && continue
dfatal "Missing a shared library required by $bin."
dfatal "Run \"ldd $bin\" to find out what it is."
dfatal "$line"
- dfatal "dracut cannot create an initrd."
+ dfatal "Cannot create a test image."
exit 1
fi
done < <(LC_ALL=C ldd "$bin" 2>/dev/null)
- inst_simple "$bin" "$target"
+
+ # Same as above, but we need to wrap certain libraries unconditionally
+ #
+ # chown, getent, login, su, useradd, userdel - dlopen()s (not only) systemd's PAM modules
+ # ls, stat - pulls in nss_systemd with certain options (like ls -l) when
+ # nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux)
+ # tar - called by machinectl in TEST-25
+ if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$bin" =~ /(chown|getent|login|ls|stat|su|tar|useradd|userdel)$ ]]; then
+ wrap_binary=1
+ fi
+
+ # If the target binary is built with ASan support, we don't need to wrap
+ # it, as it should handle everything by itself
+ if get_bool "$wrap_binary" && ! is_built_with_asan "$bin"; then
+ dinfo "Creating ASan-compatible wrapper for binary '$target'"
+ # Install the target binary with a ".orig" suffix
+ inst_simple "$bin" "${target}.orig"
+ # Create a simple shell wrapper in place of the target binary, which
+ # sets necessary ASan-related env variables and then exec()s the
+ # suffixed target binary
+ cat >"$initdir/$target" <<EOF
+#!/bin/bash
+# Preload the ASan runtime DSO, otherwise ASAn will complain
+export LD_PRELOAD="$ASAN_RT_PATH"
+# Disable LSan to speed things up, since we don't care about leak reports
+# from 'external' binaries
+export ASAN_OPTIONS=detect_leaks=0
+# Set argv[0] to the original binary name without the ".orig" suffix
+exec -a "\$0" -- "${target}.orig" "\$@"
+EOF
+ chmod +x "$initdir/$target"
+ else
+ inst_simple "$bin" "$target"
+ fi
}
# same as above, except for shell scripts.
fi
#dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)"
- dracut_install "$bin"
+ image_install "$bin"
done
}
for fun in inst_symlink inst_script inst_binary inst_simple; do
"$fun" "$@" && return 0
done
+
+ dwarn "Failed to install '$1'"
return 1
}
# inst_any -d /bin/foo /bin/bar /bin/baz
#
# Lets assume that /bin/baz exists, so it will be installed as /bin/foo in
-# initramfs.
+# initrd.
inst_any() {
local dest file
return 1
}
-# dracut_install [-o ] <file> [<file> ... ]
-# Install <file> to the initramfs image
+inst_recursive() {
+ local p item
+
+ for p in "$@"; do
+ # Make sure the source exists, as the process substitution below
+ # suppresses errors
+ stat "$p" >/dev/null || return 1
+
+ while read -r item; do
+ if [[ -d "$item" ]]; then
+ inst_dir "$item"
+ elif [[ -f "$item" ]]; then
+ inst_simple "$item"
+ fi
+ done < <(find "$p" 2>/dev/null)
+ done
+}
+
+# image_install [-o ] <file> [<file> ... ]
+# Install <file> to the test image
# -o optionally install the <file> and don't fail, if it is not there
-dracut_install() {
+image_install() {
local optional=no
local prog="${1:?}"
install_kmod_with_fw() {
local module="${1:?}"
# no need to go further if the module is already installed
- [[ -e "${initdir:?}/lib/modules/${KERNEL_VER:?}/${module##*/lib/modules/$KERNEL_VER/}" ]] && return 0
+ [[ -e "${initdir:?}/lib/modules/${KERNEL_VER:?}/${module##*"/lib/modules/$KERNEL_VER/"}" ]] && return 0
[[ -e "$initdir/.kernelmodseen/${module##*/}" ]] && return 0
[ -d "$initdir/.kernelmodseen" ] && : >"$initdir/.kernelmodseen/${module##*/}"
- inst_simple "$module" "/lib/modules/$KERNEL_VER/${module##*/lib/modules/$KERNEL_VER/}" || return $?
+ inst_simple "$module" "/lib/modules/$KERNEL_VER/${module##*"/lib/modules/$KERNEL_VER/"}" || return $?
local modname="${module##*/}"
local fwdir found fw
else
(
[[ "$mpargs" ]] && echo "$mpargs"
- find "$mod_dir" -path "*/${mod#=}/*" -type f -printf '%f\n'
+ find "$mod_dir" -path "*/${mod#=}/*" -name "*.ko*" -type f -printf '%f\n'
) | instmods
fi
;;
return 0
}
-setup_suse() {
- ln -fs ../usr/bin/systemctl "${initdir:?}/bin/"
- ln -fs ../usr/lib/systemd "$initdir/lib/"
- inst_simple "/usr/lib/systemd/system/haveged.service"
-}
-
_umount_dir() {
local mountpoint="${1:?}"
if mountpoint -q "$mountpoint"; then
[[ -n "$initdir" ]] && _umount_dir "$initdir"
[[ -n "$IMAGE_PUBLIC" ]] && rm -vf "$IMAGE_PUBLIC"
# If multiple setups/cleans are ran in parallel, this can cause a race
- if [[ -n "$IMAGESTATEDIR" && $TEST_PARALLELIZE -ne 1 ]]; then
+ if [[ -n "$IMAGESTATEDIR" && $TEST_PARALLELIZE -ne 1 ]]; then
rm -vf "${IMAGESTATEDIR}/default.img"
fi
[[ -n "$TESTDIR" ]] && rm -vfr "$TESTDIR"
fi
mount_initdir
- # We want to test all services in TEST-01-BASIC, but mask them in
- # all other tests
- if [[ "${TESTID:?}" != "01" ]]; then
+
+ if get_bool "${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED}"; then
dinfo "Masking supporting services"
mask_supporting_services
fi
+ # Send stdout/stderr of testsuite-*.service units to both journal and
+ # console to make debugging in CIs easier
+ # Note: we can't use a dropin for `testsuite-.service`, since that also
+ # overrides 'sub-units' of some tests that already use a specific
+ # value for Standard(Output|Error)=
+ # (e.g. test/units/testsuite-66-deviceisolation.service)
+ if ! get_bool "$INTERACTIVE_DEBUG"; then
+ local dropin_dir="${initdir:?}/etc/systemd/system/testsuite-${TESTID:?}.service.d"
+ mkdir -p "$dropin_dir"
+ printf '[Service]\nStandardOutput=journal+console\nStandardError=journal+console' >"$dropin_dir/99-stdout.conf"
+ fi
+
if get_bool "$hook_defined"; then
test_append_files "${initdir:?}"
fi
if ! get_bool "${TEST_NO_QEMU:=}"; then
if run_qemu "$test_id"; then
- check_result_qemu || { echo "QEMU test failed"; return 1; }
+ check_result_qemu || { echo "qemu test failed"; return 1; }
else
- dwarn "can't run QEMU, skipping"
+ dwarn "can't run qemu, skipping"
fi
fi
if ! get_bool "${TEST_NO_NSPAWN:=}"; then
fi
if get_bool "${TEST_NO_QEMU:=}" && get_bool "${TEST_NO_NSPAWN:=}"; then
- echo "TEST: $TEST_DESCRIPTION [SKIPPED]: both QEMU and nspawn disabled" >&2
+ echo "TEST: $TEST_DESCRIPTION [SKIPPED]: both qemu and nspawn disabled" >&2
exit 0
fi
if get_bool "${TEST_QEMU_ONLY:=}" && ! get_bool "$TEST_NO_NSPAWN"; then
- echo "TEST: $TEST_DESCRIPTION [SKIPPED]: QEMU-only tests requested" >&2
+ echo "TEST: $TEST_DESCRIPTION [SKIPPED]: qemu-only tests requested" >&2
exit 0
fi