mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
This is the first part of the normalization done for test tools. The idea is to keep as similar as possible all the test tools regarding imlpementation and usage.
392 lines
13 KiB
Bash
Executable File
392 lines
13 KiB
Bash
Executable File
#!/bin/bash -e
|
|
|
|
show_help() {
|
|
echo "usage: tests.session exec [-u USER] [-p PID_FILE] [--] <CMD>"
|
|
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"
|
|
}
|
|
|
|
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
|
|
;;
|
|
*)
|
|
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)
|
|
# 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
|
|
|
|
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)
|
|
# 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)
|
|
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="
|
|
BEGIN {
|
|
found=0;
|
|
ready=0;
|
|
}
|
|
|
|
# Once we get the NameAcquired message we are sure dbus-monitor is connected
|
|
# and will receive JobRemoved once it is sent. The reason we are getting this
|
|
# message despite the filter we establish above is that it is sent directly to
|
|
# us, which bypasses DBus filter expressions.
|
|
/member=NameAcquired/ {
|
|
if (!ready) {
|
|
ready=1;
|
|
print ready > \"$tmp_dir/ready.pipe\";
|
|
close(\"$tmp_dir/ready.pipe\");
|
|
}
|
|
}
|
|
|
|
# This part matches an argument to JobRemoved that contains the name of the
|
|
# tests.session-xxx.service name we picked earlier. Once we see this we are sure
|
|
# the job is gone.
|
|
/ string \"$unit_name\"/ {
|
|
found=1;
|
|
print \"found service file\";
|
|
fflush();
|
|
next;
|
|
}
|
|
|
|
# This matches any string argument but only takes effect once we found our
|
|
# JobRemoved message. The order of arguments to JobRemoved is such that the
|
|
# immediate successor of the service name is the result word. This is
|
|
# translated to a pass / fail exit code from tests.session. Scanning this part
|
|
# terminates awk.
|
|
/string \".*\"/ {
|
|
if (found==1) {
|
|
print \"found result\";
|
|
print \$2 > \"$tmp_dir/result.pipe\";
|
|
fflush(\"\");
|
|
close(\"$tmp_dir/result.pipe\");
|
|
exit;
|
|
}
|
|
}
|
|
"
|
|
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 </dev/stdin >"$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
|
|
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 "$@"
|