mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
udev-watch: push inotify fd to service manager's fdstore (#36977)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "blockdev-util.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "common-signal.h"
|
||||
#include "daemon-util.h"
|
||||
@@ -12,7 +11,6 @@
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "inotify-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "list.h"
|
||||
#include "mkdir.h"
|
||||
@@ -705,7 +703,7 @@ fail:
|
||||
event_free(event);
|
||||
}
|
||||
|
||||
static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device *dev) {
|
||||
int event_queue_assume_block_device_unlocked(Manager *manager, sd_device *dev) {
|
||||
const char *devname;
|
||||
int r;
|
||||
|
||||
@@ -858,162 +856,6 @@ static int on_worker_notify(sd_event_source *s, int fd, uint32_t revents, void *
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synthesize_change_one(sd_device *dev, sd_device *target) {
|
||||
int r;
|
||||
|
||||
assert(dev);
|
||||
assert(target);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
const char *syspath = NULL;
|
||||
(void) sd_device_get_syspath(target, &syspath);
|
||||
log_device_debug(dev, "device is closed, synthesising 'change' on %s", strna(syspath));
|
||||
}
|
||||
|
||||
r = sd_device_trigger(target, SD_DEVICE_CHANGE);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(target, r, "Failed to trigger 'change' uevent: %m");
|
||||
|
||||
DEVICE_TRACE_POINT(synthetic_change_event, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synthesize_change_all(sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(dev);
|
||||
|
||||
r = blockdev_reread_partition_table(dev);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m");
|
||||
bool part_table_read = r >= 0;
|
||||
|
||||
/* search for partitions */
|
||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||
r = partition_enumerator_new(dev, &e);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator, ignoring: %m");
|
||||
|
||||
/* We have partitions and re-read the table, the kernel already sent out a "change"
|
||||
* event for the disk, and "remove/add" for all partitions. */
|
||||
if (part_table_read && sd_device_enumerator_get_device_first(e))
|
||||
return 0;
|
||||
|
||||
/* We have partitions but re-reading the partition table did not work, synthesize
|
||||
* "change" for the disk and all partitions. */
|
||||
r = synthesize_change_one(dev, dev);
|
||||
FOREACH_DEVICE(e, d)
|
||||
RET_GATHER(r, synthesize_change_one(dev, d));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int synthesize_change_child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
assert(s);
|
||||
|
||||
sd_event_source_unref(set_remove(manager->synthesize_change_child_event_sources, s));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synthesize_change(Manager *manager, sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(dev);
|
||||
|
||||
const char *sysname;
|
||||
r = sd_device_get_sysname(dev, &sysname);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (startswith(sysname, "dm-") || block_device_is_whole_disk(dev) <= 0)
|
||||
return synthesize_change_one(dev, dev);
|
||||
|
||||
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
|
||||
r = pidref_safe_fork(
|
||||
"(udev-synth)",
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE,
|
||||
&pidref);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* child */
|
||||
(void) synthesize_change_all(dev);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
r = event_add_child_pidref(manager->event, &s, &pidref, WEXITED, synthesize_change_child_handler, manager);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to add child event source for "PID_FMT", ignoring: %m", pidref.pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = sd_event_source_set_child_pidfd_own(s, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PIDREF(pidref);
|
||||
|
||||
r = set_ensure_put(&manager->synthesize_change_child_event_sources, &event_source_hash_ops, s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_inotify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
union inotify_event_buffer buffer;
|
||||
ssize_t l;
|
||||
int r;
|
||||
|
||||
l = read(fd, &buffer, sizeof(buffer));
|
||||
if (l < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to read inotify fd: %m");
|
||||
}
|
||||
|
||||
FOREACH_INOTIFY_EVENT_WARN(e, buffer, l) {
|
||||
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||
const char *devnode;
|
||||
|
||||
/* Do not handle IN_IGNORED here. Especially, do not try to call udev_watch_end() from the
|
||||
* main process. Otherwise, the pair of the symlinks may become inconsistent, and several
|
||||
* garbage may remain. The old symlinks are removed by a worker that processes the
|
||||
* corresponding 'remove' uevent;
|
||||
* udev_event_execute_rules() -> event_execute_rules_on_remove() -> udev_watch_end(). */
|
||||
|
||||
if (!FLAGS_SET(e->mask, IN_CLOSE_WRITE))
|
||||
continue;
|
||||
|
||||
r = device_new_from_watch_handle(&dev, e->wd);
|
||||
if (r < 0) {
|
||||
/* Device may be removed just after closed. */
|
||||
log_debug_errno(r, "Failed to create sd_device object from watch handle, ignoring: %m");
|
||||
continue;
|
||||
}
|
||||
|
||||
r = sd_device_get_devname(dev, &devnode);
|
||||
if (r < 0) {
|
||||
/* Also here, device may be already removed. */
|
||||
log_device_debug_errno(dev, r, "Failed to get device node, ignoring: %m");
|
||||
continue;
|
||||
}
|
||||
|
||||
log_device_debug(dev, "Received inotify event for %s.", devnode);
|
||||
|
||||
(void) event_queue_assume_block_device_unlocked(manager, dev);
|
||||
(void) synthesize_change(manager, dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
|
||||
@@ -1123,8 +965,10 @@ static int on_post(sd_event_source *s, void *userdata) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to disable timer event source for cleaning up idle workers, ignoring: %m");
|
||||
|
||||
if (manager->exit)
|
||||
if (manager->exit) {
|
||||
udev_watch_dump();
|
||||
return sd_event_exit(manager->event, 0);
|
||||
}
|
||||
|
||||
if (manager->cgroup && set_isempty(manager->synthesize_change_child_event_sources))
|
||||
/* cleanup possible left-over processes in our cgroup */
|
||||
@@ -1199,6 +1043,8 @@ static int manager_listen_fds(Manager *manager) {
|
||||
r = manager_init_ctrl(manager, fd);
|
||||
else if (streq(names[i], "systemd-udevd-kernel.socket"))
|
||||
r = manager_init_device_monitor(manager, fd);
|
||||
else if (streq(names[i], "inotify"))
|
||||
r = manager_init_inotify(manager, fd);
|
||||
else
|
||||
r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Received unexpected fd (%s), ignoring.", names[i]);
|
||||
@@ -1252,31 +1098,6 @@ static int manager_start_device_monitor(Manager *manager) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_start_inotify(Manager *manager) {
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(manager->event);
|
||||
|
||||
fd = inotify_init1(IN_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to create inotify descriptor: %m");
|
||||
|
||||
udev_watch_restore(fd);
|
||||
|
||||
r = sd_event_add_io(manager->event, &s, fd, EPOLLIN, on_inotify, manager);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create inotify event source: %m");
|
||||
|
||||
(void) sd_event_source_set_description(s, "manager-inotify");
|
||||
|
||||
manager->inotify_fd = TAKE_FD(fd);
|
||||
manager->inotify_event = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_start_worker_notify(Manager *manager) {
|
||||
int r;
|
||||
|
||||
|
||||
@@ -66,3 +66,5 @@ void notify_ready(Manager *manager);
|
||||
void manager_kill_workers(Manager *manager, bool force);
|
||||
|
||||
bool devpath_conflict(const char *a, const char *b);
|
||||
|
||||
int event_queue_assume_block_device_unlocked(Manager *manager, sd_device *dev);
|
||||
|
||||
@@ -4,23 +4,26 @@
|
||||
* Copyright © 2009 Scott James Remnant <scott@netsplit.com>
|
||||
*/
|
||||
|
||||
#include <sys/inotify.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "device-private.h"
|
||||
#include "blockdev-util.h"
|
||||
#include "daemon-util.h"
|
||||
#include "device-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "event-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "inotify-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "parse-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "udev-manager.h"
|
||||
#include "udev-trace.h"
|
||||
#include "udev-util.h"
|
||||
#include "udev-watch.h"
|
||||
|
||||
int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd) {
|
||||
static int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd) {
|
||||
char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)];
|
||||
_cleanup_free_ char *id = NULL;
|
||||
int r;
|
||||
@@ -43,7 +46,242 @@ int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd) {
|
||||
return sd_device_new_from_device_id(ret, id);
|
||||
}
|
||||
|
||||
int udev_watch_restore(int inotify_fd) {
|
||||
void udev_watch_dump(void) {
|
||||
int r;
|
||||
|
||||
_cleanup_closedir_ DIR *dir = opendir("/run/udev/watch/");
|
||||
if (!dir)
|
||||
return (void) log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
|
||||
"Failed to open old watches directory '/run/udev/watch/': %m");
|
||||
|
||||
_cleanup_set_free_ Set *pending_wds = NULL, *verified_wds = NULL;
|
||||
FOREACH_DIRENT(de, dir, break) {
|
||||
if (safe_atoi(de->d_name, NULL) >= 0) {
|
||||
/* This should be wd -> ID symlink */
|
||||
|
||||
if (set_contains(verified_wds, de->d_name))
|
||||
continue;
|
||||
|
||||
r = set_put_strdup(&pending_wds, de->d_name);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to store pending watch handle %s, ignoring: %m", de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *wd = NULL;
|
||||
r = readlinkat_malloc(dirfd(dir), de->d_name, &wd);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Found broken inotify watch, failed to read symlink %s, ignoring: %m", de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *devnode = NULL;
|
||||
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||
if (sd_device_new_from_device_id(&dev, de->d_name) >= 0)
|
||||
(void) sd_device_get_devname(dev, &devnode);
|
||||
|
||||
_cleanup_free_ char *id = NULL;
|
||||
r = readlinkat_malloc(dirfd(dir), wd, &id);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Found broken inotify watch %s on %s (%s), failed to read symlink %s, ignoring: %m",
|
||||
wd, strna(devnode), de->d_name, wd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!streq(de->d_name, id)) {
|
||||
log_warning("Found broken inotify watch %s on %s (%s), broken symlink chain: %s → %s → %s",
|
||||
wd, strna(devnode), de->d_name, de->d_name, wd, id);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_debug("Found inotify watch %s on %s (%s).", wd, strna(devnode), de->d_name);
|
||||
|
||||
free(set_remove(pending_wds, wd));
|
||||
|
||||
r = set_ensure_put(&verified_wds, &string_hash_ops_free, wd);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to store verified watch handle %s, ignoring: %m", wd);
|
||||
continue;
|
||||
}
|
||||
TAKE_PTR(wd);
|
||||
}
|
||||
|
||||
const char *w;
|
||||
SET_FOREACH(w, pending_wds) {
|
||||
_cleanup_free_ char *id = NULL;
|
||||
r = readlinkat_malloc(dirfd(dir), w, &id);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Found broken inotify watch %s, failed to read symlink %s, ignoring: %m", w, w);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *devnode = NULL;
|
||||
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||
if (sd_device_new_from_device_id(&dev, id) >= 0)
|
||||
(void) sd_device_get_devname(dev, &devnode);
|
||||
|
||||
_cleanup_free_ char *wd = NULL;
|
||||
(void) readlinkat_malloc(dirfd(dir), id, &wd);
|
||||
|
||||
log_warning("Found broken inotify watch %s on %s (%s), broken symlink chain: %s → %s → %s",
|
||||
wd, strna(devnode), id, w, id, wd);
|
||||
}
|
||||
}
|
||||
|
||||
static int synthesize_change_one(sd_device *dev, sd_device *target) {
|
||||
int r;
|
||||
|
||||
assert(dev);
|
||||
assert(target);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
const char *syspath = NULL;
|
||||
(void) sd_device_get_syspath(target, &syspath);
|
||||
log_device_debug(dev, "device is closed, synthesising 'change' on %s", strna(syspath));
|
||||
}
|
||||
|
||||
r = sd_device_trigger(target, SD_DEVICE_CHANGE);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(target, r, "Failed to trigger 'change' uevent: %m");
|
||||
|
||||
DEVICE_TRACE_POINT(synthetic_change_event, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synthesize_change_all(sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(dev);
|
||||
|
||||
r = blockdev_reread_partition_table(dev);
|
||||
if (r < 0)
|
||||
log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m");
|
||||
bool part_table_read = r >= 0;
|
||||
|
||||
/* search for partitions */
|
||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||
r = partition_enumerator_new(dev, &e);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator, ignoring: %m");
|
||||
|
||||
/* We have partitions and re-read the table, the kernel already sent out a "change"
|
||||
* event for the disk, and "remove/add" for all partitions. */
|
||||
if (part_table_read && sd_device_enumerator_get_device_first(e))
|
||||
return 0;
|
||||
|
||||
/* We have partitions but re-reading the partition table did not work, synthesize
|
||||
* "change" for the disk and all partitions. */
|
||||
r = synthesize_change_one(dev, dev);
|
||||
FOREACH_DEVICE(e, d)
|
||||
RET_GATHER(r, synthesize_change_one(dev, d));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int synthesize_change_child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
assert(s);
|
||||
|
||||
sd_event_source_unref(set_remove(manager->synthesize_change_child_event_sources, s));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synthesize_change(Manager *manager, sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(dev);
|
||||
|
||||
const char *sysname;
|
||||
r = sd_device_get_sysname(dev, &sysname);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (startswith(sysname, "dm-") || block_device_is_whole_disk(dev) <= 0)
|
||||
return synthesize_change_one(dev, dev);
|
||||
|
||||
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
|
||||
r = pidref_safe_fork(
|
||||
"(udev-synth)",
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE,
|
||||
&pidref);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* child */
|
||||
(void) synthesize_change_all(dev);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
r = event_add_child_pidref(manager->event, &s, &pidref, WEXITED, synthesize_change_child_handler, manager);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to add child event source for "PID_FMT", ignoring: %m", pidref.pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = sd_event_source_set_child_pidfd_own(s, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PIDREF(pidref);
|
||||
|
||||
r = set_ensure_put(&manager->synthesize_change_child_event_sources, &event_source_hash_ops, s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_process_inotify(Manager *manager, const struct inotify_event *e) {
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(e);
|
||||
|
||||
/* Do not handle IN_IGNORED here. Especially, do not try to call udev_watch_end() from the
|
||||
* main process. Otherwise, the pair of the symlinks may become inconsistent, and several
|
||||
* garbage may remain. The old symlinks are removed by a worker that processes the
|
||||
* corresponding 'remove' uevent;
|
||||
* udev_event_execute_rules() -> event_execute_rules_on_remove() -> udev_watch_end(). */
|
||||
|
||||
if (!FLAGS_SET(e->mask, IN_CLOSE_WRITE))
|
||||
return 0;
|
||||
|
||||
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||
r = device_new_from_watch_handle_at(&dev, -EBADF, e->wd);
|
||||
if (r < 0) /* Device may be removed just after closed. */
|
||||
return log_debug_errno(r, "Failed to create sd_device object from watch handle, ignoring: %m");
|
||||
|
||||
log_device_debug(dev, "Received inotify event of watch handle %i.", e->wd);
|
||||
|
||||
(void) event_queue_assume_block_device_unlocked(manager, dev);
|
||||
(void) synthesize_change(manager, dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_inotify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
union inotify_event_buffer buffer;
|
||||
ssize_t l = read(fd, &buffer, sizeof(buffer));
|
||||
if (l < 0) {
|
||||
if (ERRNO_IS_TRANSIENT(errno))
|
||||
return 0;
|
||||
|
||||
return log_error_errno(errno, "Failed to read inotify fd: %m");
|
||||
}
|
||||
|
||||
FOREACH_INOTIFY_EVENT_WARN(e, buffer, l)
|
||||
(void) manager_process_inotify(manager, e);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udev_watch_restore(int inotify_fd) {
|
||||
_cleanup_(rm_rf_safep) const char *old = "/run/udev/watch.old/";
|
||||
int r;
|
||||
|
||||
@@ -88,6 +326,65 @@ int udev_watch_restore(int inotify_fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_init_inotify(Manager *manager, int fd) {
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
|
||||
/* This takes passed file descriptor on success. */
|
||||
|
||||
if (fd >= 0) {
|
||||
if (manager->inotify_fd >= 0)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple inotify fd (%i), ignoring.", fd);
|
||||
|
||||
log_debug("Received inotify fd (%i) from service manager.", fd);
|
||||
manager->inotify_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (manager->inotify_fd >= 0)
|
||||
return 0;
|
||||
|
||||
fd = inotify_init1(IN_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to create inotify descriptor: %m");
|
||||
|
||||
log_debug("Initialized new inotify instance, restoring inotify watches of previous invocation.");
|
||||
manager->inotify_fd = fd;
|
||||
(void) udev_watch_restore(fd);
|
||||
|
||||
r = notify_push_fd(manager->inotify_fd, "inotify");
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to push inotify fd to service manager, ignoring: %m");
|
||||
else
|
||||
log_debug("Pushed inotify fd to service manager.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_start_inotify(Manager *manager) {
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(manager->event);
|
||||
|
||||
r = manager_init_inotify(manager, -EBADF);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
udev_watch_dump();
|
||||
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
r = sd_event_add_io(manager->event, &s, manager->inotify_fd, EPOLLIN, on_inotify, manager);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create inotify event source: %m");
|
||||
|
||||
(void) sd_event_source_set_description(s, "manager-inotify");
|
||||
|
||||
manager->inotify_event = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udev_watch_clear(sd_device *dev, int dirfd, int *ret_wd) {
|
||||
_cleanup_free_ char *wd_str = NULL, *buf = NULL;
|
||||
const char *id;
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
#include "sd-device.h"
|
||||
|
||||
int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd);
|
||||
static inline int device_new_from_watch_handle(sd_device **ret, int wd) {
|
||||
return device_new_from_watch_handle_at(ret, -1, wd);
|
||||
}
|
||||
typedef struct Manager Manager;
|
||||
|
||||
void udev_watch_dump(void);
|
||||
|
||||
int manager_init_inotify(Manager *manager, int fd);
|
||||
int manager_start_inotify(Manager *manager);
|
||||
|
||||
int udev_watch_restore(int inotify_fd);
|
||||
int udev_watch_begin(int inotify_fd, sd_device *dev);
|
||||
int udev_watch_end(int inotify_fd, sd_device *dev);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -ex
|
||||
set -o pipefail
|
||||
|
||||
# tests for udev watch
|
||||
|
||||
function check_validity() {
|
||||
local f ID_OR_HANDLE
|
||||
|
||||
for f in /run/udev/watch/*; do
|
||||
ID_OR_HANDLE="$(readlink "$f")"
|
||||
test -L "/run/udev/watch/${ID_OR_HANDLE}"
|
||||
test "$(readlink "/run/udev/watch/${ID_OR_HANDLE}")" = "$(basename "$f")"
|
||||
done
|
||||
}
|
||||
|
||||
function check() {
|
||||
for _ in {1..2}; do
|
||||
systemctl restart systemd-udevd.service
|
||||
udevadm control --ping
|
||||
udevadm settle --timeout=30
|
||||
check_validity
|
||||
|
||||
for _ in {1..2}; do
|
||||
udevadm trigger -w --action add --subsystem-match=block
|
||||
check_validity
|
||||
done
|
||||
|
||||
for _ in {1..2}; do
|
||||
udevadm trigger -w --action change --subsystem-match=block
|
||||
check_validity
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
mkdir -p /run/udev/rules.d/
|
||||
|
||||
cat >/run/udev/rules.d/00-debug.rules <<EOF
|
||||
SUBSYSTEM=="block", KERNEL=="sda*", OPTIONS="log_level=debug"
|
||||
EOF
|
||||
|
||||
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
|
||||
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="watch"
|
||||
EOF
|
||||
|
||||
check
|
||||
|
||||
ROOTDEV="$(bootctl -RR)"
|
||||
|
||||
MAJOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')"
|
||||
MINOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')"
|
||||
test -L "/run/udev/watch/b${MAJOR}:${MINOR}"
|
||||
|
||||
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
|
||||
ACTION=="change", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="nowatch"
|
||||
EOF
|
||||
|
||||
check
|
||||
|
||||
MAJOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')"
|
||||
MINOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')"
|
||||
test ! -e "/run/udev/watch/b${MAJOR}:${MINOR}"
|
||||
|
||||
rm /run/udev/rules.d/00-debug.rules
|
||||
rm /run/udev/rules.d/50-testsuite.rules
|
||||
|
||||
udevadm control --reload
|
||||
systemctl reset-failed systemd-udevd.service
|
||||
|
||||
exit 0
|
||||
116
test/units/TEST-17-UDEV.watch.sh
Executable file
116
test/units/TEST-17-UDEV.watch.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -ex
|
||||
set -o pipefail
|
||||
|
||||
# tests for udev watch
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
function check_validity() {
|
||||
local f ID_OR_HANDLE
|
||||
|
||||
for f in /run/udev/watch/*; do
|
||||
ID_OR_HANDLE="$(readlink "$f")"
|
||||
test -L "/run/udev/watch/${ID_OR_HANDLE}"
|
||||
test "$(readlink "/run/udev/watch/${ID_OR_HANDLE}")" = "$(basename "$f")"
|
||||
|
||||
if [[ "${1:-}" == "1" ]]; then
|
||||
journalctl -n 1 -q -u systemd-udevd.service --invocation=0 --grep "Found inotify watch .*$ID_OR_HANDLE"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function check() {
|
||||
for _ in {1..2}; do
|
||||
systemctl reset-failed systemd-udevd.service
|
||||
systemctl restart systemd-udevd.service
|
||||
udevadm settle --timeout=30
|
||||
|
||||
journalctl --sync
|
||||
|
||||
# Check if the inotify watch fd is received from fd store.
|
||||
journalctl -n 1 -q -u systemd-udevd.service --invocation=0 --grep 'Received inotify fd \(\d\) from service manager.'
|
||||
|
||||
# Check if there is no broken symlink chain.
|
||||
assert_eq "$(journalctl -n 1 -q -u systemd-udevd.service --invocation=0 --grep 'Found broken inotify watch' || :)" ""
|
||||
|
||||
check_validity 1
|
||||
|
||||
for _ in {1..2}; do
|
||||
udevadm trigger -w --action add --subsystem-match=block
|
||||
check_validity
|
||||
done
|
||||
|
||||
for _ in {1..2}; do
|
||||
udevadm trigger -w --action change --subsystem-match=block
|
||||
check_validity
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
# Check if the first invocation (should be in initrd) pushed the inotify fd to fdstore,
|
||||
# and the next invocation gained the fd from service manager.
|
||||
# TNote the service may be started without generating debugging logs. Let's check failure log.
|
||||
if ! journalctl -n 1 -q -u systemd-udevd.service --invocation=1 --grep 'Pushed inotify fd to service manager.'; then
|
||||
assert_eq "$(journalctl -n 1 -q -u systemd-udevd.service --invocation=1 --grep 'Failed to push inotify fd to service manager.' || :)" ""
|
||||
fi
|
||||
if ! journalctl -n 1 -q -u systemd-udevd.service --invocation=2 --grep 'Received inotify fd \(\d\) from service manager.'; then
|
||||
assert_eq "$(journalctl -n 1 -q -u systemd-udevd.service --invocation=2 --grep 'Pushed inotify fd to service manager.' || :)" ""
|
||||
fi
|
||||
|
||||
mkdir -p /run/systemd/system/systemd-udevd.service.d/
|
||||
cat >/run/systemd/system/systemd-udevd.service.d/10-debug.conf <<EOF
|
||||
[Service]
|
||||
SYSTEMD_LOG_LEVEL=debug
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
mkdir -p /run/udev/rules.d/
|
||||
|
||||
cat >/run/udev/rules.d/00-debug.rules <<EOF
|
||||
SUBSYSTEM=="block", KERNEL=="sda*", OPTIONS="log_level=debug"
|
||||
EOF
|
||||
|
||||
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
|
||||
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="watch"
|
||||
EOF
|
||||
|
||||
# To make the previous invocation of systemd-udevd generates debugging logs on stop,
|
||||
# that will be checked by check().
|
||||
udevadm control --log-level debug
|
||||
|
||||
# Unfortunately, journalctl --invocation= is unstable when debug logging is enabled on service manager.
|
||||
SAVED_LOG_LEVEL=$(systemctl log-level)
|
||||
systemctl log-level info
|
||||
|
||||
check
|
||||
|
||||
ROOTDEV="$(bootctl -RR)"
|
||||
|
||||
MAJOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')"
|
||||
MINOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')"
|
||||
test -L "/run/udev/watch/b${MAJOR}:${MINOR}"
|
||||
|
||||
cat >/run/udev/rules.d/50-testsuite.rules <<EOF
|
||||
ACTION=="change", SUBSYSTEM=="block", KERNEL=="sda", OPTIONS:="nowatch"
|
||||
EOF
|
||||
|
||||
check
|
||||
|
||||
MAJOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MAJOR=' | sed -e 's/^E: MAJOR=//')"
|
||||
MINOR="$(udevadm info "$ROOTDEV" | grep -e '^E: MINOR=' | sed -e 's/^E: MINOR=//')"
|
||||
test ! -e "/run/udev/watch/b${MAJOR}:${MINOR}"
|
||||
|
||||
rm /run/udev/rules.d/00-debug.rules
|
||||
rm /run/udev/rules.d/50-testsuite.rules
|
||||
udevadm control --reload
|
||||
|
||||
rm -f /run/systemd/system/systemd-udevd.service.d/10-debug.conf
|
||||
systemctl daemon-reload
|
||||
|
||||
systemctl log-level "$SAVED_LOG_LEVEL"
|
||||
|
||||
exit 0
|
||||
@@ -27,6 +27,8 @@ Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket systemd-udevd-v
|
||||
Restart=always
|
||||
RestartSec=0
|
||||
ExecStart={{LIBEXECDIR}}/systemd-udevd
|
||||
FileDescriptorStoreMax=512
|
||||
FileDescriptorStorePreserve=yes
|
||||
KillMode=mixed
|
||||
TasksMax=infinity
|
||||
PrivateMounts=yes
|
||||
|
||||
Reference in New Issue
Block a user