diff --git a/tests/lib/tools/cleanup-state b/tests/lib/tools/cleanup-state index 2e660f59be4..b5273ae7ec6 100755 --- a/tests/lib/tools/cleanup-state +++ b/tests/lib/tools/cleanup-state @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash show_help() { echo "usage: cleanup-state " @@ -12,84 +12,88 @@ show_help() { echo " each test, that should not leak across tests." } -if [ $# -eq 0 ]; then - show_help - exit 1 -fi +main() { + if [ $# -eq 0 ]; then + show_help + exit 1 + fi -action= -while [ $# -gt 0 ]; do - case "$1" in - -h|--help) - show_help - exit 0 - ;; - --) - shift - break - ;; + action= + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + --) + shift + break + ;; + pre-invariant) + action=pre-invariant + shift + ;; + post-invariant) + action=post-invariant + shift + ;; + -*) + echo "cleanup-state: unsupported argument $1" >&2 + exit 1 + ;; + *) + echo "cleanup-state: unknown action $1" >&2 + exit 1 + ;; + esac + done + + case "$action" in pre-invariant) - action=pre-invariant - shift + # If the root user has a systemd --user instance then ask it to reload. + # This prevents tests from leaking user-session services that stay in + # memory but are not present on disk, or have been modified on disk, as is + # common with tests that use snaps with user services _or_ with tests that + # cause installation of the snapd.session-agent.service unit via re-exec + # machinery. + # + # This is done AHEAD of the invariant checks as it is very widespread + # and fixing it in each test is not a priority right now. + # + # Note that similar treatment is not required for the "test" user as + # correct usage of tests.session ensures that the session and all the + # processes of the "test" user are terminated. + if pgrep -u root --full "systemd --user"; then + systemctl --user daemon-reload + # Following that, check if there's a snapd.session-agent.socket and + # if one exists stop it and then start it, ignoring errors. If the + # unit was removed, stopping it clears it from memory. This is + # different from restarting the unit, which doesn't do anything if + # the unit on disk is gone. + if systemctl --user is-active snapd.session-agent.socket; then + systemctl --user stop snapd.session-agent.socket + fi + # XXX: if there's a way to check if an unit exists but is stopped, + # use it here to avoid starting a non-existing unit. + systemctl --user start snapd.session-agent.socket || true + + # Do the same for root's D-Bus session bus. + # This will stop the bus and any clients that + # may be connected to it. + if systemctl --user is-active dbus.socket; then + systemctl --user stop dbus.socket + fi + systemctl --user start dbus.socket || true + fi ;; post-invariant) - action=post-invariant - shift - ;; - -*) - echo "cleanup-state: unsupported argument $1" >&2 - exit 1 + true ;; *) - echo "cleanup-state: unknown action $1" >&2 + echo "cleanup-state: unknown action $action" >&2 exit 1 ;; esac -done - -case "$action" in - pre-invariant) - # If the root user has a systemd --user instance then ask it to reload. - # This prevents tests from leaking user-session services that stay in - # memory but are not present on disk, or have been modified on disk, as is - # common with tests that use snaps with user services _or_ with tests that - # cause installation of the snapd.session-agent.service unit via re-exec - # machinery. - # - # This is done AHEAD of the invariant checks as it is very widespread - # and fixing it in each test is not a priority right now. - # - # Note that similar treatment is not required for the "test" user as - # correct usage of tests.session ensures that the session and all the - # processes of the "test" user are terminated. - if pgrep -u root --full "systemd --user"; then - systemctl --user daemon-reload - # Following that, check if there's a snapd.session-agent.socket and - # if one exists stop it and then start it, ignoring errors. If the - # unit was removed, stopping it clears it from memory. This is - # different from restarting the unit, which doesn't do anything if - # the unit on disk is gone. - if systemctl --user is-active snapd.session-agent.socket; then - systemctl --user stop snapd.session-agent.socket - fi - # XXX: if there's a way to check if an unit exists but is stopped, - # use it here to avoid starting a non-existing unit. - systemctl --user start snapd.session-agent.socket || true +} - # Do the same for root's D-Bus session bus. - # This will stop the bus and any clients that - # may be connected to it. - if systemctl --user is-active dbus.socket; then - systemctl --user stop dbus.socket - fi - systemctl --user start dbus.socket || true - fi - ;; - post-invariant) - true - ;; - *) - echo "cleanup-state: unknown action $action" >&2 - exit 1 - ;; -esac +main "$@" diff --git a/tests/lib/tools/fs-state b/tests/lib/tools/fs-state index 87bd6c41391..4f84dbc8efa 100755 --- a/tests/lib/tools/fs-state +++ b/tests/lib/tools/fs-state @@ -77,24 +77,28 @@ restore_file() { fi } -if [ $# -eq 0 ]; then - show_help - exit 0 -fi +main() { + if [ $# -eq 0 ]; then + show_help + exit 0 + fi + + action= + while [ $# -gt 0 ]; do + case "$1" in + -h|--help|'') + show_help + exit 0 + ;; + *) + action=$(echo "$1" | tr '-' '_') + shift + break + ;; + esac + done -action= -while [ $# -gt 0 ]; do - case "$1" in - -h|--help|'') - show_help - exit 0 - ;; - *) - action=$(echo "$1" | tr '-' '_') - shift - break - ;; - esac -done + "$action" "$@" +} -"$action" "$@" +main "$@" diff --git a/tests/lib/tools/journal-state b/tests/lib/tools/journal-state index f5f05732c92..8e59815536f 100755 --- a/tests/lib/tools/journal-state +++ b/tests/lib/tools/journal-state @@ -84,31 +84,35 @@ _sync_log(){ journalctl --sync || true } -if [ $# -eq 0 ]; then - show_help - exit 0 -fi +main() { + if [ $# -eq 0 ]; then + show_help + exit 0 + fi -subcommand=$1 -action= -while [ $# -gt 0 ]; do - case "$1" in - -h|--help) - show_help - exit 0 - ;; - *) - action=$(echo "$subcommand" | tr '-' '_') - shift - break - ;; - esac -done + subcommand=$1 + action= + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + *) + action=$(echo "$subcommand" | tr '-' '_') + shift + break + ;; + esac + done -if [ -z "$(declare -f "$action")" ]; then - echo "journal-state: no such command $subcommand" >&2 - show_help - exit 1 -fi + if [ -z "$(declare -f "$action")" ]; then + echo "journal-state: no such command $subcommand" >&2 + show_help + exit 1 + fi + + "$action" "$@" +} -"$action" "$@" +main "$@" diff --git a/tests/lib/tools/lxd-state b/tests/lib/tools/lxd-state index 3a00262e666..bfe2f02738e 100755 --- a/tests/lib/tools/lxd-state +++ b/tests/lib/tools/lxd-state @@ -1,38 +1,53 @@ -#!/bin/sh -e -case "${1:-}" in - -h|--help|'') - echo "usage: lxd-state [-h] {undo-mount-changes} ..." - ;; - undo-mount-changes) - # Vanilla systems have /sys/fs/cgroup/cpuset without clone_children option. - # Using LXD to create a container enables this option, as can be seen here: - # - # -37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset - # +37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset,clone_children - # - # To restore vanilla state, disable the option now. - if [ "$(mountinfo.query /sys/fs/cgroup/cpuset .fs_type)" = cgroup ]; then - echo 0 > /sys/fs/cgroup/cpuset/cgroup.clone_children - fi +#!/bin/bash -e - # Vanilla system have /sys/fs/cgroup/unified mounted with the nsdelegate - # option which is available since kernel 4.13 Using LXD to create a - # container disables this options, as can be seen here: - # - # -32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw,nsdelegate - # +32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw - # - # To restore vanilla state, enable the option now, but only if the kernel supports that. - # https://lore.kernel.org/patchwork/patch/803265/ - # https://github.com/systemd/systemd/commit/4095205ecccdfddb822ee8fdc44d11f2ded9be24 - # The kernel version must be made compatible with the strict version - # comparison. I chose to cut at the "-" and take the stuff before it. - if [ "$(mountinfo.query /sys/fs/cgroup/unified .fs_type)" = cgroup2 ] && "$TESTSTOOLS"/version-compare --strict "$(uname -r | cut -d- -f 1)" -ge 4.13; then - mount -o remount,nsdelegate /sys/fs/cgroup/unified - fi - ;; - *) - echo "lxd-state: unknown command $*" >&2 - exit 1 - ;; -esac +show_help() { + echo "usage: lxd-state [-h] {undo-mount-changes} ..." +} + +main() { + if [ $# -eq 0 ]; then + show_help + exit 0 + fi + + case "${1:-}" in + -h|--help) + show_help + exit 0 + ;; + undo-mount-changes) + # Vanilla systems have /sys/fs/cgroup/cpuset without clone_children option. + # Using LXD to create a container enables this option, as can be seen here: + # + # -37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset + # +37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset,clone_children + # + # To restore vanilla state, disable the option now. + if [ "$(mountinfo.query /sys/fs/cgroup/cpuset .fs_type)" = cgroup ]; then + echo 0 > /sys/fs/cgroup/cpuset/cgroup.clone_children + fi + + # Vanilla system have /sys/fs/cgroup/unified mounted with the nsdelegate + # option which is available since kernel 4.13 Using LXD to create a + # container disables this options, as can be seen here: + # + # -32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw,nsdelegate + # +32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw + # + # To restore vanilla state, enable the option now, but only if the kernel supports that. + # https://lore.kernel.org/patchwork/patch/803265/ + # https://github.com/systemd/systemd/commit/4095205ecccdfddb822ee8fdc44d11f2ded9be24 + # The kernel version must be made compatible with the strict version + # comparison. I chose to cut at the "-" and take the stuff before it. + if [ "$(mountinfo.query /sys/fs/cgroup/unified .fs_type)" = cgroup2 ] && "$TESTSTOOLS"/version-compare --strict "$(uname -r | cut -d- -f 1)" -ge 4.13; then + mount -o remount,nsdelegate /sys/fs/cgroup/unified + fi + ;; + *) + echo "lxd-state: unknown command $*" >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/tests/lib/tools/snapd.tool b/tests/lib/tools/snapd.tool index 04fcc3e89a8..6764d4c6d02 100755 --- a/tests/lib/tools/snapd.tool +++ b/tests/lib/tools/snapd.tool @@ -1,11 +1,7 @@ -#!/bin/sh - -show_usage() { - echo "usage: snapd.tool [OPTIONS] exec [ARGS]" -} +#!/bin/bash show_help() { - show_usage + echo "usage: snapd.tool [OPTIONS] exec [ARGS]" echo echo "Available options:" echo " -h --help show this help message." @@ -15,41 +11,45 @@ show_help() { echo "location varies from one distribution to another" } -tool="" -while [ $# -gt 0 ]; do - case "${1:-}" in - -h|--help) - show_help - exit 0 - ;; - exec) - shift - tool="${1:-}" # empty value checked below - shift - break - ;; - --) - shift - break - ;; - -*) - echo "snapd.tool: unsupported argument $1" >&2 - show_usage - exit 1 - ;; - *) - echo "snapd.tool: unsupported argument $1" >&2 - show_usage - exit 1 - ;; - esac -done +main() { + if [ $# -eq 0 ]; then + show_help + exit 0 + fi + + tool="" + while [ $# -gt 0 ]; do + case "${1:-}" in + -h|--help) + show_help + exit 0 + ;; + exec) + shift + tool="${1:-}" # empty value checked below + shift + break + ;; + --) + shift + break + ;; + -*) + echo "snapd.tool: unsupported argument $1" >&2 + show_help + exit 1 + ;; + *) + echo "snapd.tool: unsupported argument $1" >&2 + show_help + exit 1 + ;; + esac + done -if [ "$tool" = "" ]; then - show_usage - exit 1 -fi + # shellcheck source=tests/lib/dirs.sh + . "$TESTSLIB/dirs.sh" + exec "$LIBEXECDIR/snapd/$tool" "$@" +} -# shellcheck source=tests/lib/dirs.sh -. "$TESTSLIB/dirs.sh" -exec "$LIBEXECDIR/snapd/$tool" "$@" +main "$@" \ No newline at end of file diff --git a/tests/lib/tools/tests.cleanup b/tests/lib/tools/tests.cleanup index aa696224d49..56709826531 100755 --- a/tests/lib/tools/tests.cleanup +++ b/tests/lib/tools/tests.cleanup @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash -e show_help() { echo "usage: tests.cleanup prepare" @@ -68,44 +68,48 @@ cmd_restore() { rm -f defer.sh } -if [ $# -eq 0 ]; then - show_help - exit -fi +main() { + if [ $# -eq 0 ]; then + show_help + exit + fi + + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit + ;; + prepare) + shift + cmd_prepare + exit + ;; + defer) + shift + cmd_defer "$@" + exit + ;; + pop) + shift + cmd_pop "$@" + exit + ;; + restore) + shift + cmd_restore + exit + ;; + -*) + echo "tests.cleanup: unknown option $1" >&2 + exit 1 + ;; + *) + echo "tests.cleanup: unknown command $1" >&2 + exit 1 + ;; + esac + done +} -while [ $# -gt 0 ]; do - case "$1" in - -h|--help) - show_help - exit - ;; - prepare) - shift - cmd_prepare - exit - ;; - defer) - shift - cmd_defer "$@" - exit - ;; - pop) - shift - cmd_pop "$@" - exit - ;; - restore) - shift - cmd_restore - exit - ;; - -*) - echo "tests.cleanup: unknown option $1" >&2 - exit 1 - ;; - *) - echo "tests.cleanup: unknown command $1" >&2 - exit 1 - ;; - esac -done +main "$@" diff --git a/tests/lib/tools/tests.invariant b/tests/lib/tools/tests.invariant index f9b8a4b827b..490dac019f4 100755 --- a/tests/lib/tools/tests.invariant +++ b/tests/lib/tools/tests.invariant @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash show_help() { echo "usage: tests.invariant check [INVARIANT...]" @@ -146,31 +146,35 @@ check_invariant() { esac } -ALL_INVARIANTS="root-files-in-home crashed-snap-confine lxcfs-mounted stray-dbus-daemon leftover-defer-sh" +main() { + ALL_INVARIANTS="root-files-in-home crashed-snap-confine lxcfs-mounted stray-dbus-daemon leftover-defer-sh" -case "$action" in - check) - ok=1 - if [ $# -gt 0 ]; then - INV_LIST="$*" - else - INV_LIST="$ALL_INVARIANTS" - fi - for inv in $INV_LIST; do - if check_invariant "$inv"; then - echo "tests.invariant: $inv ok" + case "$action" in + check) + ok=1 + if [ $# -gt 0 ]; then + INV_LIST="$*" else - echo "tests.invariant: $inv not-ok" >&2 - ok=0 + INV_LIST="$ALL_INVARIANTS" fi - done - if [ $ok -eq 0 ]; then - echo "tests.invariant: system is corrupted" >&2 + for inv in $INV_LIST; do + if check_invariant "$inv"; then + echo "tests.invariant: $inv ok" + else + echo "tests.invariant: $inv not-ok" >&2 + ok=0 + fi + done + if [ $ok -eq 0 ]; then + echo "tests.invariant: system is corrupted" >&2 + exit 1 + fi + ;; + *) + echo "tests.invariant: unknown action $action" >&2 exit 1 - fi - ;; - *) - echo "tests.invariant: unknown action $action" >&2 - exit 1 - ;; -esac + ;; + esac +} + +main "$@" diff --git a/tests/lib/tools/tests.session b/tests/lib/tools/tests.session index c4f830f41d8..ef3e3692bb6 100755 --- a/tests/lib/tools/tests.session +++ b/tests/lib/tools/tests.session @@ -1,244 +1,246 @@ #!/bin/bash -e + show_help() { echo "usage: tests.session exec [-u USER] [-p PID_FILE] [--] " - echo "usage: tests.session prepare | restore [-u USER | -u USER1,USER2,...]" - echo "usage: tests.session kill-leaked" - echo "usage: tests.session dump" - echo "usage: tests.session has-system-systemd-and-dbus" - echo "usage: tests.session has-session-systemd-and-dbus" + echo " tests.session prepare | restore [-u USER | -u USER1,USER2,...]" + echo " tests.session kill-leaked" + echo " tests.session dump" + echo " tests.session has-system-systemd-and-dbus" + echo " tests.session has-session-systemd-and-dbus" } -if [ $# -eq 0 ]; then - show_help - exit 1 -fi -user=root -pid_file= -action= -while [ $# -gt 0 ]; do - case "$1" in - -h) - shift - show_help - exit 0 - ;; - -p) - if [ $# -eq 1 ]; then - echo "tests.session: option -p requires an argument" >&2 +main() { + if [ $# -eq 0 ]; then + show_help + exit 1 + fi + user=root + pid_file= + action= + while [ $# -gt 0 ]; do + case "$1" in + -h) + shift + show_help + exit 0 + ;; + -p) + if [ $# -eq 1 ]; then + echo "tests.session: option -p requires an argument" >&2 + exit 1 + fi + pid_file="$2" + shift 2 + ;; + --) + shift + break + ;; + kill-leaked) + action=kill-leaked + shift + ;; + prepare) + action=prepare + shift + ;; + restore) + action=restore + shift + ;; + dump) + action=dump + shift + ;; + has-system-systemd-and-dbus) + action=has-system-systemd-and-dbus + shift + ;; + has-session-systemd-and-dbus) + action=has-session-systemd-and-dbus + shift + ;; + exec) + action="exec" + shift + # remaining arguments are the command to execute + break + ;; + -u) + if [ $# -eq 1 ]; then + echo "tests.session: option -u requires an argument" >&2 + exit 1 + fi + user="$2" + shift 2 + ;; + -*) + echo "tests.session: unsupported argument $1" >&2 exit 1 - fi - pid_file="$2" - shift 2 - ;; - --) - shift - break - ;; + ;; + *) + echo "tests.session: unsupported action $1" >&2 + exit 1 + ;; + esac + done + + if [ "$(id -u)" -ne 0 ]; then + echo "tests.session needs to be invoked as root" >&2 + exit 1 + fi + + case "$action" in kill-leaked) - action=kill-leaked - shift + # Work around a bug in older versions of logind that leak closing session + # without a process inhabiting it anymore. In the case we've observed there's a + # session that is being closed with a missing process. + for session_id in $(loginctl --no-legend | awk '{ print $1 }'); do + # It would be easier to use "loginctl show-session --value" but we cannot rely on it. + if [ "$(loginctl show-session "$session_id" --property=State | cut -d = -f 2-)" = closing ]; then + leader=$(loginctl show-session "$session_id" --property=Leader | cut -d = -f 2-) + if [ ! -e "/proc/$leader" ]; then + loginctl kill-session "$session_id" + fi + fi + done + + exit 0 ;; prepare) - action=prepare - shift + # Try to enable linger for the selected user(s). + for u in $(echo "$user" | tr ',' ' '); do + if ! loginctl enable-linger "$u"; then + echo "tests.session requires external fix for //github.com/systemd/systemd/issues/12401" >&2 + exit 1 + fi + done + + for u in $(echo "$user" | tr ',' ' '); do + # We've enabled linger, now let's explicitly start the + # default.target. This ensures that test code does not need to pay + # extra attention to synchronization. + # + # Because some systems do not support systemd --user (see + # tests/main/tests.session-support for details), check if this is + # expected to work ahead of trying. + if tests.session -u "$u" exec systemctl --user is-enabled default.target >/dev/null; then + tests.session -u "$u" exec systemctl --user start default.target + fi + done + + exit 0 ;; restore) - action=restore - shift + # Disable linger for the selected user(s). + for u in $(echo "$user" | tr ',' ' '); do + + loginctl disable-linger "$u" + # If the user is not root, also stop their user slice and ensure + # their XDG_RUNTIME_DIR goes away. Currently doing this for root is + # impossible, because spread logs into a test system over ssh and + # gets a root user slice, at minimum. + # + # If spread ever changes, so that the tests are executed as system + # services, then there this condition could be removed. + if [ "$u" != root ]; then + uid="$(id -u "$u")" + + # Unmount gvfs-fuse and the document portal explicitly. FUSE + # file-systems can stay mounted in a broken state even if their + # corresponding process goes away. This can happen if we stop + # the slice and the cleanup is not perfectly graceful. + umount "/run/user/$uid/gvfs" || true + umount "/run/user/$uid/doc" || true + + # See user@.service(5) for discussion about user-UID.slice and + # user@UID.service and why both are used. Here we stop the + # slice as that encompasses both user@UID.service and any + # existing sessions of that user. + systemctl stop "user-$uid.slice" + + # On Ubuntu 16.04 and Debian 9 stopping the slice above is + # insufficient to stop the service responsible for + # /run/user/UID *immediately*. This seems to be related to + # *absence* of user-session-dir@.service (template service) + # which is responsible for this operation later on (and where + # it works more reliably). + # + # Give the system some time to clean things up. + retry -n 3 --wait 3 test ! -e "/run/user/$uid" + if [ -e "/run/user/$uid" ]; then + echo "tests.session: /run/user/$uid still exists" >&2 + exit 1 + fi + fi + done + + if [ -e /run/tests.session-core16.workaround ]; then + rm /run/tests.session-core16.workaround + # Undo changes to make /var/lib/systemd/ writable. + umount -l /var/lib/systemd + fi + + exit 0 ;; dump) - action=dump - shift + echo "Active sessions:" + for session_id in $(loginctl list-sessions --no-legend | awk '{ print($1) }'); do + echo "Details of session $session_id" + loginctl show-session "$session_id" + done + exit 0 ;; has-system-systemd-and-dbus) - action=has-system-systemd-and-dbus - shift + # Ubuntu 14.04 with deputy systemd does not connect to DBus + # and systemd-shim is really responding to "systemd" requests. + test -n "$(command -v busctl)" || ( echo "no busctl"; exit 1 ) + # /lib /usr/lib (if present) dbus-broker.service variant + test -f /lib/systemd/system/default.target || test -f /usr/lib/systemd/system/default.target || ( echo "no system default.target"; exit 1 ) + test -f /lib/systemd/system/dbus.socket || test -f /usr/lib/systemd/system/dbus.socket || ( echo "no system dbus.socket"; exit 1 ) + test -f /lib/systemd/system/dbus.service || test -f /usr/lib/systemd/system/dbus.service || test -f /lib/systemd/system/dbus-broker.service || ( echo "no system dbus{,-broker}.service"; exit 1 ) + echo "ok" + exit 0 ;; has-session-systemd-and-dbus) - action=has-session-systemd-and-dbus - shift - ;; - exec) - action="exec" - shift - # remaining arguments are the command to execute - break - ;; - -u) - if [ $# -eq 1 ]; then - echo "tests.session: option -u requires an argument" >&2 - exit 1 - fi - user="$2" - shift 2 - ;; - -*) - echo "tests.session: unsupported argument $1" >&2 - exit 1 - ;; - *) - echo "tests.session: unsupported action $1" >&2 - exit 1 + # CentOS 7 and derivatives disabled systemd --user + # https://bugs.centos.org/view.php?id=8767 + test -n "$(command -v busctl)" || ( echo "no busctl"; exit 1 ) + # /lib /usr/lib (if present) dbus-broker.service variant + test -f /lib/systemd/user/default.target || test -f /usr/lib/systemd/user/default.target || ( echo "no user default.target"; exit 1) + test -f /lib/systemd/user/dbus.socket || test -f /usr/lib/systemd/user/dbus.socket || ( echo "no user dbus.socket"; exit 1 ) + test -f /lib/systemd/user/dbus.service || test -f /usr/lib/systemd/user/dbus.service || test -f /lib/systemd/user/dbus-broker.service || ( echo "no system dbus{,-broker}.service"; exit 1 ) + echo "ok" + exit 0 ;; esac -done - -if [ "$(id -u)" -ne 0 ]; then - echo "tests.session needs to be invoked as root" >&2 - exit 1 -fi - -case "$action" in - kill-leaked) - # Work around a bug in older versions of logind that leak closing session - # without a process inhabiting it anymore. In the case we've observed there's a - # session that is being closed with a missing process. - for session_id in $(loginctl --no-legend | awk '{ print $1 }'); do - # It would be easier to use "loginctl show-session --value" but we cannot rely on it. - if [ "$(loginctl show-session "$session_id" --property=State | cut -d = -f 2-)" = closing ]; then - leader=$(loginctl show-session "$session_id" --property=Leader | cut -d = -f 2-) - if [ ! -e "/proc/$leader" ]; then - loginctl kill-session "$session_id" - fi - fi - done - exit 0 - ;; - prepare) - # Try to enable linger for the selected user(s). - for u in $(echo "$user" | tr ',' ' '); do - if ! loginctl enable-linger "$u"; then - echo "tests.session requires external fix for //github.com/systemd/systemd/issues/12401" >&2 - exit 1 - fi - done + if [ -z "$(command -v busctl)" ]; then + echo "tests.session requires busctl" >&2 + exit 1 + fi - for u in $(echo "$user" | tr ',' ' '); do - # We've enabled linger, now let's explicitly start the - # default.target. This ensures that test code does not need to pay - # extra attention to synchronization. - # - # Because some systems do not support systemd --user (see - # tests/main/tests.session-support for details), check if this is - # expected to work ahead of trying. - if tests.session -u "$u" exec systemctl --user is-enabled default.target >/dev/null; then - tests.session -u "$u" exec systemctl --user start default.target - fi - done + # This fixes a bug in some older Debian systems where /root/.profile contains + # unconditional invocation of mesg, which on non-tty shells prints "mesg + # ttyname failed inappropriate ioctl for device" which pollutes output from + # invoked programs. + # TODO: move this to spread wide project setup. + test -f /root/.profile && sed -i -e 's/mesg n .*true/tty -s \&\& mesg n/g' /root/.profile - exit 0 - ;; - restore) - # Disable linger for the selected user(s). - for u in $(echo "$user" | tr ',' ' '); do - - loginctl disable-linger "$u" - # If the user is not root, also stop their user slice and ensure - # their XDG_RUNTIME_DIR goes away. Currently doing this for root is - # impossible, because spread logs into a test system over ssh and - # gets a root user slice, at minimum. - # - # If spread ever changes, so that the tests are executed as system - # services, then there this condition could be removed. - if [ "$u" != root ]; then - uid="$(id -u "$u")" - - # Unmount gvfs-fuse and the document portal explicitly. FUSE - # file-systems can stay mounted in a broken state even if their - # corresponding process goes away. This can happen if we stop - # the slice and the cleanup is not perfectly graceful. - umount "/run/user/$uid/gvfs" || true - umount "/run/user/$uid/doc" || true - - # See user@.service(5) for discussion about user-UID.slice and - # user@UID.service and why both are used. Here we stop the - # slice as that encompasses both user@UID.service and any - # existing sessions of that user. - systemctl stop "user-$uid.slice" - - # On Ubuntu 16.04 and Debian 9 stopping the slice above is - # insufficient to stop the service responsible for - # /run/user/UID *immediately*. This seems to be related to - # *absence* of user-session-dir@.service (template service) - # which is responsible for this operation later on (and where - # it works more reliably). - # - # Give the system some time to clean things up. - retry -n 3 --wait 3 test ! -e "/run/user/$uid" - if [ -e "/run/user/$uid" ]; then - echo "tests.session: /run/user/$uid still exists" >&2 - exit 1 - fi - fi - done + read -r uuid < /proc/sys/kernel/random/uuid + unit_name="tests.session-$uuid.service" + tmp_dir="/tmp/tests.session-$uuid" + mkdir "$tmp_dir" + mkfifo -m 0666 "$tmp_dir/dbus-monitor.pipe" "$tmp_dir/ready.pipe" "$tmp_dir/result.pipe" "$tmp_dir/stdin.pipe" "$tmp_dir/stdout.pipe" "$tmp_dir/stderr.pipe" + trap 'rm -rf $tmp_dir' EXIT - if [ -e /run/tests.session-core16.workaround ]; then - rm /run/tests.session-core16.workaround - # Undo changes to make /var/lib/systemd/ writable. - umount -l /var/lib/systemd - fi - - exit 0 - ;; - dump) - echo "Active sessions:" - for session_id in $(loginctl list-sessions --no-legend | awk '{ print($1) }'); do - echo "Details of session $session_id" - loginctl show-session "$session_id" - done - exit 0 - ;; - has-system-systemd-and-dbus) - # Ubuntu 14.04 with deputy systemd does not connect to DBus - # and systemd-shim is really responding to "systemd" requests. - test -n "$(command -v busctl)" || ( echo "no busctl"; exit 1 ) - # /lib /usr/lib (if present) dbus-broker.service variant - test -f /lib/systemd/system/default.target || test -f /usr/lib/systemd/system/default.target || ( echo "no system default.target"; exit 1 ) - test -f /lib/systemd/system/dbus.socket || test -f /usr/lib/systemd/system/dbus.socket || ( echo "no system dbus.socket"; exit 1 ) - test -f /lib/systemd/system/dbus.service || test -f /usr/lib/systemd/system/dbus.service || test -f /lib/systemd/system/dbus-broker.service || ( echo "no system dbus{,-broker}.service"; exit 1 ) - echo "ok" - exit 0 - ;; - has-session-systemd-and-dbus) - # CentOS 7 and derivatives disabled systemd --user - # https://bugs.centos.org/view.php?id=8767 - test -n "$(command -v busctl)" || ( echo "no busctl"; exit 1 ) - # /lib /usr/lib (if present) dbus-broker.service variant - test -f /lib/systemd/user/default.target || test -f /usr/lib/systemd/user/default.target || ( echo "no user default.target"; exit 1) - test -f /lib/systemd/user/dbus.socket || test -f /usr/lib/systemd/user/dbus.socket || ( echo "no user dbus.socket"; exit 1 ) - test -f /lib/systemd/user/dbus.service || test -f /usr/lib/systemd/user/dbus.service || test -f /lib/systemd/user/dbus-broker.service || ( echo "no system dbus{,-broker}.service"; exit 1 ) - echo "ok" - exit 0 - ;; -esac - -if [ -z "$(command -v busctl)" ]; then - echo "tests.session requires busctl" >&2 - exit 1 -fi - -# This fixes a bug in some older Debian systems where /root/.profile contains -# unconditional invocation of mesg, which on non-tty shells prints "mesg -# ttyname failed inappropriate ioctl for device" which pollutes output from -# invoked programs. -# TODO: move this to spread wide project setup. -test -f /root/.profile && sed -i -e 's/mesg n .*true/tty -s \&\& mesg n/g' /root/.profile - -read -r uuid < /proc/sys/kernel/random/uuid -unit_name="tests.session-$uuid.service" -tmp_dir="/tmp/tests.session-$uuid" -mkdir "$tmp_dir" -mkfifo -m 0666 "$tmp_dir/dbus-monitor.pipe" "$tmp_dir/ready.pipe" "$tmp_dir/result.pipe" "$tmp_dir/stdin.pipe" "$tmp_dir/stdout.pipe" "$tmp_dir/stderr.pipe" -trap 'rm -rf $tmp_dir' EXIT - -# Run dbus-monitor waiting for systemd JobRemoved signal. Process the output -# with awk, forwarding the result of the service to a fifo. -monitor_expr="type='signal', sender='org.freedesktop.systemd1', interface='org.freedesktop.systemd1.Manager', path='/org/freedesktop/systemd1', member='JobRemoved'" -stdbuf -oL dbus-monitor --system --monitor "$monitor_expr" > "$tmp_dir/dbus-monitor.pipe" 2>"$tmp_dir/dbus-monitor.stderr" & -dbus_monitor_pid=$! - -awk_expr=" + # Run dbus-monitor waiting for systemd JobRemoved signal. Process the output + # with awk, forwarding the result of the service to a fifo. + monitor_expr="type='signal', sender='org.freedesktop.systemd1', interface='org.freedesktop.systemd1.Manager', path='/org/freedesktop/systemd1', member='JobRemoved'" + stdbuf -oL dbus-monitor --system --monitor "$monitor_expr" > "$tmp_dir/dbus-monitor.pipe" 2>"$tmp_dir/dbus-monitor.stderr" & + dbus_monitor_pid=$! + + awk_expr=" BEGIN { found=0; ready=0; @@ -281,106 +283,109 @@ BEGIN { } } " -awk -W interactive "$awk_expr" <"$tmp_dir/dbus-monitor.pipe" >"$tmp_dir/awk.log" 2>&1 & -awk_pid=$! - -# Wait for dbus-monitor to start. -cat "$tmp_dir/ready.pipe" >/dev/null - -# Use busctl to spawn a command. The command is wrapped in shell, runuser -l -# and redirects to capture output. Sadly busctl doesn't support passing file -# descriptors https://github.com/systemd/systemd/issues/14954 As a workaround -# we pass a set of pipes. This is good for non-interactive work. -# NOTE: /dev/stdin is provided explicitly to trigger special behavior in bash, -# as otherwise background tasks are started with /dev/null for stdin. -cat "$tmp_dir/stdin.pipe" & -cat_stdin_pid=$! -cat <"$tmp_dir/stdout.pipe" >&1 & -cat_stdout_pid=$! -cat <"$tmp_dir/stderr.pipe" >&2 & -cat_stderr_pid=$! - -( - echo "#!/bin/sh" - test -n "$pid_file" && echo "echo \$$ >\"$pid_file\"" - printf "exec " - for arg in "$@"; do - printf '%q ' "$arg" + awk -W interactive "$awk_expr" <"$tmp_dir/dbus-monitor.pipe" >"$tmp_dir/awk.log" 2>&1 & + awk_pid=$! + + # Wait for dbus-monitor to start. + cat "$tmp_dir/ready.pipe" >/dev/null + + # Use busctl to spawn a command. The command is wrapped in shell, runuser -l + # and redirects to capture output. Sadly busctl doesn't support passing file + # descriptors https://github.com/systemd/systemd/issues/14954 As a workaround + # we pass a set of pipes. This is good for non-interactive work. + # NOTE: /dev/stdin is provided explicitly to trigger special behavior in bash, + # as otherwise background tasks are started with /dev/null for stdin. + cat "$tmp_dir/stdin.pipe" & + cat_stdin_pid=$! + cat <"$tmp_dir/stdout.pipe" >&1 & + cat_stdout_pid=$! + cat <"$tmp_dir/stderr.pipe" >&2 & + cat_stderr_pid=$! + + ( + echo "#!/bin/sh" + test -n "$pid_file" && echo "echo \$$ >\"$pid_file\"" + printf "exec " + for arg in "$@"; do + printf '%q ' "$arg" + done + echo + )>"$tmp_dir/exec" + chmod +x "$tmp_dir/exec" + + # We want to provide a SELinuxContext but systemd in older SELinux-using distributions + # does not recognize this attribute. See https://github.com/systemd/systemd/blob/master/NEWS#L6402 + # The typical user context as reported by id -Z is: unconfined_u:unconfined_r:unconfined_t:s0 + selinux_context_arg= + case $SPREAD_SYSTEM in + fedora-*|centos-*) + if [ "$(systemctl --version | head -n 1 | awk '{ print $2 }')" -gt 219 ]; then + selinux_context_arg="SELinuxContext s unconfined_u:unconfined_r:unconfined_t:s0" + fi + ;; + esac + + # NOTE: This shellcheck directive is for the $selinux_context_arg expansion below. + # shellcheck disable=SC2086 + busctl \ + --allow-interactive-authorization=no --quiet \ + --system \ + -- \ + call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + StartTransientUnit "ssa(sv)a(sa(sv))" \ + "$unit_name" fail $(( 4 + $(test -n "$selinux_context_arg" && echo 1 || echo 0))) \ + Description s "tests.session running $* as $user" \ + Type s oneshot \ + Environment as 1 TERM=xterm-256color \ + $selinux_context_arg \ + ExecStart "a(sasb)" 1 \ + "$(command -v runuser)" 6 "$(command -v runuser)" -l "$user" - -c "SNAPD_DEBUG=$SNAPD_DEBUG SNAP_CONFINE_DEBUG=$SNAP_CONFINE_DEBUG exec $tmp_dir/exec <$tmp_dir/stdin.pipe >$tmp_dir/stdout.pipe 2>$tmp_dir/stderr.pipe" false \ + 0 + # This is done so that we can configure file redirects. Once Ubuntu 16.04 is no + # longer supported we can use Standard{Input,Output,Error}=file:path property + # of systemd, or perhaps even StandardInputFileDescriptor and pass a file + # descriptor to the FIFOs we open in the script above. + + # Wait for the service to terminate. The trigger is a successful read + # from the result FIFO. In case we are signaled in one of the familiar + # ways, kill the started service with the same signal. The reading occurs + # in a loop as the signal will first interrupt the read process, which will + # fail and return nothing. + trap 'systemctl kill --signal=INT $unit_name' INT + trap 'systemctl kill --signal=TERM $unit_name' TERM + trap 'systemctl kill --signal=QUIT $unit_name' QUIT + result="" + for sig in INT TERM QUIT; do + result=$(cat "$tmp_dir/result.pipe" 2>/dev/null) && break + trap - "$sig" done - echo -)>"$tmp_dir/exec" -chmod +x "$tmp_dir/exec" - -# We want to provide a SELinuxContext but systemd in older SELinux-using distributions -# does not recognize this attribute. See https://github.com/systemd/systemd/blob/master/NEWS#L6402 -# The typical user context as reported by id -Z is: unconfined_u:unconfined_r:unconfined_t:s0 -selinux_context_arg= -case $SPREAD_SYSTEM in - fedora-*|centos-*) - if [ "$(systemctl --version | head -n 1 | awk '{ print $2 }')" -gt 219 ]; then - selinux_context_arg="SELinuxContext s unconfined_u:unconfined_r:unconfined_t:s0" - fi - ;; -esac - -# NOTE: This shellcheck directive is for the $selinux_context_arg expansion below. -# shellcheck disable=SC2086 -busctl \ - --allow-interactive-authorization=no --quiet \ - --system \ - -- \ - call \ - org.freedesktop.systemd1 \ - /org/freedesktop/systemd1 \ - org.freedesktop.systemd1.Manager \ - StartTransientUnit "ssa(sv)a(sa(sv))" \ - "$unit_name" fail $(( 4 + $(test -n "$selinux_context_arg" && echo 1 || echo 0))) \ - Description s "tests.session running $* as $user" \ - Type s oneshot \ - Environment as 1 TERM=xterm-256color \ - $selinux_context_arg \ - ExecStart "a(sasb)" 1 \ - "$(command -v runuser)" 6 "$(command -v runuser)" -l "$user" - -c "SNAPD_DEBUG=$SNAPD_DEBUG SNAP_CONFINE_DEBUG=$SNAP_CONFINE_DEBUG exec $tmp_dir/exec <$tmp_dir/stdin.pipe >$tmp_dir/stdout.pipe 2>$tmp_dir/stderr.pipe" false \ - 0 -# This is done so that we can configure file redirects. Once Ubuntu 16.04 is no -# longer supported we can use Standard{Input,Output,Error}=file:path property -# of systemd, or perhaps even StandardInputFileDescriptor and pass a file -# descriptor to the FIFOs we open in the script above. - -# Wait for the service to terminate. The trigger is a successful read -# from the result FIFO. In case we are signaled in one of the familiar -# ways, kill the started service with the same signal. The reading occurs -# in a loop as the signal will first interrupt the read process, which will -# fail and return nothing. -trap 'systemctl kill --signal=INT $unit_name' INT -trap 'systemctl kill --signal=TERM $unit_name' TERM -trap 'systemctl kill --signal=QUIT $unit_name' QUIT -result="" -for sig in INT TERM QUIT; do - result=$(cat "$tmp_dir/result.pipe" 2>/dev/null) && break - trap - "$sig" -done -trap - INT TERM QUIT - -# Kill dbus-monitor that otherwise runs until it notices the pipe is no longer -# connected, which happens after a longer while. Redirect stderr to /dev/null -# to avoid upsetting tests which are sensitive to stderr, e.g. tests/main/document-portal-activation -kill $dbus_monitor_pid 2>/dev/null || true -wait $dbus_monitor_pid 2>/dev/null || true -wait $awk_pid - -wait "$cat_stdin_pid" -wait "$cat_stdout_pid" -wait "$cat_stderr_pid" - -# Prevent accumulation of failed sessions. - -if [ "$result" != '"done"' ]; then - systemctl reset-failed "$unit_name" -fi - -case "$result" in - '"done"') exit 0; ;; - '"failed"') exit 1; ;; - '"canceled"') exit 1; ;; -esac + trap - INT TERM QUIT + + # Kill dbus-monitor that otherwise runs until it notices the pipe is no longer + # connected, which happens after a longer while. Redirect stderr to /dev/null + # to avoid upsetting tests which are sensitive to stderr, e.g. tests/main/document-portal-activation + kill $dbus_monitor_pid 2>/dev/null || true + wait $dbus_monitor_pid 2>/dev/null || true + wait $awk_pid + + wait "$cat_stdin_pid" + wait "$cat_stdout_pid" + wait "$cat_stderr_pid" + + # Prevent accumulation of failed sessions. + + if [ "$result" != '"done"' ]; then + systemctl reset-failed "$unit_name" + fi + + case "$result" in + '"done"') exit 0; ;; + '"failed"') exit 1; ;; + '"canceled"') exit 1; ;; + esac +} + +main "$@" diff --git a/tests/lib/tools/to-one-line b/tests/lib/tools/to-one-line index 88b959e7fab..21eecf18110 100755 --- a/tests/lib/tools/to-one-line +++ b/tests/lib/tools/to-one-line @@ -8,13 +8,20 @@ to_one_line(){ echo "$1" | tr '\r\n' ' ' | tr -s ' ' } -if [ $# -eq 0 ]; then - show_help - exit -fi +main() { + if [ $# -eq 0 ]; then + show_help + exit + fi + case "${1:-}" in + -h|--help) + show_help + exit 0 + ;; + *) + to_one_line "$1" + ;; + esac +} -if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then - show_help -else - to_one_line "$1" -fi +main "$@"