PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH
-LOOKS_LIKE_DEBIAN=$(source /etc/os-release && [[ "$ID" = "debian" || " $ID_LIKE " = *" debian "* ]] && echo yes || true)
-LOOKS_LIKE_ARCH=$(source /etc/os-release && [[ "$ID" = "arch" || " $ID_LIKE " = *" arch "* ]] && echo yes || true)
-LOOKS_LIKE_SUSE=$(source /etc/os-release && [[ " $ID_LIKE " = *" suse "* ]] && echo yes || true)
+LOOKS_LIKE_DEBIAN=$(source /etc/os-release && [[ "$ID" = "debian" || " $ID_LIKE " = *" debian "* ]] && echo yes || :)
+LOOKS_LIKE_ARCH=$(source /etc/os-release && [[ "$ID" = "arch" || " $ID_LIKE " = *" arch "* ]] && echo yes || :)
+LOOKS_LIKE_SUSE=$(source /etc/os-release && [[ " $ID_LIKE " = *" suse "* ]] && echo yes || :)
KERNEL_VER=${KERNEL_VER-$(uname -r)}
KERNEL_MODS="/lib/modules/$KERNEL_VER/"
QEMU_TIMEOUT="${QEMU_TIMEOUT:-infinity}"
EFI_MOUNT="$(bootctl -x 2>/dev/null || echo /boot)"
QEMU_MEM="${QEMU_MEM:-512M}"
+# 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 [[ -n "$TEST_NESTED_KVM" || ( -z "$TEST_NO_KVM" && $(systemd-detect-virt -v) != kvm ) ]]; then
+ QEMU_KVM=yes
+else
+ QEMU_KVM=no
+fi
+
if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then
echo "WARNING! Cannot determine rootlibdir from pkg-config, assuming /usr/lib/systemd" >&2
ROOTLIBDIR=/usr/lib/systemd
fi
PATH_TO_INIT=$ROOTLIBDIR/systemd
+[ "$SYSTEMD_JOURNALD" ] || SYSTEMD_JOURNALD=$(which -a $BUILD_DIR/systemd-journald $ROOTLIBDIR/systemd-journald 2>/dev/null | grep '^/' -m1)
+[ "$SYSTEMD" ] || SYSTEMD=$(which -a $BUILD_DIR/systemd $ROOTLIBDIR/systemd 2>/dev/null | grep '^/' -m1)
+[ "$SYSTEMD_NSPAWN" ] || SYSTEMD_NSPAWN=$(which -a $BUILD_DIR/systemd-nspawn systemd-nspawn 2>/dev/null | grep '^/' -m1)
+[ "$JOURNALCTL" ] || JOURNALCTL=$(which -a $BUILD_DIR/journalctl journalctl 2>/dev/null | grep '^/' -m1)
BASICTOOLS="test sh bash setsid loadkeys setfont login sulogin gzip sleep echo head tail cat mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false chmod chown ln xargs"
DEBUGTOOLS="df free ls stty ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find vi mv"
fi
# Borrowed from https://github.com/google/oss-fuzz/blob/cd9acd02f9d3f6e80011cc1e9549be526ce5f270/infra/base-images/base-runner/bad_build_check#L182
- local _asan_calls=$(objdump -dC $BUILD_DIR/systemd-journald | egrep "callq\s+[0-9a-f]+\s+<__asan" -c)
+ local _asan_calls=$(objdump -dC $SYSTEMD_JOURNALD | egrep "callq\s+[0-9a-f]+\s+<__asan" -c)
if (( $_asan_calls < 1000 )); then
return 1
else
STRIP_BINARIES=no
SKIP_INITRD="${SKIP_INITRD:-yes}"
PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
- QEMU_MEM="1536M"
+ QEMU_MEM="2048M"
QEMU_SMP=4
+
+ # We need to correctly distinguish between gcc's and clang's ASan DSOs.
+ if ldd $SYSTEMD | grep -q libasan.so; then
+ ASAN_COMPILER=gcc
+ elif ldd $SYSTEMD | grep -q libclang_rt.asan; then
+ ASAN_COMPILER=clang
+
+ # As clang's ASan DSO is usually in a non-standard path, let's check if
+ # the environment is set accordingly. If not, warn the user and exit.
+ # We're not setting the LD_LIBRARY_PATH automagically here, because
+ # user should encounter (and fix) the same issue when running the unit
+ # tests (meson test)
+ if ldd "$SYSTEMD" | grep -q "libclang_rt.asan.*not found"; then
+ _asan_rt_name="$(ldd $SYSTEMD | awk '/libclang_rt.asan/ {print $1; exit}')"
+ _asan_rt_path="$(find /usr/lib* /usr/local/lib* -type f -name "$_asan_rt_name" 2>/dev/null | sed 1q)"
+ echo >&2 "clang's ASan DSO ($_asan_rt_name) is not present in the runtime library path"
+ echo >&2 "Consider setting LD_LIBRARY_PATH=${_asan_rt_path%/*}"
+ exit 1
+ fi
+ else
+ echo >&2 "systemd is not linked against the ASan DSO"
+ echo >&2 "gcc does this by default, for clang compile with -shared-libasan"
+ exit 1
+ fi
fi
function find_qemu_bin() {
# SUSE and Red Hat call the binary qemu-kvm. Debian and Gentoo call it kvm.
- # Either way, only use this version if we aren't running in KVM, because
- # nested KVM is flaky still.
- if [[ $(systemd-detect-virt -v) != kvm && -z $TEST_NO_KVM ]] ; then
+ if [[ $QEMU_KVM == "yes" ]]; then
[ "$QEMU_BIN" ] || QEMU_BIN=$(which -a kvm qemu-kvm 2>/dev/null | grep '^/' -m1)
fi
[ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu 2>/dev/null | grep '^/' -m1)
;;
ppc64*)
- [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-$ARCH 2>/dev/null | grep '^/' -m1)
+ [ "$QEMU_BIN" ] || QEMU_BIN=$(which -a qemu-system-ppc64 2>/dev/null | grep '^/' -m1)
;;
esac
-nographic \
-kernel $KERNEL_BIN \
-drive format=raw,cache=unsafe,file=${TESTDIR}/rootdisk.img \
+$QEMU_OPTIONS \
"
if [[ "$INITRD" && "$SKIP_INITRD" != "yes" ]]; then
QEMU_OPTIONS="$QEMU_OPTIONS -initrd $INITRD"
fi
- # Let's use KVM if it is available, but let's avoid using nested KVM as that is still flaky
- if [[ -c /dev/kvm && $(systemd-detect-virt -v) != kvm && -z $TEST_NO_KVM ]] ; then
+ # Let's use KVM if possible
+ if [[ -c /dev/kvm && $QEMU_KVM == "yes" ]]; then
QEMU_OPTIONS="$QEMU_OPTIONS -machine accel=kvm -enable-kvm -cpu host"
fi
run_nspawn() {
[[ -d /run/systemd/system ]] || return 1
- local _nspawn_cmd="$BUILD_DIR/systemd-nspawn $NSPAWN_ARGUMENTS --register=no --kill-signal=SIGKILL --directory=$TESTDIR/$1 $PATH_TO_INIT $KERNEL_APPEND"
+ local _nspawn_cmd="$SYSTEMD_NSPAWN $NSPAWN_ARGUMENTS --register=no --kill-signal=SIGKILL --directory=$TESTDIR/$1 $PATH_TO_INIT $KERNEL_APPEND"
if [[ "$NSPAWN_TIMEOUT" != "infinity" ]]; then
_nspawn_cmd="timeout --foreground $NSPAWN_TIMEOUT $_nspawn_cmd"
fi
if [[ "$UNIFIED_CGROUP_HIERARCHY" = "hybrid" ]]; then
- dwarn "nspawn doesn't support UNIFIED_CGROUP_HIERARCHY=hybrid, skipping"
+ dwarn "nspawn doesn't support SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=hybrid, skipping"
exit
elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "yes" || "$UNIFIED_CGROUP_HIERARCHY" = "no" ]]; then
- _nspawn_cmd="env UNIFIED_CGROUP_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
+ _nspawn_cmd="env SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=$UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
elif [[ "$UNIFIED_CGROUP_HIERARCHY" = "default" ]]; then
- _nspawn_cmd="env --unset=UNIFIED_CGROUP_HIERARCHY $_nspawn_cmd"
+ _nspawn_cmd="env --unset=UNIFIED_CGROUP_HIERARCHY --unset=SYSTEMD_NSPAWN_UNIFIED_HIERARCHY $_nspawn_cmd"
else
dfatal "Unknown UNIFIED_CGROUP_HIERARCHY. Got $UNIFIED_CGROUP_HIERARCHY, expected [yes|no|hybrid|default]"
exit 1
install_depmod_files
generate_module_dependencies
if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
- create_asan_wrapper
+ create_asan_wrapper
fi
}
create_asan_wrapper() {
local _asan_wrapper=$initdir/$ROOTLIBDIR/systemd-under-asan
+ local _asan_rt_pattern
ddebug "Create $_asan_wrapper"
+
+ case "$ASAN_COMPILER" in
+ gcc)
+ _asan_rt_pattern="*libasan*"
+ ;;
+ clang)
+ _asan_rt_pattern="libclang_rt.asan-*"
+ # Install llvm-symbolizer to generate useful reports
+ # See: https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
+ dracut_install "llvm-symbolizer"
+ ;;
+ *)
+ dfail "Unsupported compiler: $ASAN_COMPILER"
+ exit 1
+ esac
+
cat >$_asan_wrapper <<EOF
#!/bin/bash
set -x
-DEFAULT_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
-DEFAULT_UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
+DEFAULT_ASAN_OPTIONS=${ASAN_OPTIONS:-strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1}
+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"
+# 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 proc proc /proc
mount -t sysfs sysfs /sys
mount -o remount,rw /
-PATH_TO_ASAN=\$(find / -name '*libasan*' | sed 1q)
+PATH_TO_ASAN=\$(find / -name '$_asan_rt_pattern' | sed 1q)
if [[ "\$PATH_TO_ASAN" ]]; then
# 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=\$PATH_TO_ASAN"
+ # Let's add the ASan DSO's path to the dynamic linker's cache. This is pretty
+ # unnecessary for gcc & libasan, however, for clang this is crucial, as its
+ # runtime ASan DSO is in a non-standard (library) path.
+ echo \${PATH_TO_ASAN%/*} > /etc/ld.so.conf.d/asan-path-override.conf
+ ldconfig
fi
echo DefaultEnvironment=\$DEFAULT_ENVIRONMENT >>/etc/systemd/system.conf
echo DefaultTimeoutStartSec=180s >>/etc/systemd/system.conf
+echo DefaultStandardOutput=journal+console >>/etc/systemd/system.conf
# ASAN and syscall filters aren't compatible with each other.
find / -name '*.service' -type f | xargs sed -i 's/^\\(MemoryDeny\\|SystemCall\\)/#\\1/'
install_dmevent() {
instmods dm_crypt =crypto
- type -P dmeventd >/dev/null && dracut_install dmeventd
- inst_libdir_file "libdevmapper-event.so*"
+ inst_binary dmeventd
if [[ "$LOOKS_LIKE_DEBIAN" ]]; then
# dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu
# and since buster/bionic 95-dm-notify.rules
# enable debug logging in PID1
echo LogLevel=debug >> $initdir/etc/systemd/system.conf
+ # store coredumps in journal
+ echo Storage=journal >> $initdir/etc/systemd/coredump.conf
}
get_ldpath() {
install_missing_libraries() {
# install possible missing libraries
for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
- LD_LIBRARY_PATH=$(get_ldpath $i) inst_libs $i
+ LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath $i)" inst_libs $i
done
}
create_empty_image() {
local _size=500
if [[ "$STRIP_BINARIES" = "no" ]]; then
- _size=$((2*_size))
+ _size=$((4*_size))
fi
rm -f "$TESTDIR/rootdisk.img"
# Create the blank file to use as a root filesystem
- dd if=/dev/null of="$TESTDIR/rootdisk.img" bs=1M seek="$_size"
+ truncate -s "${_size}M" "$TESTDIR/rootdisk.img"
LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img)
[ -b "$LOOPDEV" ] || return 1
echo "LOOPDEV=$LOOPDEV" >> $STATEFILE
fi
}
+create_empty_image_rootdir() {
+ create_empty_image
+ mkdir -p $initdir
+ mount ${LOOPDEV}p1 $initdir
+ TEST_SETUP_CLEANUP_ROOTDIR=1
+}
+
check_asan_reports() {
local ret=0
local root="$1"
journald_report=$(find "$root" -name "systemd-journald.*san.log*" -exec cat {} \;)
if [[ ! -z "$journald_report" ]]; then
printf "%s\n" "$journald_report"
- cat "$root/systemd-journald.out" || true
+ cat "$root/systemd-journald.out" || :
ret=$(($ret+1))
fi
pids=$(
- "$BUILD_DIR/journalctl" -D "$root/var/log/journal" | perl -alne '
+ "$JOURNALCTL" -D "$root/var/log/journal" | perl -alne '
BEGIN {
%services_to_ignore = (
"dbus-daemon" => undef,
if [[ ! -z "$pids" ]]; then
ret=$(($ret+1))
for pid in $pids; do
- "$BUILD_DIR/journalctl" -D "$root/var/log/journal" _PID=$pid --no-pager
+ "$JOURNALCTL" -D "$root/var/log/journal" _PID=$pid --no-pager
done
fi
fi
# can be overridden in specific test
check_result_qemu() {
local ret=1
- mkdir -p $TESTDIR/root
- mount ${LOOPDEV}p1 $TESTDIR/root
- [[ -e $TESTDIR/root/testok ]] && ret=0
- [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR
- cp -a $TESTDIR/root/var/log/journal $TESTDIR
- check_asan_reports "$TESTDIR/root" || ret=$(($ret+1))
- umount $TESTDIR/root
+ mkdir -p $initdir
+ mount ${LOOPDEV}p1 $initdir
+ [[ -e $initdir/testok ]] && ret=0
+ [[ -f $initdir/failed ]] && cp -a $initdir/failed $TESTDIR
+ cp -a $initdir/var/log/journal $TESTDIR
+ check_asan_reports "$initdir" || ret=$(($ret+1))
+ umount $initdir
[[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
ls -l $TESTDIR/journal/*/*.journal
test -s $TESTDIR/failed && ret=$(($ret+1))
ddebug "Strip binaries"
find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | \
xargs strip --strip-unneeded |& \
- grep -v 'file format not recognized' | \
+ grep -vi 'file format not recognized' | \
ddebug
}
# some {rc,halt}.local scripts and programs are okay to not exist, the rest should
# also, plymouth is pulled in by rescue.service, but even there the exit code
# is ignored; as it's not present on some distros, don't fail if it doesn't exist
+ dinfo "Attempting to install $i"
inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "/bin/plymouth" == "$i" ]
done
)
}
install_config_files() {
- inst /etc/sysconfig/init || true
+ inst /etc/sysconfig/init || :
inst /etc/passwd
inst /etc/shadow
inst /etc/login.defs
inst /etc/group
inst /etc/shells
inst /etc/nsswitch.conf
- inst /etc/pam.conf || true
- inst /etc/securetty || true
+ inst /etc/pam.conf || :
+ inst /etc/securetty || :
inst /etc/os-release
inst /etc/localtime
# we want an empty environment
install_dbus() {
inst $ROOTLIBDIR/system/dbus.socket
- # Fedora rawhide replaced dbus.service with dbus-daemon.service
- if [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
+ # Newer Fedora versions use dbus-broker by default. Let's install it is available.
+ if [ -f $ROOTLIBDIR/system/dbus-broker.service ]; then
+ inst $ROOTLIBDIR/system/dbus-broker.service
+ inst_symlink /etc/systemd/system/dbus.service
+ inst /usr/bin/dbus-broker
+ inst /usr/bin/dbus-broker-launch
+ elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
+ # Fedora rawhide replaced dbus.service with dbus-daemon.service
inst $ROOTLIBDIR/system/dbus-daemon.service
# Alias symlink
inst_symlink /etc/systemd/system/dbus.service
else
inst $ROOTLIBDIR/system/dbus.service
fi
- # Newer Fedora versions use dbus-broker by default. Let's install it is available.
- [ -f /usr/bin/dbus-broker ] && inst /usr/bin/dbus-broker
- [ -f /usr/bin/dbus-broker-launch ] && inst /usr/bin/dbus-broker-launch
find \
/etc/dbus-1 /usr/share/dbus-1 -xtype f \
ln -sfn /run/lock "$initdir/var/lock"
}
+mask_supporting_services() {
+ # mask some services that we do not want to run in these tests
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
+}
+
inst_libs() {
local _bin=$1
local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)'
import_initdir() {
initdir=$TESTDIR/root
+ mkdir -p $initdir
export initdir
}
# Install binary executable, and all shared library dependencies, if any.
inst_binary() {
local _bin _target
+
+ # 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
+ # success.
+ [[ $# -eq 1 && -e $initdir/$1 ]] && return 0
+
_bin=$(find_binary "$1") || return 1
_target=${2:-$_bin}
[[ -e $initdir/$_target ]] && return 0
| instmods
else
( [[ "$_mpargs" ]] && echo $_mpargs
- find "$KERNEL_MODS" -path "*/${_mod#=}/*" -printf '%f\n' ) \
+ find "$KERNEL_MODS" -path "*/${_mod#=}/*" -type f -printf '%f\n' ) \
| instmods
fi
;;
return $_ret
}
-# inst_libdir_file [-n <pattern>] <file> [<file>...]
-# Install a <file> located on a lib directory to the initramfs image
-# -n <pattern> install non-matching files
-inst_libdir_file() {
- if [[ "$1" == "-n" ]]; then
- local _pattern=$1
- shift 2
- for _dir in $libdirs; do
- for _i in "$@"; do
- for _f in "$_dir"/$_i; do
- [[ "$_i" =~ $_pattern ]] || continue
- [[ -e "$_i" ]] && dracut_install "$_i"
- done
- done
- done
- else
- for _dir in $libdirs; do
- for _i in "$@"; do
- for _f in "$_dir"/$_i; do
- [[ -e "$_f" ]] && dracut_install "$_f"
- done
- done
- done
- fi
-}
-
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() {
+ if mountpoint -q $1; then
+ ddebug "umount $1"
+ umount $1
+ fi
+}
+
+_test_setup_cleanup() {
+ # only umount if create_empty_image_rootdir() was called to mount it
+ [[ -z $TEST_SETUP_CLEANUP_ROOTDIR ]] || _umount_dir $initdir
+}
+
+# can be overridden in specific test
+test_setup_cleanup() {
+ _test_setup_cleanup
+}
+
+_test_cleanup() {
+ # (post-test) cleanup should always ignore failure and cleanup as much as possible
+ (
+ set +e
+ _umount_dir $initdir
+ if [[ $LOOPDEV && -b $LOOPDEV ]]; then
+ ddebug "losetup -d $LOOPDEV"
+ losetup -d $LOOPDEV
+ fi
+ rm -fr "$TESTDIR"
+ rm -f "$STATEFILE"
+ ) || :
+}
+
# can be overridden in specific test
test_cleanup() {
- umount $TESTDIR/root 2>/dev/null || true
- [[ $LOOPDEV ]] && losetup -d $LOOPDEV || true
- return 0
+ _test_cleanup
}
test_run() {
--setup)
echo "TEST SETUP: $TEST_DESCRIPTION"
test_setup
+ test_setup_cleanup
;;
--clean)
echo "TEST CLEANUP: $TEST_DESCRIPTION"
test_cleanup
- rm -fr "$TESTDIR"
- rm -f "$STATEFILE"
;;
--all)
ret=0
- echo -n "TEST: $TEST_DESCRIPTION ";
+ echo -n "TEST: $TEST_DESCRIPTION "
(
- test_setup && test_run
- ret=$?
- test_cleanup
- rm -fr "$TESTDIR"
- rm -f "$STATEFILE"
- exit $ret
+ test_setup
+ test_setup_cleanup
+ test_run
) </dev/null >"$TESTLOG" 2>&1 || ret=$?
+ test_cleanup
if [ $ret -eq 0 ]; then
rm "$TESTLOG"
echo "[OK]"