udev: serialize queued events on exit (#37047)
Previously, all queued events were discarded on exit, hence several events might not be processed by udevd when it is restarted. Such situation especially easily happens on switching root. This makes queued events serialized on exit, and deserialized in the next invocation. Hence, no events should be lost during restarting udevd. This is important with the several aspects. Basically systemd-udev-trigger.service (re)triggers all devices anyway after switching root, But the service may be disabled or modified by admin. Moreover, the service produces only 'add' events, and thus the service cannot cover events with other actions generated by the kernel during switching root. Also, the userspace triggered events may not contain some parameters compared with events triggered by the kernel.
This commit is contained in:
@@ -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");
|
||||
|
||||
+75
@@ -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
|
||||
Reference in New Issue
Block a user