#!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later # Utility functions for shell tests # shellcheck disable=SC2034 [[ -e /var/tmp/.systemd_reboot_count ]] && REBOOT_COUNT="$(&2 exit 1 fi )} assert_eq() {( set +ex if [[ "${1?}" != "${2?}" ]]; then echo "FAIL: expected: '$2' actual: '$1'" >&2 exit 1 fi )} assert_le() {( set +ex if [[ "${1:?}" -gt "${2:?}" ]]; then echo "FAIL: '$1' > '$2'" >&2 exit 1 fi )} assert_in() {( set +ex if ! [[ "${2?}" =~ ${1?} ]]; then echo "FAIL: '$1' not found in:" >&2 echo "$2" >&2 exit 1 fi )} assert_not_in() {( set +ex if [[ "${2?}" =~ ${1?} ]]; then echo "FAIL: '$1' found in:" >&2 echo "$2" >&2 exit 1 fi )} assert_rc() {( set +ex local rc exp="${1?}" shift "$@" rc=$? assert_eq "$rc" "$exp" )} assert_not_reached() { echo >&2 "Code should not be reached at ${BASH_SOURCE[1]}:${BASH_LINENO[1]}, function ${FUNCNAME[1]}()" exit 1 } run_and_grep() {( set +ex local expression local log ec local exp_ec=0 # Invert the grep condition - i.e. check if the expression is _not_ in command's output if [[ "${1:?}" == "-n" ]]; then exp_ec=1 shift fi expression="${1:?}" shift if [[ $# -eq 0 ]]; then echo >&2 "FAIL: Not enough arguments for ${FUNCNAME[0]}()" return 1 fi log="$(mktemp)" if ! "$@" |& tee "${log:?}"; then echo >&2 "FAIL: Command '$*' failed" return 1 fi grep -qE "$expression" "$log" && ec=0 || ec=$? if [[ "$exp_ec" -eq 0 && "$ec" -ne 0 ]]; then echo >&2 "FAIL: Expression '$expression' not found in the output of '$*'" return 1 elif [[ "$exp_ec" -ne 0 && "$ec" -eq 0 ]]; then echo >&2 "FAIL: Expression '$expression' found in the output of '$*'" return 1 fi rm -f "$log" )} 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 ;; *) echo >&2 "Failed to determine host's cgroup hierarchy" exit 1 esac } runas() { local userid="${1:?}" shift XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@" } coverage_create_nspawn_dropin() { # If we're collecting coverage, bind mount the $BUILD_DIR into the nspawn # container so gcov can update the counters. This is mostly for standalone # containers, as machinectl stuff is handled by overriding the systemd-nspawn@.service # (see test/test-functions:install_systemd()) local root="${1:?}" local container if [[ -z "${COVERAGE_BUILD_DIR:-}" ]]; then return 0 fi container="$(basename "$root")" mkdir -p "/run/systemd/nspawn" echo -ne "[Files]\nBind=$COVERAGE_BUILD_DIR\n" >"/run/systemd/nspawn/${container:?}.nspawn" } create_dummy_container() { local root="${1:?}" if [[ ! -d /usr/share/TEST-13-NSPAWN-container-template ]]; then echo >&2 "Missing container template, probably not running in TEST-13-NSPAWN?" exit 1 fi mkdir -p "$root" cp -a /usr/share/TEST-13-NSPAWN-container-template/* "$root" coverage_create_nspawn_dropin "$root" } # Bump the reboot counter and call systemctl with the given arguments systemctl_final() { local counter if [[ $# -eq 0 ]]; then echo >&2 "Missing arguments" exit 1 fi [[ -e /var/tmp/.systemd_reboot_count ]] && counter="$(/var/tmp/.systemd_reboot_count systemctl "$@" } cgroupfs_supports_user_xattrs() { local xattr xattr="user.supported_$RANDOM" # shellcheck disable=SC2064 trap "setfattr --remove=$xattr /sys/fs/cgroup || :" RETURN setfattr --name="$xattr" --value=254 /sys/fs/cgroup [[ "$(getfattr --name="$xattr" --absolute-names --only-values /sys/fs/cgroup)" -eq 254 ]] } tpm_has_pcr() { local algorithm="${1:?}" local pcr="${2:?}" [[ -f "/sys/class/tpm/tpm0/pcr-$algorithm/$pcr" ]] } openssl_supports_kdf() { local kdf="${1:?}" # The arguments will need to be adjusted to make this work for other KDFs than SSKDF, # but let's do that when/if the need arises openssl kdf -keylen 16 -kdfopt digest:SHA2-256 -kdfopt key:foo -out /dev/null "$kdf" } kernel_supports_lsm() { local lsm="${1:?}" local items item if [[ ! -e /sys/kernel/security/lsm ]]; then echo "/sys/kernel/security/lsm doesn't exist, assuming $lsm is not supported" return 1 fi mapfile -t -d, items "$initdir/usr/lib/extension-release.d/extension-release.app0" echo "$version_id" >>"$initdir/usr/lib/extension-release.d/extension-release.app0" ( echo "$version_id" echo "SYSEXT_IMAGE_ID=app" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app0" cat >"$initdir/usr/lib/systemd/system/app0.service" <"$initdir/opt/script0.sh" <\${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" /tmp/app0.raw -noappend initdir="/var/tmp/conf0" mkdir -p "$initdir/etc/extension-release.d" "$initdir/etc/systemd/system" "$initdir/opt" grep "^ID=" "$os_release" >"$initdir/etc/extension-release.d/extension-release.conf0" echo "$version_id" >>"$initdir/etc/extension-release.d/extension-release.conf0" ( echo "$version_id" echo "CONFEXT_IMAGE_ID=app" ) >>"$initdir/etc/extension-release.d/extension-release.conf0" echo MARKER_1 >"$initdir/etc/systemd/system/some_file" mksquashfs "$initdir" /tmp/conf0.raw -noappend initdir="/var/tmp/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.app2" ( echo "$version_id" echo "SYSEXT_SCOPE=portable" echo "SYSEXT_IMAGE_ID=app" echo "SYSEXT_IMAGE_VERSION=1" 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" <"$initdir/opt/script1.sh" <\${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" /tmp/app1.raw -noappend initdir="/var/tmp/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" /tmp/app-nodistro.raw -noappend initdir="/var/tmp/service-scoped-test" mkdir -p "$initdir/etc/extension-release.d" "$initdir/etc/systemd/system" ( echo "ID=_any" echo "ARCHITECTURE=_any" ) >"$initdir/etc/extension-release.d/extension-release.service-scoped-test" echo MARKER_CONFEXT_123 >"$initdir/etc/systemd/system/some_file" mksquashfs "$initdir" /etc/service-scoped-test.raw -noappend # We need to create a dedicated sysext image to test the reload mechanism. If we share an image to install the # 'foo.service' it will be loaded from another test run, which will impact the targeted test. initdir="/var/tmp/app-reload" mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" ( echo "ID=_any" echo "ARCHITECTURE=_any" echo "EXTENSION_RELOAD_MANAGER=1" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-reload" mkdir -p "$initdir/usr/lib/systemd/system/multi-user.target.d" cat >"$initdir/usr/lib/systemd/system/foo.service" <"$initdir/usr/lib/systemd/system/multi-user.target.d/10-foo-service.conf" mksquashfs "$initdir" /tmp/app-reload.raw -noappend } restore_locale() { if [[ -d /usr/lib/locale/xx_XX.UTF-8 ]]; then rmdir /usr/lib/locale/xx_XX.UTF-8 fi if [[ -f /tmp/locale.conf.bak ]]; then mv /tmp/locale.conf.bak /etc/locale.conf else rm -f /etc/locale.conf fi if [[ -f /tmp/default-locale.bak ]]; then mv /tmp/default-locale.bak /etc/default/locale else rm -rf /etc/default fi if [[ -f /tmp/locale.gen.bak ]]; then mv /tmp/locale.gen.bak /etc/locale.gen else rm -f /etc/locale.gen fi } generate_locale() { local locale="${1:?}" if command -v locale-gen >/dev/null && ! localectl list-locales | grep -F "$locale"; then echo "$locale UTF-8" >/etc/locale.gen locale-gen "$locale" fi }