Add a new integration test to verify the signal handler validation for Type=notify-reload services, as introduced in the previous commit. The test adds a shell harness and four service files to cover all scenarios: 1. `notify-reload-no-handler.service`: Verifies that the service fails to start with Result=protocol when the handler is missing at the first READY=1. 2. `notify-reload-sigstop.service`: Verifies that SIGSTOP is exempt from handler validation because it cannot be caught or blocked. 3. `notify-reload-toggle-handler.service`: Verifies that removing the handler at runtime results in a warning on reload, but the signal is still sent, causing the service to terminate from unhandled SIGHUP. 4. `notify-reload-well-behaved.service`: Verifies the happy path where a service with a handler starts and reloads successfully. Use bounded FIFO operations and journal cursors so failures are prompt and log assertions cannot race a relative time window.
257 lines
11 KiB
Bash
Executable File
257 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
# shellcheck disable=SC2016
|
|
set -eux
|
|
set -o pipefail
|
|
|
|
# shellcheck source=test/units/util.sh
|
|
. "$(dirname "$0")"/util.sh
|
|
|
|
mkfifo /tmp/syncfifo1 /tmp/syncfifo2
|
|
|
|
sync_in() {
|
|
read -r x </tmp/syncfifo1
|
|
test "$x" = "$1"
|
|
}
|
|
|
|
sync_out() {
|
|
echo "$1" >/tmp/syncfifo2
|
|
}
|
|
|
|
export SYSTEMD_LOG_LEVEL=debug
|
|
|
|
# Test NotifyAccess= override through sd_notify()
|
|
|
|
systemctl --no-block start notify.service
|
|
|
|
sync_in a
|
|
|
|
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
|
|
assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts"
|
|
|
|
sync_out b
|
|
sync_in c
|
|
|
|
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "main"
|
|
assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unprivileged process"
|
|
assert_rc 3 systemctl --quiet is-active notify.service
|
|
|
|
sync_out d
|
|
sync_in e
|
|
|
|
systemctl --quiet is-active notify.service
|
|
[[ "$(systemctl show notify.service -P StatusText)" != BOGUS* ]]
|
|
|
|
assert_eq "$(systemctl show notify.service -P StatusErrno)" "1"
|
|
assert_eq "$(systemctl show notify.service -P StatusBusError)" "org.freedesktop.DBus.Error.InvalidArgs"
|
|
assert_eq "$(systemctl show notify.service -P StatusVarlinkError)" "org.varlink.service.InvalidParameter"
|
|
|
|
sync_out f
|
|
sync_in g
|
|
|
|
assert_eq "$(systemctl show notify.service -P StatusErrno)" "1"
|
|
assert_eq "$(systemctl show notify.service -P StatusBusError)" "org.freedesktop.DBus.Error.InvalidArgs"
|
|
assert_eq "$(systemctl show notify.service -P StatusVarlinkError)" "org.varlink.service.InvalidParameter"
|
|
|
|
sync_out h
|
|
sync_in i
|
|
|
|
assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK"
|
|
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none"
|
|
|
|
systemctl stop notify.service
|
|
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
|
|
|
|
# Timeout of READY=1 for Type=notify-reload services (issue #37515)
|
|
|
|
systemctl start reload-timeout.service
|
|
|
|
systemctl reload --no-block reload-timeout.service
|
|
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
|
|
sync_in hup1
|
|
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
|
|
timeout 80 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 5; done'
|
|
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "timeout"
|
|
|
|
systemctl reload --no-block reload-timeout.service
|
|
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
|
|
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
|
|
sync_in hup2
|
|
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
|
|
sync_out ready
|
|
timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
|
|
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
|
|
|
|
systemctl reload --no-block reload-timeout.service
|
|
sync_in hup3
|
|
timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
|
|
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
|
|
|
|
systemctl stop reload-timeout.service
|
|
|
|
rm /tmp/syncfifo1 /tmp/syncfifo2
|
|
|
|
# Explicitly test busctl's BUSERROR= reporting and systemctl status should show it
|
|
|
|
(! systemd-run --wait --unit="TEST-80-BUSERROR.service" -p NotifyAccess=main busctl introspect org.freedesktop.systemd1 /bogus/001)
|
|
assert_eq "$(systemctl show TEST-80-BUSERROR.service -P StatusBusError)" "org.freedesktop.DBus.Error.UnknownObject"
|
|
assert_in "D-Bus: org.freedesktop.DBus.Error.UnknownObject" "$(systemctl status TEST-80-BUSERROR.service)"
|
|
|
|
# Now test basic fdstore behaviour
|
|
|
|
MYSCRIPT="/tmp/myscript$RANDOM.sh"
|
|
cat >>"$MYSCRIPT" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -eux
|
|
set -o pipefail
|
|
test "$FDSTORE" -eq 7
|
|
N="/tmp/$RANDOM"
|
|
echo $RANDOM >"$N"
|
|
systemd-notify --fd=4 --fdname=quux --pid=parent 4<"$N"
|
|
rm "$N"
|
|
systemd-notify --ready
|
|
exec sleep infinity
|
|
EOF
|
|
|
|
chmod +x "$MYSCRIPT"
|
|
|
|
MYUNIT="myunit$RANDOM.service"
|
|
systemd-run -u "$MYUNIT" -p Type=notify -p FileDescriptorStoreMax=7 "$MYSCRIPT"
|
|
|
|
test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2
|
|
systemd-analyze fdstore "$MYUNIT" --json=short
|
|
systemd-analyze fdstore "$MYUNIT" --json=short | grep -P '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]' >/dev/null
|
|
|
|
systemctl stop "$MYUNIT"
|
|
rm "$MYSCRIPT"
|
|
|
|
# Test fdstore pinning (this will pull in fdstore-pin.service fdstore-nopin.service)
|
|
systemctl start fdstore-pin.target
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P FileDescriptorStorePreserve)" yes
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P FileDescriptorStorePreserve)" restart
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
|
|
|
|
# The file descriptor store should survive service restarts
|
|
systemctl restart fdstore-pin.service fdstore-nopin.service
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
|
|
|
# It should not survive the service stop plus a later start (unless pinned)
|
|
systemctl stop fdstore-pin.service fdstore-nopin.service
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
|
|
|
systemctl start fdstore-pin.service fdstore-nopin.service
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
|
|
|
systemctl stop fdstore-pin.service fdstore-nopin.service
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
|
|
|
systemctl clean fdstore-pin.service --what=fdstore
|
|
|
|
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 0
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
|
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead
|
|
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
|
|
|
# Test notify-reload signal handler validation
|
|
|
|
cleanup_notify_reload() {
|
|
systemctl stop notify-reload-no-handler.service \
|
|
notify-reload-sigstop.service \
|
|
notify-reload-toggle-handler.service \
|
|
notify-reload-well-behaved.service || :
|
|
systemctl reset-failed notify-reload-no-handler.service \
|
|
notify-reload-sigstop.service \
|
|
notify-reload-toggle-handler.service \
|
|
notify-reload-well-behaved.service || :
|
|
rm -f /tmp/reload-nohandler-in /tmp/reload-nohandler-out \
|
|
/tmp/reload-nohandler-cursor \
|
|
/tmp/reload-sigstop-in /tmp/reload-sigstop-out \
|
|
/tmp/reload-toggle-in /tmp/reload-toggle-out \
|
|
/tmp/reload-toggle-cursor \
|
|
/tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
|
|
}
|
|
|
|
trap cleanup_notify_reload EXIT
|
|
cleanup_notify_reload
|
|
|
|
# Test 1: Service without signal handler should fail to start
|
|
mkfifo /tmp/reload-nohandler-in /tmp/reload-nohandler-out
|
|
journalctl -q -n 0 --cursor-file=/tmp/reload-nohandler-cursor
|
|
(! systemctl start notify-reload-no-handler.service)
|
|
assert_eq "$(systemctl show notify-reload-no-handler.service -P SubState)" "failed"
|
|
assert_eq "$(systemctl show notify-reload-no-handler.service -P Result)" "protocol"
|
|
# Verify error was logged about missing signal handler
|
|
journalctl --sync
|
|
journalctl -u notify-reload-no-handler.service --cursor-file=/tmp/reload-nohandler-cursor \
|
|
--grep "lacks handler for reload signal" >/dev/null
|
|
systemctl reset-failed notify-reload-no-handler.service
|
|
rm -f /tmp/reload-nohandler-in /tmp/reload-nohandler-out
|
|
|
|
# Test 2: SIGSTOP cannot be caught or blocked, so it is exempt from handler validation
|
|
mkfifo /tmp/reload-sigstop-in /tmp/reload-sigstop-out
|
|
systemctl start notify-reload-sigstop.service
|
|
assert_eq "$(systemctl show notify-reload-sigstop.service -P SubState)" "running"
|
|
systemctl stop notify-reload-sigstop.service
|
|
rm -f /tmp/reload-sigstop-in /tmp/reload-sigstop-out
|
|
|
|
# Test 3: Service that removes handler should warn on reload but still reload
|
|
mkfifo /tmp/reload-toggle-in /tmp/reload-toggle-out
|
|
systemctl start notify-reload-toggle-handler.service
|
|
assert_eq "$(systemctl show notify-reload-toggle-handler.service -P SubState)" "running"
|
|
|
|
# Command service to remove its signal handler
|
|
timeout 10 bash -c 'echo "remove-handler" >/tmp/reload-toggle-in'
|
|
response="$(timeout 10 cat /tmp/reload-toggle-out)"
|
|
assert_eq "$response" "handler-removed"
|
|
|
|
# Reload should succeed (with warning) even though handler is gone - service will be killed by SIGHUP
|
|
# but that's intentional after the first start succeeds
|
|
journalctl -q -n 0 --cursor-file=/tmp/reload-toggle-cursor
|
|
systemctl reload --no-block notify-reload-toggle-handler.service
|
|
# The service will die from SIGHUP since the handler is removed
|
|
timeout 10 bash -c 'while systemctl is-active --quiet notify-reload-toggle-handler.service; do sleep .5; done'
|
|
# Verify warning was logged
|
|
journalctl --sync
|
|
journalctl -u notify-reload-toggle-handler.service --cursor-file=/tmp/reload-toggle-cursor \
|
|
--grep "lacks handler for reload signal" >/dev/null
|
|
rm -f /tmp/reload-toggle-in /tmp/reload-toggle-out
|
|
|
|
# Test 4: Well-behaved service with handler should work correctly
|
|
mkfifo /tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
|
|
systemctl start notify-reload-well-behaved.service
|
|
assert_eq "$(systemctl show notify-reload-well-behaved.service -P SubState)" "running"
|
|
|
|
# Reload should succeed
|
|
systemctl reload --no-block notify-reload-well-behaved.service
|
|
timeout 10 bash -c 'until [[ $(systemctl show notify-reload-well-behaved.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
|
|
response="$(timeout 10 cat /tmp/reload-wellbehaved-out)"
|
|
assert_eq "$response" "reload"
|
|
timeout 10 bash -c 'until [[ $(systemctl show notify-reload-well-behaved.service -P SubState) == "running" ]]; do sleep .5; done'
|
|
assert_eq "$(systemctl show notify-reload-well-behaved.service -P ReloadResult)" "success"
|
|
|
|
timeout 10 bash -c 'echo "exit" >/tmp/reload-wellbehaved-in'
|
|
systemctl stop notify-reload-well-behaved.service
|
|
rm -f /tmp/reload-wellbehaved-in /tmp/reload-wellbehaved-out
|
|
|
|
touch /testok
|