diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 234deb1378..8f96cad659 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -11,6 +11,7 @@ #include "fd-util.h" #include "fs-util.h" #include "hashmap.h" +#include "io-util.h" #include "iovec-util.h" #include "list.h" #include "mkdir.h" @@ -148,6 +149,7 @@ Manager* manager_free(Manager *manager) { free(manager->worker_notify_socket_path); sd_device_monitor_unref(manager->monitor); + udev_ctrl_unref(manager->ctrl); sd_varlink_server_unref(manager->varlink_server); @@ -863,6 +865,105 @@ static int event_queue_insert(Manager *manager, sd_device *dev) { return 0; } +static int manager_serialize_events(Manager *manager) { + int r; + + assert(manager); + + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *storage = NULL; + r = device_monitor_new_full(&storage, MONITOR_GROUP_NONE, -EBADF); + if (r < 0) + return log_warning_errno(r, "Failed to create new device monitor instance: %m"); + + union sockaddr_union a; + r = device_monitor_get_address(storage, &a); + if (r < 0) + return log_warning_errno(r, "Failed to get address of device monitor socket: %m"); + + uint64_t n = 0; + LIST_FOREACH(event, event, manager->events) { + if (event->state != EVENT_QUEUED) + continue; + + r = device_monitor_send(storage, &a, event->dev); + if (r < 0) { + log_device_warning_errno(event->dev, r, "Failed to save event to socket storage, ignoring: %m"); + continue; + } + + n++; + } + + if (n == 0) + return 0; + + r = notify_push_fd(sd_device_monitor_get_fd(storage), "event-serialization"); + if (r < 0) + return log_warning_errno(r, "Failed to push event serialization fd to service manager: %m"); + + log_debug("Serialized %"PRIu64" events.", n); + return 0; +} + +static int manager_deserialize_events(Manager *manager, int *fd) { + int r; + + assert(manager); + assert(fd); + assert(*fd >= 0); + + /* This may take and invalidate passed file descriptor even on failure. */ + + /* At this stage, we have not receive any events from the kernel, hence should be empty. */ + if (manager->events) + return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple event storage socket (%i).", *fd); + + r = sd_is_socket(*fd, AF_NETLINK, SOCK_RAW, /* listening = */ -1); + if (r < 0) + return log_warning_errno(r, "Failed to verify type of event storage socket (%i): %m", *fd); + if (r == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Received invalid event storage socket (%i).", *fd); + + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *storage = NULL; + r = device_monitor_new_full(&storage, MONITOR_GROUP_NONE, *fd); + if (r < 0) + return log_warning_errno(r, "Failed to initialize event storage: %m"); + TAKE_FD(*fd); + + r = device_monitor_allow_unicast_sender(storage, storage); + if (r < 0) + return log_warning_errno(r, "Failed to set trusted sender for event storage: %m"); + + uint64_t n = 0; + for (;;) { + r = fd_wait_for_event(sd_device_monitor_get_fd(storage), POLLIN, 0); + if (r == -EINTR) + continue; + if (r < 0) + return log_warning_errno(r, "Failed to wait for event from event storage: %m"); + if (r == 0) + break; + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_monitor_receive(storage, &dev); + if (r < 0) { + log_warning_errno(r, "Failed to receive device from event storage, ignoring: %m"); + continue; + } + + r = event_queue_insert(manager, dev); + if (r < 0) { + log_device_warning_errno(dev, r, "Failed to insert device into event queue, ignoring: %m"); + continue; + } + + n++; + } + + log_debug("Deserialized %"PRIu64" events.", n); + return 0; +} + static int on_uevent(sd_device_monitor *monitor, sd_device *dev, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; @@ -1077,6 +1178,8 @@ static int on_post_exit(Manager *manager) { if (!hashmap_isempty(manager->workers)) return 0; /* There still exist running workers. */ + (void) manager_serialize_events(manager); + udev_watch_dump(); return sd_event_exit(manager->event, 0); } @@ -1181,6 +1284,8 @@ static int manager_listen_fds(Manager *manager) { r = manager_init_inotify(manager, fd); else if (streq(names[i], "config-serialization")) r = manager_deserialize_config(manager, &fd); + else if (streq(names[i], "event-serialization")) + r = manager_deserialize_events(manager, &fd); else r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Received unexpected fd (%s), ignoring.", names[i]); @@ -1248,6 +1353,11 @@ int manager_main(Manager *manager) { _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING); + /* We will start processing events in the loop below. Before starting processing, let's remove the + * event serialization fd from the fdstore, to avoid retrieving the serialized events again in future + * invocations. Otherwise, the serialized events may be processed multiple times. */ + (void) notify_remove_fd_warn("event-serialization"); + r = sd_event_loop(manager->event); if (r < 0) return log_error_errno(r, "Event loop failed: %m"); diff --git a/test/units/TEST-17-UDEV.queued-events-serialization.sh b/test/units/TEST-17-UDEV.queued-events-serialization.sh new file mode 100755 index 0000000000..254cc0a2db --- /dev/null +++ b/test/units/TEST-17-UDEV.queued-events-serialization.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Test for queued events serialization on stop and deserialization on start. + +rules="/run/udev/rules.d/99-test-17.serialization.rules" + +mkdir -p "${rules%/*}" +cat > "$rules" <<'EOF' +SUBSYSTEM!="mem", GOTO="end" +KERNEL!="null", GOTO="end" +ACTION=="remove", GOTO="end" + +IMPORT{db}="INVOCATIONS" +IMPORT{program}="/bin/bash -c 'systemctl show --property=InvocationID systemd-udevd.service'" +ENV{INVOCATIONS}+="%E{ACTION}_%E{SEQNUM}_%E{InvocationID}" +ACTION=="add", RUN+="/bin/bash -c ':> /tmp/marker'", RUN+="/usr/bin/sleep 10" + +LABEL="end" +EOF + +udevadm control --reload --log-level=debug + +udevadm settle --timeout 30 +rm -f /tmp/marker + +# Save the current invocation ID of udevd. +INVOCATION_BEFORE="$(systemctl show --property=InvocationID --value systemd-udevd.service)" + +# Trigger several actions. +# The first 'add' event should be processed by the current invocation. +for action in remove add change change change change; do + udevadm trigger --action="$action" /dev/null +done + +# While processing the first 'add' event, request restarting udevd. +# Hence, the queued subsequent 'change' events should be serialized, +# then deserialized, requeued, and processed in the next invocation. +timeout 10 bash -c 'until test -e /tmp/marker; do sleep .1; done' +systemctl restart systemd-udevd.service +udevadm settle --timeout 30 + +# Get the invocation ID of the restarted udevd. +INVOCATION_AFTER="$(systemctl show --property=InvocationID --value systemd-udevd.service)" + +udevadm info --no-pager /dev/null + +# Check the properties that records action, seqnum, and invocation ID. +previous_seqnum=0 +expected_action=add +expected_invocation="$INVOCATION_BEFORE" +count=0 +while read -r action seqnum invocation; do + test "$seqnum" -gt "$previous_seqnum" + assert_eq "$action" "$expected_action" + assert_eq "$invocation" "$expected_invocation" + + previous_seqnum="$seqnum" + expected_action=change + expected_invocation="$INVOCATION_AFTER" + : $((++count)) +done < <(udevadm info -q property --property=INVOCATIONS --value /dev/null | sed -e 's/ /\n/g; s/_/ /g') + +# Check the total number of events processed by the udevd. +assert_eq "$count" "5" + +rm -f "$rules" /tmp/marker +udevadm control --reload + +exit 0