diff --git a/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf b/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf new file mode 100644 index 0000000000..ab3687743c --- /dev/null +++ b/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/test/meson.build b/src/test/meson.build index da3148d955..d0d4f25f17 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -315,6 +315,10 @@ executables += [ 'dependencies' : libopenssl_cflags, 'conditions' : ['HAVE_OPENSSL'], }, + test_template + { + 'sources' : files('test-dbus-name-holder.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-display-quota.c'), 'type' : 'manual', diff --git a/src/test/test-dbus-name-holder.c b/src/test/test-dbus-name-holder.c new file mode 100644 index 0000000000..011ce782ea --- /dev/null +++ b/src/test/test-dbus-name-holder.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-daemon.h" + +#include "log.h" +#include "main-func.h" +#include "time-util.h" + +static int run(int argc, char *argv[]) { + int r; + + if (argc < 3 || argc > 5) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Usage: %s BUSNAME READY_MARKER [RELEASE_MARKER] [NOTIFY_MARKER]", + program_invocation_short_name); + + const char + *name = argv[1], + *marker = argv[2], + *release_marker = argc > 3 ? argv[3] : NULL, + *notify_marker = argc > 4 ? argv[4] : NULL; + + for (;;) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_request_name(bus, name, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to acquire dbus name '%s': %m", name); + + log_info("Acquired dbus name '%s'.", name); + + for (;;) { + r = sd_bus_process(bus, /* ret= */ NULL); + if (r < 0) + break; + if (r > 0) + continue; + + if (release_marker && access(release_marker, F_OK) >= 0) { + r = sd_bus_release_name(bus, name); + if (r < 0) + return log_error_errno(r, "Failed to release dbus name '%s': %m", name); + + log_info("Released dbus name '%s'.", name); + release_marker = NULL; /* go back to blocking until we're killed */ + } + + r = sd_bus_wait(bus, release_marker ? 100 * USEC_PER_MSEC : USEC_INFINITY); + if (r < 0) + break; + } + + log_info("Disconnected from bus, reconnecting..."); + + while (access(marker, F_OK) < 0) { + if (notify_marker && access(notify_marker, F_OK) >= 0) { + r = sd_notify(/* unset_environment= */ false, "RELOADING=1\nREADY=1"); + if (r < 0) + return log_error_errno(r, "Failed to send reload notification: %m"); + + log_info("Sent reload notification."); + notify_marker = NULL; + } + + usleep_safe(50 * USEC_PER_MSEC); + } + } +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh b/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh new file mode 100755 index 0000000000..9a64131cd5 --- /dev/null +++ b/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +holder=/usr/lib/systemd/tests/unit-tests/manual/test-dbus-name-holder +name=systemd.test.DBusReconnect +marker=/run/dbus-reconnect-go +release=/run/dbus-reconnect-release +notify=/run/dbus-reconnect-notify +reloaded=/run/dbus-reconnect-reloaded + +at_exit() { + rm -f "$marker" "$release" "$notify" "$reloaded" + systemctl stop \ + dbus-reconnect-recover.service \ + dbus-reconnect-stop.service \ + dbus-reconnect-exit.service \ + dbus-reconnect-drop.service \ + dbus-reconnect-notify.service \ + dbus-reconnect-notify-target.service \ + dbus-reconnect-reexec.service 2>/dev/null || : +} + +trap at_exit EXIT + +# Poll until property $2 of unit $1 reaches value $3 +poll() { + local unit="${1:?}" prop="${2:?}" want="${3:?}" got i + for ((i = 0; i < 150; i++)); do + got="$(systemctl show -p "$prop" --value "$unit")" + if [[ "$got" == "$want" ]]; then + return 0 + fi + sleep 0.2 + done + echo "poll: timed out waiting for $unit $prop=$want (last: $got)" >&2 + return 1 +} + +reset_failed() { + local unit="${1:?}" + + systemctl reset-failed "$unit" 2>/dev/null || : +} + +restart_broker() { + systemctl kill --signal=KILL dbus.service + timeout 90 bash -c ' + until busctl --system status >/dev/null 2>&1; do + systemctl reset-failed dbus.service dbus.socket 2>/dev/null + systemctl start dbus.socket 2>/dev/null + sleep 0.2 + done' +} + +enter_revalidation() { + local unit="${1:?}" + + shift + reset_failed "$unit" + rm -f "$marker" + systemd-run \ + --unit="$unit" \ + -p Type=dbus \ + -p BusName="$name" \ + -p Restart=no \ + "$@" \ + "$holder" "$name" "$marker" + restart_broker + poll "$unit" SubState running-revalidating +} + +# the name comes back within the grace window +unit=dbus-reconnect-recover.service +enter_revalidation "$unit" +main_pid="$(systemctl show -p MainPID --value "$unit")" +assert_neq "$main_pid" "0" +touch "$marker" +poll "$unit" SubState running +assert_eq "$(systemctl show -p MainPID --value "$unit")" "$main_pid" +systemctl stop "$unit" + +# the name never comes back +unit=dbus-reconnect-stop.service +enter_revalidation "$unit" +poll "$unit" ActiveState inactive + +# ditto with RemainAfterExit=yes +unit=dbus-reconnect-exit.service +enter_revalidation "$unit" -p RemainAfterExit=yes +poll "$unit" SubState exited +systemctl stop "$unit" + +# the name is dropped on a live connection and we action it at once +unit=dbus-reconnect-drop.service +reset_failed "$unit" +rm -f "$release" +systemd-run \ + --unit="$unit" \ + -p Type=dbus \ + -p BusName="$name" \ + -p Restart=no \ + "$holder" "$name" "$marker" "$release" +poll "$unit" SubState running +touch "$release" +for ((i = 0; i < 150; i++)); do + sub_state="$(systemctl show -p SubState --value "$unit")" + if [[ "$sub_state" == running-revalidating ]]; then + echo "live drop entered the revalidation window" >&2 + exit 1 + fi + + active_state="$(systemctl show -p ActiveState --value "$unit")" + if [[ "$active_state" == inactive ]]; then + break + fi + + sleep 0.2 +done +assert_eq "$(systemctl show -p ActiveState --value "$unit")" inactive + +# a reload notification is propagated even while the name is absent (revalidating) +target=dbus-reconnect-notify-target.service +reset_failed "$target" +systemd-run \ + --unit="$target" \ + -p Type=oneshot \ + -p RemainAfterExit=yes \ + -p "ExecReload=/usr/bin/touch $reloaded" \ + /usr/bin/true + +unit=dbus-reconnect-notify.service +reset_failed "$unit" +rm -f "$marker" "$release" "$notify" "$reloaded" +systemd-run \ + --unit="$unit" \ + -p Type=dbus \ + -p BusName="$name" \ + -p Restart=no \ + -p NotifyAccess=main \ + -p PropagatesReloadTo="$target" \ + "$holder" "$name" "$marker" "$release" "$notify" +restart_broker +poll "$unit" SubState running-revalidating +touch "$notify" +timeout 30 bash -c "until [[ -e '$reloaded' ]]; do sleep 0.2; done" +touch "$marker" +poll "$unit" SubState running +systemctl stop "$unit" "$target" + +# the revalidation state survives a daemon-reexec and still recovers afterwards +unit=dbus-reconnect-reexec.service +enter_revalidation "$unit" +systemctl daemon-reexec +touch "$marker" +poll "$unit" SubState running +systemctl stop "$unit"