From 9daec33938676c7439eecefe4c29d0891dd386ba Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 15:55:12 +0900 Subject: [PATCH 01/14] udev-util: drop udev_queue_init() from shared It is only used in libudev, let's move it. --- src/libudev/libudev-queue.c | 13 ++++++++----- src/shared/udev-util.c | 14 -------------- src/shared/udev-util.h | 1 - 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/libudev/libudev-queue.c b/src/libudev/libudev-queue.c index 7c5bb5500c..0af99e5481 100644 --- a/src/libudev/libudev-queue.c +++ b/src/libudev/libudev-queue.c @@ -192,18 +192,21 @@ _public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_qu * Returns: a file descriptor to watch for a queue to become empty. */ _public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { - int r; + _cleanup_close_ int fd = -EBADF; assert_return(udev_queue, -EINVAL); if (udev_queue->fd >= 0) return udev_queue->fd; - r = udev_queue_init(); - if (r < 0) - return r; + fd = inotify_init1(IN_CLOEXEC); + if (fd < 0) + return -errno; - return udev_queue->fd = r; + if (inotify_add_watch(fd, "/run/udev" , IN_DELETE) < 0) + return -errno; + + return udev_queue->fd = TAKE_FD(fd); } /** diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index 3e81234dc1..fddf096d76 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "alloc-util.h" @@ -578,19 +577,6 @@ int udev_queue_is_empty(void) { (errno == ENOENT ? true : -errno) : false; } -int udev_queue_init(void) { - _cleanup_close_ int fd = -EBADF; - - fd = inotify_init1(IN_CLOEXEC); - if (fd < 0) - return -errno; - - if (inotify_add_watch(fd, "/run/udev" , IN_DELETE) < 0) - return -errno; - - return TAKE_FD(fd); -} - bool udev_available(void) { static int cache = -1; diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 175fa0b6f0..05a0849934 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -53,7 +53,6 @@ int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool devpath_conflict(const char *a, const char *b); int udev_queue_is_empty(void); -int udev_queue_init(void); bool udev_available(void); From 2f7862cc5743bb7501e6d980392edf1bdcc34c10 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 23:12:52 +0900 Subject: [PATCH 02/14] meson: sort files --- src/udev/meson.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/udev/meson.build b/src/udev/meson.build index 01ec8b79bc..2e14867ba7 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -1,29 +1,29 @@ # SPDX-License-Identifier: LGPL-2.1-or-later udevadm_sources = files( - 'udevadm.c', 'udevadm-control.c', 'udevadm-hwdb.c', 'udevadm-info.c', 'udevadm-lock.c', 'udevadm-monitor.c', 'udevadm-settle.c', - 'udevadm-test.c', 'udevadm-test-builtin.c', + 'udevadm-test.c', 'udevadm-trigger.c', 'udevadm-util.c', 'udevadm-verify.c', 'udevadm-wait.c', + 'udevadm.c', 'udevd.c', ) libudevd_core_sources = files( + 'net/link-config.c', 'udev-ctrl.c', 'udev-event.c', 'udev-node.c', 'udev-rules.c', 'udev-watch.c', - 'udev-builtin.c', 'udev-builtin-btrfs.c', 'udev-builtin-hwdb.c', 'udev-builtin-input_id.c', @@ -32,7 +32,7 @@ libudevd_core_sources = files( 'udev-builtin-net_setup_link.c', 'udev-builtin-path_id.c', 'udev-builtin-usb_id.c', - 'net/link-config.c', + 'udev-builtin.c', ) if conf.get('HAVE_KMOD') == 1 From 1c3edc2ee3cd1625146617f0286fdaf0978402bf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 23:10:32 +0900 Subject: [PATCH 03/14] udev: move DEVICE_TRACE() to udev-trace.h It is only used by udevd, and not necessary in src/shared. --- src/shared/udev-util.h | 32 -------------------------------- src/udev/udev-event.c | 1 + src/udev/udev-rules.c | 1 + src/udev/udev-trace.h | 35 +++++++++++++++++++++++++++++++++++ src/udev/udevd.c | 1 + 5 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 src/udev/udev-trace.h diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 05a0849934..c848cf6177 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -1,11 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#if HAVE_SYS_SDT_H -#define SDT_USE_VARIADIC -#include -#endif - #include "sd-device.h" #include "time-util.h" @@ -55,30 +50,3 @@ bool devpath_conflict(const char *a, const char *b); int udev_queue_is_empty(void); bool udev_available(void); - -#if HAVE_SYS_SDT_H - -/* Each trace point can have different number of additional arguments. Note that when the macro is used only - * additional arguments are listed in the macro invocation! - * - * Default arguments for each trace point are as follows: - * - arg0 - action - * - arg1 - sysname - * - arg2 - syspath - * - arg3 - subsystem - */ -#define DEVICE_TRACE_POINT(name, dev, ...) \ - do { \ - PROTECT_ERRNO; \ - const char *_n = NULL, *_p = NULL, *_s = NULL; \ - sd_device *_d = (dev); \ - sd_device_action_t _a = _SD_DEVICE_ACTION_INVALID; \ - (void) sd_device_get_action(_d, &_a); \ - (void) sd_device_get_sysname(_d, &_n); \ - (void) sd_device_get_syspath(_d, &_p); \ - (void) sd_device_get_subsystem(_d, &_s); \ - STAP_PROBEV(udev, name, device_action_to_string(_a), _n, _p, _s __VA_OPT__(,) __VA_ARGS__);\ - } while (false); -#else -#define DEVICE_TRACE_POINT(name, dev, ...) ((void) 0) -#endif diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index 31547b7679..745f29a88f 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -31,6 +31,7 @@ #include "udev-builtin.h" #include "udev-event.h" #include "udev-node.h" +#include "udev-trace.h" #include "udev-util.h" #include "udev-watch.h" #include "user-util.h" diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 88846ae275..2c9797f099 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -32,6 +32,7 @@ #include "udev-event.h" #include "udev-node.h" #include "udev-rules.h" +#include "udev-trace.h" #include "udev-util.h" #include "user-util.h" #include "virt.h" diff --git a/src/udev/udev-trace.h b/src/udev/udev-trace.h new file mode 100644 index 0000000000..5e94390854 --- /dev/null +++ b/src/udev/udev-trace.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if HAVE_SYS_SDT_H +#define SDT_USE_VARIADIC +#include + +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" + +/* Each trace point can have different number of additional arguments. Note that when the macro is used only + * additional arguments are listed in the macro invocation! + * + * Default arguments for each trace point are as follows: + * - arg0 - action + * - arg1 - sysname + * - arg2 - syspath + * - arg3 - subsystem + */ +#define DEVICE_TRACE_POINT(name, dev, ...) \ + do { \ + PROTECT_ERRNO; \ + const char *_n = NULL, *_p = NULL, *_s = NULL; \ + sd_device *_d = (dev); \ + sd_device_action_t _a = _SD_DEVICE_ACTION_INVALID; \ + (void) sd_device_get_action(_d, &_a); \ + (void) sd_device_get_sysname(_d, &_n); \ + (void) sd_device_get_syspath(_d, &_p); \ + (void) sd_device_get_subsystem(_d, &_s); \ + STAP_PROBEV(udev, name, device_action_to_string(_a), _n, _p, _s __VA_OPT__(,) __VA_ARGS__);\ + } while (false); +#else +#define DEVICE_TRACE_POINT(name, dev, ...) ((void) 0) +#endif diff --git a/src/udev/udevd.c b/src/udev/udevd.c index fe29dd94b3..b545abd3e4 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -69,6 +69,7 @@ #include "udev-ctrl.h" #include "udev-event.h" #include "udev-node.h" +#include "udev-trace.h" #include "udev-util.h" #include "udev-watch.h" #include "user-util.h" From 0b76cc2fdf133927c307f851ba1bc9395ab09f5a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 23:10:41 +0900 Subject: [PATCH 04/14] udev: split-out formatter and spawning commands from udev-event.c No functional changes, just refactoring. --- src/test/udev-rule-runner.c | 1 + src/udev/meson.build | 2 + src/udev/test-udev-event.c | 1 + src/udev/udev-event.c | 833 ------------------------------------ src/udev/udev-event.h | 32 +- src/udev/udev-format.c | 483 +++++++++++++++++++++ src/udev/udev-format.h | 18 + src/udev/udev-rules.c | 2 + src/udev/udev-spawn.c | 355 +++++++++++++++ src/udev/udev-spawn.h | 28 ++ src/udev/udevadm-test.c | 1 + src/udev/udevd.c | 3 +- 12 files changed, 898 insertions(+), 861 deletions(-) create mode 100644 src/udev/udev-format.c create mode 100644 src/udev/udev-format.h create mode 100644 src/udev/udev-spawn.c create mode 100644 src/udev/udev-spawn.h diff --git a/src/test/udev-rule-runner.c b/src/test/udev-rule-runner.c index fe854a3d2e..72296b3f0e 100644 --- a/src/test/udev-rule-runner.c +++ b/src/test/udev-rule-runner.c @@ -24,6 +24,7 @@ #include "string-util.h" #include "tests.h" #include "udev-event.h" +#include "udev-spawn.h" #include "version.h" static int device_new_from_synthetic_event(sd_device **ret, const char *syspath, const char *action) { diff --git a/src/udev/meson.build b/src/udev/meson.build index 2e14867ba7..3dbda3cc2f 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -21,8 +21,10 @@ libudevd_core_sources = files( 'net/link-config.c', 'udev-ctrl.c', 'udev-event.c', + 'udev-format.c', 'udev-node.c', 'udev-rules.c', + 'udev-spawn.c', 'udev-watch.c', 'udev-builtin-btrfs.c', 'udev-builtin-hwdb.c', diff --git a/src/udev/test-udev-event.c b/src/udev/test-udev-event.c index 42f435916b..4f43facd80 100644 --- a/src/udev/test-udev-event.c +++ b/src/udev/test-udev-event.c @@ -5,6 +5,7 @@ #include "strv.h" #include "tests.h" #include "udev-event.h" +#include "udev-spawn.h" #define BUF_SIZE 1024 diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index 745f29a88f..0876e478d8 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -1,58 +1,21 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-event.h" - #include "alloc-util.h" #include "device-internal.h" #include "device-private.h" #include "device-util.h" -#include "fd-util.h" #include "fs-util.h" -#include "format-util.h" #include "netif-naming-scheme.h" #include "netlink-util.h" -#include "parse-util.h" #include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "stdio-util.h" #include "string-util.h" #include "strv.h" -#include "strxcpyx.h" -#include "udev-builtin.h" #include "udev-event.h" #include "udev-node.h" #include "udev-trace.h" -#include "udev-util.h" #include "udev-watch.h" #include "user-util.h" -typedef struct Spawn { - sd_device *device; - const char *cmd; - pid_t pid; - usec_t timeout_warn_usec; - usec_t timeout_usec; - int timeout_signal; - usec_t event_birth_usec; - bool accept_failure; - int fd_stdout; - int fd_stderr; - char *result; - size_t result_size; - size_t result_len; - bool truncated; -} Spawn; - UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rtnl, int log_level) { UdevEvent *event; @@ -93,771 +56,6 @@ UdevEvent *udev_event_free(UdevEvent *event) { return mfree(event); } -typedef enum { - FORMAT_SUBST_DEVNODE, - FORMAT_SUBST_ATTR, - FORMAT_SUBST_ENV, - FORMAT_SUBST_KERNEL, - FORMAT_SUBST_KERNEL_NUMBER, - FORMAT_SUBST_DRIVER, - FORMAT_SUBST_DEVPATH, - FORMAT_SUBST_ID, - FORMAT_SUBST_MAJOR, - FORMAT_SUBST_MINOR, - FORMAT_SUBST_RESULT, - FORMAT_SUBST_PARENT, - FORMAT_SUBST_NAME, - FORMAT_SUBST_LINKS, - FORMAT_SUBST_ROOT, - FORMAT_SUBST_SYS, - _FORMAT_SUBST_TYPE_MAX, - _FORMAT_SUBST_TYPE_INVALID = -EINVAL, -} FormatSubstitutionType; - -struct subst_map_entry { - const char *name; - const char fmt; - FormatSubstitutionType type; -}; - -static const struct subst_map_entry map[] = { - { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, - { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ - { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, - { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ - { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, - { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, - { .name = "number", .fmt = 'n', .type = FORMAT_SUBST_KERNEL_NUMBER }, - { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, - { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, - { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, - { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, - { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, - { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, - { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, - { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, - { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, - { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, - { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, -}; - -static const char *format_type_to_string(FormatSubstitutionType t) { - for (size_t i = 0; i < ELEMENTSOF(map); i++) - if (map[i].type == t) - return map[i].name; - return NULL; -} - -static char format_type_to_char(FormatSubstitutionType t) { - for (size_t i = 0; i < ELEMENTSOF(map); i++) - if (map[i].type == t) - return map[i].fmt; - return '\0'; -} - -static int get_subst_type(const char **str, bool strict, FormatSubstitutionType *ret_type, char ret_attr[static UDEV_PATH_SIZE]) { - const char *p = *str, *q = NULL; - size_t i; - - assert(str); - assert(*str); - assert(ret_type); - assert(ret_attr); - - if (*p == '$') { - p++; - if (*p == '$') { - *str = p; - return 0; - } - for (i = 0; i < ELEMENTSOF(map); i++) - if ((q = startswith(p, map[i].name))) - break; - } else if (*p == '%') { - p++; - if (*p == '%') { - *str = p; - return 0; - } - - for (i = 0; i < ELEMENTSOF(map); i++) - if (*p == map[i].fmt) { - q = p + 1; - break; - } - } else - return 0; - if (!q) - /* When 'strict' flag is set, then '$' and '%' must be escaped. */ - return strict ? -EINVAL : 0; - - if (*q == '{') { - const char *start, *end; - size_t len; - - start = q + 1; - end = strchr(start, '}'); - if (!end) - return -EINVAL; - - len = end - start; - if (len == 0 || len >= UDEV_PATH_SIZE) - return -EINVAL; - - strnscpy(ret_attr, UDEV_PATH_SIZE, start, len); - q = end + 1; - } else - *ret_attr = '\0'; - - *str = q; - *ret_type = map[i].type; - return 1; -} - -static int safe_atou_optional_plus(const char *s, unsigned *ret) { - const char *p; - int r; - - assert(s); - assert(ret); - - /* Returns 1 if plus, 0 if no plus, negative on error */ - - p = endswith(s, "+"); - if (p) - s = strndupa_safe(s, p - s); - - r = safe_atou(s, ret); - if (r < 0) - return r; - - return !!p; -} - -static ssize_t udev_event_subst_format( - UdevEvent *event, - FormatSubstitutionType type, - const char *attr, - char *dest, - size_t l, - bool *ret_truncated) { - - sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); - const char *val = NULL; - bool truncated = false; - char *s = dest; - int r; - - switch (type) { - case FORMAT_SUBST_DEVPATH: - r = sd_device_get_devpath(dev, &val); - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_KERNEL: - r = sd_device_get_sysname(dev, &val); - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_KERNEL_NUMBER: - r = sd_device_get_sysnum(dev, &val); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_ID: - if (!event->dev_parent) - goto null_terminate; - r = sd_device_get_sysname(event->dev_parent, &val); - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_DRIVER: - if (!event->dev_parent) - goto null_terminate; - r = sd_device_get_driver(event->dev_parent, &val); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_MAJOR: - case FORMAT_SUBST_MINOR: { - dev_t devnum; - - r = sd_device_get_devnum(dev, &devnum); - if (r < 0 && r != -ENOENT) - return r; - strpcpyf_full(&s, l, &truncated, "%u", r < 0 ? 0 : type == FORMAT_SUBST_MAJOR ? major(devnum) : minor(devnum)); - break; - } - case FORMAT_SUBST_RESULT: { - unsigned index = 0; /* 0 means whole string */ - bool has_plus; - - if (!event->program_result) - goto null_terminate; - - if (!isempty(attr)) { - r = safe_atou_optional_plus(attr, &index); - if (r < 0) - return r; - - has_plus = r; - } - - if (index == 0) - strpcpy_full(&s, l, event->program_result, &truncated); - else { - const char *start, *p; - unsigned i; - - p = skip_leading_chars(event->program_result, NULL); - - for (i = 1; i < index; i++) { - while (*p && !strchr(WHITESPACE, *p)) - p++; - p = skip_leading_chars(p, NULL); - if (*p == '\0') - break; - } - if (i != index) { - log_device_debug(dev, "requested part of result string not found"); - goto null_terminate; - } - - start = p; - /* %c{2+} copies the whole string from the second part on */ - if (has_plus) - strpcpy_full(&s, l, start, &truncated); - else { - while (*p && !strchr(WHITESPACE, *p)) - p++; - strnpcpy_full(&s, l, start, p - start, &truncated); - } - } - break; - } - case FORMAT_SUBST_ATTR: { - char vbuf[UDEV_NAME_SIZE]; - int count; - bool t; - - if (isempty(attr)) - return -EINVAL; - - /* try to read the value specified by "[dmi/id]product_name" */ - if (udev_resolve_subsys_kernel(attr, vbuf, sizeof(vbuf), true) == 0) - val = vbuf; - - /* try to read the attribute the device */ - if (!val) - (void) sd_device_get_sysattr_value(dev, attr, &val); - - /* try to read the attribute of the parent device, other matches have selected */ - if (!val && event->dev_parent && event->dev_parent != dev) - (void) sd_device_get_sysattr_value(event->dev_parent, attr, &val); - - if (!val) - goto null_terminate; - - /* strip trailing whitespace, and replace unwanted characters */ - if (val != vbuf) - strscpy_full(vbuf, sizeof(vbuf), val, &truncated); - delete_trailing_chars(vbuf, NULL); - count = udev_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT); - if (count > 0) - log_device_debug(dev, "%i character(s) replaced", count); - strpcpy_full(&s, l, vbuf, &t); - truncated = truncated || t; - break; - } - case FORMAT_SUBST_PARENT: - r = sd_device_get_parent(dev, &parent); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - r = sd_device_get_devname(parent, &val); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - strpcpy_full(&s, l, val + STRLEN("/dev/"), &truncated); - break; - case FORMAT_SUBST_DEVNODE: - r = sd_device_get_devname(dev, &val); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - case FORMAT_SUBST_NAME: - if (event->name) - strpcpy_full(&s, l, event->name, &truncated); - else if (sd_device_get_devname(dev, &val) >= 0) - strpcpy_full(&s, l, val + STRLEN("/dev/"), &truncated); - else { - r = sd_device_get_sysname(dev, &val); - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - } - break; - case FORMAT_SUBST_LINKS: - FOREACH_DEVICE_DEVLINK(dev, link) { - if (s == dest) - strpcpy_full(&s, l, link + STRLEN("/dev/"), &truncated); - else - strpcpyl_full(&s, l, &truncated, " ", link + STRLEN("/dev/"), NULL); - if (truncated) - break; - } - if (s == dest) - goto null_terminate; - break; - case FORMAT_SUBST_ROOT: - strpcpy_full(&s, l, "/dev", &truncated); - break; - case FORMAT_SUBST_SYS: - strpcpy_full(&s, l, "/sys", &truncated); - break; - case FORMAT_SUBST_ENV: - if (isempty(attr)) - return -EINVAL; - r = sd_device_get_property_value(dev, attr, &val); - if (r == -ENOENT) - goto null_terminate; - if (r < 0) - return r; - strpcpy_full(&s, l, val, &truncated); - break; - default: - assert_not_reached(); - } - - if (ret_truncated) - *ret_truncated = truncated; - - return s - dest; - -null_terminate: - if (ret_truncated) - *ret_truncated = truncated; - - *s = '\0'; - return 0; -} - -size_t udev_event_apply_format( - UdevEvent *event, - const char *src, - char *dest, - size_t size, - bool replace_whitespace, - bool *ret_truncated) { - - bool truncated = false; - const char *s = ASSERT_PTR(src); - int r; - - assert(event); - assert(event->dev); - assert(dest); - assert(size > 0); - - while (*s) { - FormatSubstitutionType type; - char attr[UDEV_PATH_SIZE]; - ssize_t subst_len; - bool t; - - r = get_subst_type(&s, false, &type, attr); - if (r < 0) { - log_device_warning_errno(event->dev, r, "Invalid format string, ignoring: %s", src); - break; - } else if (r == 0) { - if (size < 2) { - /* need space for this char and the terminating NUL */ - truncated = true; - break; - } - *dest++ = *s++; - size--; - continue; - } - - subst_len = udev_event_subst_format(event, type, attr, dest, size, &t); - if (subst_len < 0) { - log_device_warning_errno(event->dev, subst_len, - "Failed to substitute variable '$%s' or apply format '%%%c', ignoring: %m", - format_type_to_string(type), format_type_to_char(type)); - break; - } - - truncated = truncated || t; - - /* FORMAT_SUBST_RESULT handles spaces itself */ - if (replace_whitespace && type != FORMAT_SUBST_RESULT) - /* udev_replace_whitespace can replace in-place, - * and does nothing if subst_len == 0 */ - subst_len = udev_replace_whitespace(dest, dest, subst_len); - - dest += subst_len; - size -= subst_len; - } - - assert(size >= 1); - - if (ret_truncated) - *ret_truncated = truncated; - - *dest = '\0'; - return size; -} - -int udev_check_format(const char *value, size_t *offset, const char **hint) { - FormatSubstitutionType type; - const char *s = value; - char attr[UDEV_PATH_SIZE]; - int r; - - while (*s) { - r = get_subst_type(&s, true, &type, attr); - if (r < 0) { - if (offset) - *offset = s - value; - if (hint) - *hint = "invalid substitution type"; - return r; - } else if (r == 0) { - s++; - continue; - } - - if (IN_SET(type, FORMAT_SUBST_ATTR, FORMAT_SUBST_ENV) && isempty(attr)) { - if (offset) - *offset = s - value; - if (hint) - *hint = "attribute value missing"; - return -EINVAL; - } - - if (type == FORMAT_SUBST_RESULT && !isempty(attr)) { - unsigned i; - - r = safe_atou_optional_plus(attr, &i); - if (r < 0) { - if (offset) - *offset = s - value; - if (hint) - *hint = "attribute value not a valid number"; - return r; - } - } - } - - return 0; -} - -static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Spawn *spawn = ASSERT_PTR(userdata); - char buf[4096], *p; - size_t size; - ssize_t l; - int r; - - assert(fd == spawn->fd_stdout || fd == spawn->fd_stderr); - assert(!spawn->result || spawn->result_len < spawn->result_size); - - if (fd == spawn->fd_stdout && spawn->result) { - p = spawn->result + spawn->result_len; - size = spawn->result_size - spawn->result_len; - } else { - p = buf; - size = sizeof(buf); - } - - l = read(fd, p, size - (p == buf)); - if (l < 0) { - if (errno == EAGAIN) - goto reenable; - - log_device_error_errno(spawn->device, errno, - "Failed to read stdout of '%s': %m", spawn->cmd); - - return 0; - } - - if ((size_t) l == size) { - log_device_warning(spawn->device, "Truncating stdout of '%s' up to %zu byte.", - spawn->cmd, spawn->result_size); - l--; - spawn->truncated = true; - } - - p[l] = '\0'; - if (fd == spawn->fd_stdout && spawn->result) - spawn->result_len += l; - - /* Log output only if we watch stderr. */ - if (l > 0 && spawn->fd_stderr >= 0) { - _cleanup_strv_free_ char **v = NULL; - - r = strv_split_newlines_full(&v, p, EXTRACT_RETAIN_ESCAPE); - if (r < 0) - log_device_debug(spawn->device, - "Failed to split output from '%s'(%s), ignoring: %m", - spawn->cmd, fd == spawn->fd_stdout ? "out" : "err"); - - STRV_FOREACH(q, v) - log_device_debug(spawn->device, "'%s'(%s) '%s'", spawn->cmd, - fd == spawn->fd_stdout ? "out" : "err", *q); - } - - if (l == 0 || spawn->truncated) - return 0; - -reenable: - /* Re-enable the event source if we did not encounter EOF */ - - r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT); - if (r < 0) - log_device_error_errno(spawn->device, r, - "Failed to reactivate IO source of '%s'", spawn->cmd); - return 0; -} - -static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - Spawn *spawn = ASSERT_PTR(userdata); - - DEVICE_TRACE_POINT(spawn_timeout, spawn->device, spawn->cmd); - - kill_and_sigcont(spawn->pid, spawn->timeout_signal); - - log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing", - spawn->cmd, spawn->pid, - FORMAT_TIMESPAN(spawn->timeout_usec, USEC_PER_SEC)); - - return 1; -} - -static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { - Spawn *spawn = ASSERT_PTR(userdata); - - log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete", - spawn->cmd, spawn->pid, - FORMAT_TIMESPAN(spawn->timeout_warn_usec, USEC_PER_SEC)); - - return 1; -} - -static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { - Spawn *spawn = ASSERT_PTR(userdata); - int ret = -EIO; - - switch (si->si_code) { - case CLD_EXITED: - if (si->si_status == 0) - log_device_debug(spawn->device, "Process '%s' succeeded.", spawn->cmd); - else - log_device_full(spawn->device, spawn->accept_failure ? LOG_DEBUG : LOG_WARNING, - "Process '%s' failed with exit code %i.", spawn->cmd, si->si_status); - ret = si->si_status; - break; - case CLD_KILLED: - case CLD_DUMPED: - log_device_error(spawn->device, "Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status)); - break; - default: - log_device_error(spawn->device, "Process '%s' failed due to unknown reason.", spawn->cmd); - } - - DEVICE_TRACE_POINT(spawn_exit, spawn->device, spawn->cmd); - - sd_event_exit(sd_event_source_get_event(s), ret); - return 1; -} - -static int spawn_wait(Spawn *spawn) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_event_source_disable_unrefp) sd_event_source *sigchld_source = NULL; - _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stdout_source = NULL; - _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stderr_source = NULL; - int r; - - assert(spawn); - - r = sd_event_new(&e); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to allocate sd-event object: %m"); - - if (spawn->timeout_usec > 0) { - usec_t usec, age_usec; - - usec = now(CLOCK_MONOTONIC); - age_usec = usec - spawn->event_birth_usec; - if (age_usec < spawn->timeout_usec) { - if (spawn->timeout_warn_usec > 0 && - spawn->timeout_warn_usec < spawn->timeout_usec && - spawn->timeout_warn_usec > age_usec) { - spawn->timeout_warn_usec -= age_usec; - - r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, - usec + spawn->timeout_warn_usec, USEC_PER_SEC, - on_spawn_timeout_warning, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m"); - } - - spawn->timeout_usec -= age_usec; - - r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, - usec + spawn->timeout_usec, USEC_PER_SEC, on_spawn_timeout, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m"); - } - } - - if (spawn->fd_stdout >= 0) { - r = sd_event_add_io(e, &stdout_source, spawn->fd_stdout, EPOLLIN, on_spawn_io, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create stdio event source: %m"); - r = sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to enable stdio event source: %m"); - } - - if (spawn->fd_stderr >= 0) { - r = sd_event_add_io(e, &stderr_source, spawn->fd_stderr, EPOLLIN, on_spawn_io, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create stderr event source: %m"); - r = sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to enable stderr event source: %m"); - } - - r = sd_event_add_child(e, &sigchld_source, spawn->pid, WEXITED, on_spawn_sigchld, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create sigchild event source: %m"); - /* SIGCHLD should be processed after IO is complete */ - r = sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to set priority to sigchild event source: %m"); - - return sd_event_loop(e); -} - -int udev_event_spawn( - UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - bool accept_failure, - const char *cmd, - char *result, - size_t ressize, - bool *ret_truncated) { - - _cleanup_close_pair_ int outpipe[2] = PIPE_EBADF, errpipe[2] = PIPE_EBADF; - _cleanup_strv_free_ char **argv = NULL; - char **envp = NULL; - Spawn spawn; - pid_t pid; - int r; - - assert(event); - assert(event->dev); - assert(result || ressize == 0); - - /* pipes from child to parent */ - if (result || log_get_max_level() >= LOG_INFO) - if (pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) != 0) - return log_device_error_errno(event->dev, errno, - "Failed to create pipe for command '%s': %m", cmd); - - if (log_get_max_level() >= LOG_INFO) - if (pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) != 0) - return log_device_error_errno(event->dev, errno, - "Failed to create pipe for command '%s': %m", cmd); - - r = strv_split_full(&argv, cmd, NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_RETAIN_ESCAPE); - if (r < 0) - return log_device_error_errno(event->dev, r, "Failed to split command: %m"); - - if (isempty(argv[0])) - return log_device_error_errno(event->dev, SYNTHETIC_ERRNO(EINVAL), - "Invalid command '%s'", cmd); - - /* allow programs in /usr/lib/udev/ to be called without the path */ - if (!path_is_absolute(argv[0])) { - char *program; - - program = path_join(UDEVLIBEXECDIR, argv[0]); - if (!program) - return log_oom(); - - free_and_replace(argv[0], program); - } - - r = device_get_properties_strv(event->dev, &envp); - if (r < 0) - return log_device_error_errno(event->dev, r, "Failed to get device properties"); - - log_device_debug(event->dev, "Starting '%s'", cmd); - - r = safe_fork_full("(spawn)", - (int[]) { -EBADF, outpipe[WRITE_END], errpipe[WRITE_END] }, - NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, - &pid); - if (r < 0) - return log_device_error_errno(event->dev, r, - "Failed to fork() to execute command '%s': %m", cmd); - if (r == 0) { - DEVICE_TRACE_POINT(spawn_exec, event->dev, cmd); - execve(argv[0], argv, envp); - _exit(EXIT_FAILURE); - } - - /* parent closed child's ends of pipes */ - outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]); - errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]); - - spawn = (Spawn) { - .device = event->dev, - .cmd = cmd, - .pid = pid, - .accept_failure = accept_failure, - .timeout_warn_usec = udev_warn_timeout(timeout_usec), - .timeout_usec = timeout_usec, - .timeout_signal = timeout_signal, - .event_birth_usec = event->birth_usec, - .fd_stdout = outpipe[READ_END], - .fd_stderr = errpipe[READ_END], - .result = result, - .result_size = ressize, - }; - r = spawn_wait(&spawn); - if (r < 0) - return log_device_error_errno(event->dev, r, - "Failed to wait for spawned command '%s': %m", cmd); - - if (result) - result[spawn.result_len] = '\0'; - - if (ret_truncated) - *ret_truncated = spawn.truncated; - - return r; /* 0 for success, and positive if the program failed */ -} - static int device_rename(sd_device *device, const char *name) { _cleanup_free_ char *new_syspath = NULL; const char *s; @@ -1205,34 +403,3 @@ int udev_event_execute_rules( return 0; } - -void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal) { - const char *command; - void *val; - int r; - - ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { - UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); - - if (builtin_cmd != _UDEV_BUILTIN_INVALID) { - log_device_debug(event->dev, "Running built-in command \"%s\"", command); - r = udev_builtin_run(event, builtin_cmd, command, false); - if (r < 0) - log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command); - } else { - if (event->exec_delay_usec > 0) { - log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.", - command, FORMAT_TIMESPAN(event->exec_delay_usec, USEC_PER_SEC)); - (void) usleep_safe(event->exec_delay_usec); - } - - log_device_debug(event->dev, "Running command \"%s\"", command); - - r = udev_event_spawn(event, timeout_usec, timeout_signal, false, command, NULL, 0, NULL); - if (r < 0) - log_device_warning_errno(event->dev, r, "Failed to execute '%s', ignoring: %m", command); - else if (r > 0) /* returned value is positive when program fails */ - log_device_debug(event->dev, "Command \"%s\" returned %d (error), ignoring.", command, r); - } - } -} diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h index b99559ca2e..6b94fd08ab 100644 --- a/src/udev/udev-event.h +++ b/src/udev/udev-event.h @@ -5,17 +5,17 @@ * Copyright © 2003 Greg Kroah-Hartman */ +#include +#include + #include "sd-device.h" #include "sd-netlink.h" #include "hashmap.h" #include "macro.h" +#include "time-util.h" #include "udev-rules.h" -#include "udev-util.h" - -#define READ_END 0 -#define WRITE_END 1 -#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," +#include "user-util.h" typedef struct UdevEvent { sd_device *dev; @@ -51,23 +51,6 @@ UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rt UdevEvent *udev_event_free(UdevEvent *event); DEFINE_TRIVIAL_CLEANUP_FUNC(UdevEvent*, udev_event_free); -size_t udev_event_apply_format( - UdevEvent *event, - const char *src, - char *dest, - size_t size, - bool replace_whitespace, - bool *ret_truncated); -int udev_check_format(const char *value, size_t *offset, const char **hint); -int udev_event_spawn( - UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - bool accept_failure, - const char *cmd, - char *result, - size_t ressize, - bool *ret_truncated); int udev_event_execute_rules( UdevEvent *event, int inotify_fd, @@ -75,8 +58,3 @@ int udev_event_execute_rules( int timeout_signal, Hashmap *properties_list, UdevRules *rules); -void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal); - -static inline usec_t udev_warn_timeout(usec_t timeout_usec) { - return DIV_ROUND_UP(timeout_usec, 3); -} diff --git a/src/udev/udev-format.c b/src/udev/udev-format.c new file mode 100644 index 0000000000..b33d8646c6 --- /dev/null +++ b/src/udev/udev-format.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "device-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "udev-event.h" +#include "udev-format.h" +#include "udev-util.h" + +typedef enum { + FORMAT_SUBST_DEVNODE, + FORMAT_SUBST_ATTR, + FORMAT_SUBST_ENV, + FORMAT_SUBST_KERNEL, + FORMAT_SUBST_KERNEL_NUMBER, + FORMAT_SUBST_DRIVER, + FORMAT_SUBST_DEVPATH, + FORMAT_SUBST_ID, + FORMAT_SUBST_MAJOR, + FORMAT_SUBST_MINOR, + FORMAT_SUBST_RESULT, + FORMAT_SUBST_PARENT, + FORMAT_SUBST_NAME, + FORMAT_SUBST_LINKS, + FORMAT_SUBST_ROOT, + FORMAT_SUBST_SYS, + _FORMAT_SUBST_TYPE_MAX, + _FORMAT_SUBST_TYPE_INVALID = -EINVAL, +} FormatSubstitutionType; + +struct subst_map_entry { + const char *name; + const char fmt; + FormatSubstitutionType type; +}; + +static const struct subst_map_entry map[] = { + { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, + { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ + { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, + { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ + { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, + { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, + { .name = "number", .fmt = 'n', .type = FORMAT_SUBST_KERNEL_NUMBER }, + { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, + { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, + { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, + { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, + { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, + { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, + { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, + { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, + { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, + { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, + { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, +}; + +static const char *format_type_to_string(FormatSubstitutionType t) { + for (size_t i = 0; i < ELEMENTSOF(map); i++) + if (map[i].type == t) + return map[i].name; + return NULL; +} + +static char format_type_to_char(FormatSubstitutionType t) { + for (size_t i = 0; i < ELEMENTSOF(map); i++) + if (map[i].type == t) + return map[i].fmt; + return '\0'; +} + +static int get_subst_type(const char **str, bool strict, FormatSubstitutionType *ret_type, char ret_attr[static UDEV_PATH_SIZE]) { + const char *p = *str, *q = NULL; + size_t i; + + assert(str); + assert(*str); + assert(ret_type); + assert(ret_attr); + + if (*p == '$') { + p++; + if (*p == '$') { + *str = p; + return 0; + } + for (i = 0; i < ELEMENTSOF(map); i++) + if ((q = startswith(p, map[i].name))) + break; + } else if (*p == '%') { + p++; + if (*p == '%') { + *str = p; + return 0; + } + + for (i = 0; i < ELEMENTSOF(map); i++) + if (*p == map[i].fmt) { + q = p + 1; + break; + } + } else + return 0; + if (!q) + /* When 'strict' flag is set, then '$' and '%' must be escaped. */ + return strict ? -EINVAL : 0; + + if (*q == '{') { + const char *start, *end; + size_t len; + + start = q + 1; + end = strchr(start, '}'); + if (!end) + return -EINVAL; + + len = end - start; + if (len == 0 || len >= UDEV_PATH_SIZE) + return -EINVAL; + + strnscpy(ret_attr, UDEV_PATH_SIZE, start, len); + q = end + 1; + } else + *ret_attr = '\0'; + + *str = q; + *ret_type = map[i].type; + return 1; +} + +static int safe_atou_optional_plus(const char *s, unsigned *ret) { + const char *p; + int r; + + assert(s); + assert(ret); + + /* Returns 1 if plus, 0 if no plus, negative on error */ + + p = endswith(s, "+"); + if (p) + s = strndupa_safe(s, p - s); + + r = safe_atou(s, ret); + if (r < 0) + return r; + + return !!p; +} + +static ssize_t udev_event_subst_format( + UdevEvent *event, + FormatSubstitutionType type, + const char *attr, + char *dest, + size_t l, + bool *ret_truncated) { + + sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *val = NULL; + bool truncated = false; + char *s = dest; + int r; + + switch (type) { + case FORMAT_SUBST_DEVPATH: + r = sd_device_get_devpath(dev, &val); + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_KERNEL: + r = sd_device_get_sysname(dev, &val); + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_KERNEL_NUMBER: + r = sd_device_get_sysnum(dev, &val); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_ID: + if (!event->dev_parent) + goto null_terminate; + r = sd_device_get_sysname(event->dev_parent, &val); + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_DRIVER: + if (!event->dev_parent) + goto null_terminate; + r = sd_device_get_driver(event->dev_parent, &val); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_MAJOR: + case FORMAT_SUBST_MINOR: { + dev_t devnum; + + r = sd_device_get_devnum(dev, &devnum); + if (r < 0 && r != -ENOENT) + return r; + strpcpyf_full(&s, l, &truncated, "%u", r < 0 ? 0 : type == FORMAT_SUBST_MAJOR ? major(devnum) : minor(devnum)); + break; + } + case FORMAT_SUBST_RESULT: { + unsigned index = 0; /* 0 means whole string */ + bool has_plus; + + if (!event->program_result) + goto null_terminate; + + if (!isempty(attr)) { + r = safe_atou_optional_plus(attr, &index); + if (r < 0) + return r; + + has_plus = r; + } + + if (index == 0) + strpcpy_full(&s, l, event->program_result, &truncated); + else { + const char *start, *p; + unsigned i; + + p = skip_leading_chars(event->program_result, NULL); + + for (i = 1; i < index; i++) { + while (*p && !strchr(WHITESPACE, *p)) + p++; + p = skip_leading_chars(p, NULL); + if (*p == '\0') + break; + } + if (i != index) { + log_device_debug(dev, "requested part of result string not found"); + goto null_terminate; + } + + start = p; + /* %c{2+} copies the whole string from the second part on */ + if (has_plus) + strpcpy_full(&s, l, start, &truncated); + else { + while (*p && !strchr(WHITESPACE, *p)) + p++; + strnpcpy_full(&s, l, start, p - start, &truncated); + } + } + break; + } + case FORMAT_SUBST_ATTR: { + char vbuf[UDEV_NAME_SIZE]; + int count; + bool t; + + if (isempty(attr)) + return -EINVAL; + + /* try to read the value specified by "[dmi/id]product_name" */ + if (udev_resolve_subsys_kernel(attr, vbuf, sizeof(vbuf), true) == 0) + val = vbuf; + + /* try to read the attribute the device */ + if (!val) + (void) sd_device_get_sysattr_value(dev, attr, &val); + + /* try to read the attribute of the parent device, other matches have selected */ + if (!val && event->dev_parent && event->dev_parent != dev) + (void) sd_device_get_sysattr_value(event->dev_parent, attr, &val); + + if (!val) + goto null_terminate; + + /* strip trailing whitespace, and replace unwanted characters */ + if (val != vbuf) + strscpy_full(vbuf, sizeof(vbuf), val, &truncated); + delete_trailing_chars(vbuf, NULL); + count = udev_replace_chars(vbuf, UDEV_ALLOWED_CHARS_INPUT); + if (count > 0) + log_device_debug(dev, "%i character(s) replaced", count); + strpcpy_full(&s, l, vbuf, &t); + truncated = truncated || t; + break; + } + case FORMAT_SUBST_PARENT: + r = sd_device_get_parent(dev, &parent); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + r = sd_device_get_devname(parent, &val); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + strpcpy_full(&s, l, val + STRLEN("/dev/"), &truncated); + break; + case FORMAT_SUBST_DEVNODE: + r = sd_device_get_devname(dev, &val); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + case FORMAT_SUBST_NAME: + if (event->name) + strpcpy_full(&s, l, event->name, &truncated); + else if (sd_device_get_devname(dev, &val) >= 0) + strpcpy_full(&s, l, val + STRLEN("/dev/"), &truncated); + else { + r = sd_device_get_sysname(dev, &val); + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + } + break; + case FORMAT_SUBST_LINKS: + FOREACH_DEVICE_DEVLINK(dev, link) { + if (s == dest) + strpcpy_full(&s, l, link + STRLEN("/dev/"), &truncated); + else + strpcpyl_full(&s, l, &truncated, " ", link + STRLEN("/dev/"), NULL); + if (truncated) + break; + } + if (s == dest) + goto null_terminate; + break; + case FORMAT_SUBST_ROOT: + strpcpy_full(&s, l, "/dev", &truncated); + break; + case FORMAT_SUBST_SYS: + strpcpy_full(&s, l, "/sys", &truncated); + break; + case FORMAT_SUBST_ENV: + if (isempty(attr)) + return -EINVAL; + r = sd_device_get_property_value(dev, attr, &val); + if (r == -ENOENT) + goto null_terminate; + if (r < 0) + return r; + strpcpy_full(&s, l, val, &truncated); + break; + default: + assert_not_reached(); + } + + if (ret_truncated) + *ret_truncated = truncated; + + return s - dest; + +null_terminate: + if (ret_truncated) + *ret_truncated = truncated; + + *s = '\0'; + return 0; +} + +size_t udev_event_apply_format( + UdevEvent *event, + const char *src, + char *dest, + size_t size, + bool replace_whitespace, + bool *ret_truncated) { + + bool truncated = false; + const char *s = ASSERT_PTR(src); + int r; + + assert(event); + assert(event->dev); + assert(dest); + assert(size > 0); + + while (*s) { + FormatSubstitutionType type; + char attr[UDEV_PATH_SIZE]; + ssize_t subst_len; + bool t; + + r = get_subst_type(&s, false, &type, attr); + if (r < 0) { + log_device_warning_errno(event->dev, r, "Invalid format string, ignoring: %s", src); + break; + } else if (r == 0) { + if (size < 2) { + /* need space for this char and the terminating NUL */ + truncated = true; + break; + } + *dest++ = *s++; + size--; + continue; + } + + subst_len = udev_event_subst_format(event, type, attr, dest, size, &t); + if (subst_len < 0) { + log_device_warning_errno(event->dev, subst_len, + "Failed to substitute variable '$%s' or apply format '%%%c', ignoring: %m", + format_type_to_string(type), format_type_to_char(type)); + break; + } + + truncated = truncated || t; + + /* FORMAT_SUBST_RESULT handles spaces itself */ + if (replace_whitespace && type != FORMAT_SUBST_RESULT) + /* udev_replace_whitespace can replace in-place, + * and does nothing if subst_len == 0 */ + subst_len = udev_replace_whitespace(dest, dest, subst_len); + + dest += subst_len; + size -= subst_len; + } + + assert(size >= 1); + + if (ret_truncated) + *ret_truncated = truncated; + + *dest = '\0'; + return size; +} + +int udev_check_format(const char *value, size_t *offset, const char **hint) { + FormatSubstitutionType type; + const char *s = value; + char attr[UDEV_PATH_SIZE]; + int r; + + while (*s) { + r = get_subst_type(&s, true, &type, attr); + if (r < 0) { + if (offset) + *offset = s - value; + if (hint) + *hint = "invalid substitution type"; + return r; + } else if (r == 0) { + s++; + continue; + } + + if (IN_SET(type, FORMAT_SUBST_ATTR, FORMAT_SUBST_ENV) && isempty(attr)) { + if (offset) + *offset = s - value; + if (hint) + *hint = "attribute value missing"; + return -EINVAL; + } + + if (type == FORMAT_SUBST_RESULT && !isempty(attr)) { + unsigned i; + + r = safe_atou_optional_plus(attr, &i); + if (r < 0) { + if (offset) + *offset = s - value; + if (hint) + *hint = "attribute value not a valid number"; + return r; + } + } + } + + return 0; +} diff --git a/src/udev/udev-format.h b/src/udev/udev-format.h new file mode 100644 index 0000000000..0dbdd561b1 --- /dev/null +++ b/src/udev/udev-format.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include +#include + +typedef struct UdevEvent UdevEvent; + +#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," + +size_t udev_event_apply_format( + UdevEvent *event, + const char *src, + char *dest, + size_t size, + bool replace_whitespace, + bool *ret_truncated); +int udev_check_format(const char *value, size_t *offset, const char **hint); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 2c9797f099..c4f2a7ffbb 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -30,8 +30,10 @@ #include "syslog-util.h" #include "udev-builtin.h" #include "udev-event.h" +#include "udev-format.h" #include "udev-node.h" #include "udev-rules.h" +#include "udev-spawn.h" #include "udev-trace.h" #include "udev-util.h" #include "user-util.h" diff --git a/src/udev/udev-spawn.c b/src/udev/udev-spawn.c new file mode 100644 index 0000000000..f90b9c62a1 --- /dev/null +++ b/src/udev/udev-spawn.c @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "sd-event.h" + +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "strv.h" +#include "udev-builtin.h" +#include "udev-event.h" +#include "udev-spawn.h" +#include "udev-trace.h" + +typedef struct Spawn { + sd_device *device; + const char *cmd; + pid_t pid; + usec_t timeout_warn_usec; + usec_t timeout_usec; + int timeout_signal; + usec_t event_birth_usec; + bool accept_failure; + int fd_stdout; + int fd_stderr; + char *result; + size_t result_size; + size_t result_len; + bool truncated; +} Spawn; + +static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Spawn *spawn = ASSERT_PTR(userdata); + char buf[4096], *p; + size_t size; + ssize_t l; + int r; + + assert(fd == spawn->fd_stdout || fd == spawn->fd_stderr); + assert(!spawn->result || spawn->result_len < spawn->result_size); + + if (fd == spawn->fd_stdout && spawn->result) { + p = spawn->result + spawn->result_len; + size = spawn->result_size - spawn->result_len; + } else { + p = buf; + size = sizeof(buf); + } + + l = read(fd, p, size - (p == buf)); + if (l < 0) { + if (errno == EAGAIN) + goto reenable; + + log_device_error_errno(spawn->device, errno, + "Failed to read stdout of '%s': %m", spawn->cmd); + + return 0; + } + + if ((size_t) l == size) { + log_device_warning(spawn->device, "Truncating stdout of '%s' up to %zu byte.", + spawn->cmd, spawn->result_size); + l--; + spawn->truncated = true; + } + + p[l] = '\0'; + if (fd == spawn->fd_stdout && spawn->result) + spawn->result_len += l; + + /* Log output only if we watch stderr. */ + if (l > 0 && spawn->fd_stderr >= 0) { + _cleanup_strv_free_ char **v = NULL; + + r = strv_split_newlines_full(&v, p, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + log_device_debug(spawn->device, + "Failed to split output from '%s'(%s), ignoring: %m", + spawn->cmd, fd == spawn->fd_stdout ? "out" : "err"); + + STRV_FOREACH(q, v) + log_device_debug(spawn->device, "'%s'(%s) '%s'", spawn->cmd, + fd == spawn->fd_stdout ? "out" : "err", *q); + } + + if (l == 0 || spawn->truncated) + return 0; + +reenable: + /* Re-enable the event source if we did not encounter EOF */ + + r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT); + if (r < 0) + log_device_error_errno(spawn->device, r, + "Failed to reactivate IO source of '%s'", spawn->cmd); + return 0; +} + +static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Spawn *spawn = ASSERT_PTR(userdata); + + DEVICE_TRACE_POINT(spawn_timeout, spawn->device, spawn->cmd); + + kill_and_sigcont(spawn->pid, spawn->timeout_signal); + + log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing", + spawn->cmd, spawn->pid, + FORMAT_TIMESPAN(spawn->timeout_usec, USEC_PER_SEC)); + + return 1; +} + +static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { + Spawn *spawn = ASSERT_PTR(userdata); + + log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete", + spawn->cmd, spawn->pid, + FORMAT_TIMESPAN(spawn->timeout_warn_usec, USEC_PER_SEC)); + + return 1; +} + +static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { + Spawn *spawn = ASSERT_PTR(userdata); + int ret = -EIO; + + switch (si->si_code) { + case CLD_EXITED: + if (si->si_status == 0) + log_device_debug(spawn->device, "Process '%s' succeeded.", spawn->cmd); + else + log_device_full(spawn->device, spawn->accept_failure ? LOG_DEBUG : LOG_WARNING, + "Process '%s' failed with exit code %i.", spawn->cmd, si->si_status); + ret = si->si_status; + break; + case CLD_KILLED: + case CLD_DUMPED: + log_device_error(spawn->device, "Process '%s' terminated by signal %s.", spawn->cmd, signal_to_string(si->si_status)); + break; + default: + log_device_error(spawn->device, "Process '%s' failed due to unknown reason.", spawn->cmd); + } + + DEVICE_TRACE_POINT(spawn_exit, spawn->device, spawn->cmd); + + sd_event_exit(sd_event_source_get_event(s), ret); + return 1; +} + +static int spawn_wait(Spawn *spawn) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *sigchld_source = NULL; + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stdout_source = NULL; + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *stderr_source = NULL; + int r; + + assert(spawn); + + r = sd_event_new(&e); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to allocate sd-event object: %m"); + + if (spawn->timeout_usec > 0) { + usec_t usec, age_usec; + + usec = now(CLOCK_MONOTONIC); + age_usec = usec - spawn->event_birth_usec; + if (age_usec < spawn->timeout_usec) { + if (spawn->timeout_warn_usec > 0 && + spawn->timeout_warn_usec < spawn->timeout_usec && + spawn->timeout_warn_usec > age_usec) { + spawn->timeout_warn_usec -= age_usec; + + r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, + usec + spawn->timeout_warn_usec, USEC_PER_SEC, + on_spawn_timeout_warning, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m"); + } + + spawn->timeout_usec -= age_usec; + + r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, + usec + spawn->timeout_usec, USEC_PER_SEC, on_spawn_timeout, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m"); + } + } + + if (spawn->fd_stdout >= 0) { + r = sd_event_add_io(e, &stdout_source, spawn->fd_stdout, EPOLLIN, on_spawn_io, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create stdio event source: %m"); + r = sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to enable stdio event source: %m"); + } + + if (spawn->fd_stderr >= 0) { + r = sd_event_add_io(e, &stderr_source, spawn->fd_stderr, EPOLLIN, on_spawn_io, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create stderr event source: %m"); + r = sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to enable stderr event source: %m"); + } + + r = sd_event_add_child(e, &sigchld_source, spawn->pid, WEXITED, on_spawn_sigchld, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create sigchild event source: %m"); + /* SIGCHLD should be processed after IO is complete */ + r = sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to set priority to sigchild event source: %m"); + + return sd_event_loop(e); +} + +int udev_event_spawn( + UdevEvent *event, + usec_t timeout_usec, + int timeout_signal, + bool accept_failure, + const char *cmd, + char *result, + size_t ressize, + bool *ret_truncated) { + + _cleanup_close_pair_ int outpipe[2] = PIPE_EBADF, errpipe[2] = PIPE_EBADF; + _cleanup_strv_free_ char **argv = NULL; + char **envp = NULL; + Spawn spawn; + pid_t pid; + int r; + + assert(event); + assert(event->dev); + assert(result || ressize == 0); + + /* pipes from child to parent */ + if (result || log_get_max_level() >= LOG_INFO) + if (pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) != 0) + return log_device_error_errno(event->dev, errno, + "Failed to create pipe for command '%s': %m", cmd); + + if (log_get_max_level() >= LOG_INFO) + if (pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) != 0) + return log_device_error_errno(event->dev, errno, + "Failed to create pipe for command '%s': %m", cmd); + + r = strv_split_full(&argv, cmd, NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_device_error_errno(event->dev, r, "Failed to split command: %m"); + + if (isempty(argv[0])) + return log_device_error_errno(event->dev, SYNTHETIC_ERRNO(EINVAL), + "Invalid command '%s'", cmd); + + /* allow programs in /usr/lib/udev/ to be called without the path */ + if (!path_is_absolute(argv[0])) { + char *program; + + program = path_join(UDEVLIBEXECDIR, argv[0]); + if (!program) + return log_oom(); + + free_and_replace(argv[0], program); + } + + r = device_get_properties_strv(event->dev, &envp); + if (r < 0) + return log_device_error_errno(event->dev, r, "Failed to get device properties"); + + log_device_debug(event->dev, "Starting '%s'", cmd); + + r = safe_fork_full("(spawn)", + (int[]) { -EBADF, outpipe[WRITE_END], errpipe[WRITE_END] }, + NULL, 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, + &pid); + if (r < 0) + return log_device_error_errno(event->dev, r, + "Failed to fork() to execute command '%s': %m", cmd); + if (r == 0) { + DEVICE_TRACE_POINT(spawn_exec, event->dev, cmd); + execve(argv[0], argv, envp); + _exit(EXIT_FAILURE); + } + + /* parent closed child's ends of pipes */ + outpipe[WRITE_END] = safe_close(outpipe[WRITE_END]); + errpipe[WRITE_END] = safe_close(errpipe[WRITE_END]); + + spawn = (Spawn) { + .device = event->dev, + .cmd = cmd, + .pid = pid, + .accept_failure = accept_failure, + .timeout_warn_usec = udev_warn_timeout(timeout_usec), + .timeout_usec = timeout_usec, + .timeout_signal = timeout_signal, + .event_birth_usec = event->birth_usec, + .fd_stdout = outpipe[READ_END], + .fd_stderr = errpipe[READ_END], + .result = result, + .result_size = ressize, + }; + r = spawn_wait(&spawn); + if (r < 0) + return log_device_error_errno(event->dev, r, + "Failed to wait for spawned command '%s': %m", cmd); + + if (result) + result[spawn.result_len] = '\0'; + + if (ret_truncated) + *ret_truncated = spawn.truncated; + + return r; /* 0 for success, and positive if the program failed */ +} + +void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal) { + const char *command; + void *val; + int r; + + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { + UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) { + log_device_debug(event->dev, "Running built-in command \"%s\"", command); + r = udev_builtin_run(event, builtin_cmd, command, false); + if (r < 0) + log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command); + } else { + if (event->exec_delay_usec > 0) { + log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.", + command, FORMAT_TIMESPAN(event->exec_delay_usec, USEC_PER_SEC)); + (void) usleep_safe(event->exec_delay_usec); + } + + log_device_debug(event->dev, "Running command \"%s\"", command); + + r = udev_event_spawn(event, timeout_usec, timeout_signal, false, command, NULL, 0, NULL); + if (r < 0) + log_device_warning_errno(event->dev, r, "Failed to execute '%s', ignoring: %m", command); + else if (r > 0) /* returned value is positive when program fails */ + log_device_debug(event->dev, "Command \"%s\" returned %d (error), ignoring.", command, r); + } + } +} diff --git a/src/udev/udev-spawn.h b/src/udev/udev-spawn.h new file mode 100644 index 0000000000..5efea2e886 --- /dev/null +++ b/src/udev/udev-spawn.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include +#include + +#include "macro.h" +#include "time-util.h" + +#define READ_END 0 +#define WRITE_END 1 + +typedef struct UdevEvent UdevEvent; + +int udev_event_spawn( + UdevEvent *event, + usec_t timeout_usec, + int timeout_signal, + bool accept_failure, + const char *cmd, + char *result, + size_t ressize, + bool *ret_truncated); +void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal); + +static inline usec_t udev_warn_timeout(usec_t timeout_usec) { + return DIV_ROUND_UP(timeout_usec, 3); +} diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 682ff56720..8590bf3bf5 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -21,6 +21,7 @@ #include "strxcpyx.h" #include "udev-builtin.h" #include "udev-event.h" +#include "udev-format.h" #include "udevadm-util.h" #include "udevadm.h" diff --git a/src/udev/udevd.c b/src/udev/udevd.c index b545abd3e4..a2b164e933 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -64,14 +64,15 @@ #include "strv.h" #include "strxcpyx.h" #include "syslog-util.h" -#include "udevd.h" #include "udev-builtin.h" #include "udev-ctrl.h" #include "udev-event.h" #include "udev-node.h" +#include "udev-spawn.h" #include "udev-trace.h" #include "udev-util.h" #include "udev-watch.h" +#include "udevd.h" #include "user-util.h" #include "version.h" From 162d516834d8f9cd2ae4ed7308c2f4d71388463e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 23:39:04 +0900 Subject: [PATCH 05/14] udev: rename test-udev-event.c -> test-udev-spawn.c --- src/udev/meson.build | 8 ++++---- src/udev/{test-udev-event.c => test-udev-spawn.c} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename src/udev/{test-udev-event.c => test-udev-spawn.c} (100%) diff --git a/src/udev/meson.build b/src/udev/meson.build index 3dbda3cc2f..e9a3e5bcec 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -212,12 +212,12 @@ tests += [ 'sources' : files('test-udev-builtin.c'), 'base' : test_libudev_base, }, - { - 'sources' : files('test-udev-event.c'), - 'base' : test_libudev_base, - }, { 'sources' : files('test-udev-node.c'), 'base' : test_libudev_base, }, + { + 'sources' : files('test-udev-spawn.c'), + 'base' : test_libudev_base, + }, ] diff --git a/src/udev/test-udev-event.c b/src/udev/test-udev-spawn.c similarity index 100% rename from src/udev/test-udev-event.c rename to src/udev/test-udev-spawn.c From 02267291815a524956fdd25f143f19781e69bfa9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 18 Jul 2023 16:10:36 +0900 Subject: [PATCH 06/14] udev: move several functions from udev-util.c to relevant udevd source files The functions are only used by udevd (and relevant tests), hence it is not necessary to be in src/shared. --- src/shared/udev-util.c | 152 -------------------------- src/shared/udev-util.h | 5 - src/test/test-udev-util.c | 113 ------------------- src/udev/fuzz-udev-rule-parse-value.c | 3 +- src/udev/meson.build | 22 +++- src/udev/test-udev-format.c | 37 +++++++ src/udev/test-udev-rules.c | 77 +++++++++++++ src/udev/test-udevd.c | 19 ++++ src/udev/udev-format.c | 65 +++++++++++ src/udev/udev-format.h | 2 + src/udev/udev-rules.c | 74 +++++++++++++ src/udev/udev-rules.h | 1 + src/udev/udevd.c | 13 +++ src/udev/udevd.h | 2 + 14 files changed, 308 insertions(+), 277 deletions(-) create mode 100644 src/udev/test-udev-format.c create mode 100644 src/udev/test-udev-rules.c create mode 100644 src/udev/test-udevd.c diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index fddf096d76..abf3c5e955 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -10,7 +10,6 @@ #include "device-util.h" #include "env-file.h" #include "errno-util.h" -#include "escape.h" #include "fd-util.h" #include "id128-util.h" #include "log.h" @@ -18,11 +17,9 @@ #include "parse-util.h" #include "path-util.h" #include "signal-util.h" -#include "socket-util.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" -#include "strxcpyx.h" #include "udev-util.h" #include "utf8.h" @@ -337,62 +334,6 @@ void log_device_uevent(sd_device *device, const char *str) { sd_id128_is_null(event_id) ? "" : SD_ID128_TO_UUID_STRING(event_id)); } -int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) { - char *i, *j; - bool is_escaped; - - /* value must be double quotated */ - is_escaped = str[0] == 'e'; - str += is_escaped; - if (str[0] != '"') - return -EINVAL; - - if (!is_escaped) { - /* unescape double quotation '\"'->'"' */ - for (j = str, i = str + 1; *i != '"'; i++, j++) { - if (*i == '\0') - return -EINVAL; - if (i[0] == '\\' && i[1] == '"') - i++; - *j = *i; - } - j[0] = '\0'; - /* - * The return value must be terminated by two subsequent NULs - * so it could be safely interpreted as nulstr. - */ - j[1] = '\0'; - } else { - _cleanup_free_ char *unescaped = NULL; - ssize_t l; - - /* find the end position of value */ - for (i = str + 1; *i != '"'; i++) { - if (i[0] == '\\') - i++; - if (*i == '\0') - return -EINVAL; - } - i[0] = '\0'; - - l = cunescape_length(str + 1, i - (str + 1), 0, &unescaped); - if (l < 0) - return l; - - assert(l <= i - (str + 1)); - memcpy(str, unescaped, l + 1); - /* - * The return value must be terminated by two subsequent NULs - * so it could be safely interpreted as nulstr. - */ - str[l + 1] = '\0'; - } - - *ret_value = str; - *ret_endpos = i + 1; - return 0; -} - size_t udev_replace_whitespace(const char *str, char *to, size_t len) { bool is_space = false; size_t i, j; @@ -435,22 +376,6 @@ size_t udev_replace_whitespace(const char *str, char *to, size_t len) { return j; } -size_t udev_replace_ifname(char *str) { - size_t replaced = 0; - - assert(str); - - /* See ifname_valid_full(). */ - - for (char *p = str; *p != '\0'; p++) - if (!ifname_valid_char(*p)) { - *p = '_'; - replaced++; - } - - return replaced; -} - size_t udev_replace_chars(char *str, const char *allow) { size_t i = 0, replaced = 0; @@ -495,83 +420,6 @@ size_t udev_replace_chars(char *str, const char *allow) { return replaced; } -int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) { - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - _cleanup_free_ char *temp = NULL; - char *subsys, *sysname, *attr; - const char *val; - int r; - - assert(string); - assert(result); - - /* handle "[/]" format */ - - if (string[0] != '[') - return -EINVAL; - - temp = strdup(string); - if (!temp) - return -ENOMEM; - - subsys = &temp[1]; - - sysname = strchr(subsys, '/'); - if (!sysname) - return -EINVAL; - sysname[0] = '\0'; - sysname = &sysname[1]; - - attr = strchr(sysname, ']'); - if (!attr) - return -EINVAL; - attr[0] = '\0'; - attr = &attr[1]; - if (attr[0] == '/') - attr = &attr[1]; - if (attr[0] == '\0') - attr = NULL; - - if (read_value && !attr) - return -EINVAL; - - r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname); - if (r < 0) - return r; - - if (read_value) { - r = sd_device_get_sysattr_value(dev, attr, &val); - if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT) - return r; - if (r >= 0) - strscpy(result, maxsize, val); - else - result[0] = '\0'; - log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); - } else { - r = sd_device_get_syspath(dev, &val); - if (r < 0) - return r; - - strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL); - log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result); - } - return 0; -} - -bool devpath_conflict(const char *a, const char *b) { - /* This returns true when two paths are equivalent, or one is a child of another. */ - - if (!a || !b) - return false; - - for (; *a != '\0' && *b != '\0'; a++, b++) - if (*a != *b) - return false; - - return *a == '/' || *b == '/' || *a == *b; -} - int udev_queue_is_empty(void) { return access("/run/udev/queue", F_OK) < 0 ? (errno == ENOENT ? true : -errno) : false; diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index c848cf6177..97c14fb9bb 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -39,13 +39,8 @@ bool device_for_action(sd_device *dev, sd_device_action_t action); void log_device_uevent(sd_device *device, const char *str); -int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos); size_t udev_replace_whitespace(const char *str, char *to, size_t len); -size_t udev_replace_ifname(char *str); size_t udev_replace_chars(char *str, const char *allow); -int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value); - -bool devpath_conflict(const char *a, const char *b); int udev_queue_is_empty(void); diff --git a/src/test/test-udev-util.c b/src/test/test-udev-util.c index 4be3694e9e..cb80c69c96 100644 --- a/src/test/test-udev-util.c +++ b/src/test/test-udev-util.c @@ -8,76 +8,6 @@ #include "tests.h" #include "udev-util.h" -static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, int expected_retval) { - _cleanup_free_ char *str = NULL; - char *value = UINT_TO_PTR(0x12345678U); - char *endpos = UINT_TO_PTR(0x87654321U); - - log_info("/* %s (%s, %s, %d) */", __func__, in, strnull(expected_value), expected_retval); - - assert_se(str = strdup(in)); - assert_se(udev_rule_parse_value(str, &value, &endpos) == expected_retval); - if (expected_retval < 0) { - /* not modified on failure */ - assert_se(value == UINT_TO_PTR(0x12345678U)); - assert_se(endpos == UINT_TO_PTR(0x87654321U)); - } else { - assert_se(streq_ptr(value, expected_value)); - assert_se(endpos == str + strlen(in)); - /* - * The return value must be terminated by two subsequent NULs - * so it could be safely interpreted as nulstr. - */ - assert_se(value[strlen(value) + 1] == '\0'); - } -} - -TEST(udev_rule_parse_value) { - /* input: "valid operand" - * parsed: valid operand - * use the following command to help generate textual C strings: - * python3 -c 'import json; print(json.dumps(input()))' */ - test_udev_rule_parse_value_one("\"valid operand\"", "valid operand", 0); - /* input: "va'l\'id\"op\"erand" - * parsed: va'l\'id"op"erand */ - test_udev_rule_parse_value_one("\"va'l\\'id\\\"op\\\"erand\"", "va'l\\'id\"op\"erand", 0); - test_udev_rule_parse_value_one("no quotes", NULL, -EINVAL); - test_udev_rule_parse_value_one("\"\\\\a\\b\\x\\y\"", "\\\\a\\b\\x\\y", 0); - test_udev_rule_parse_value_one("\"reject\0nul\"", NULL, -EINVAL); - /* input: e"" */ - test_udev_rule_parse_value_one("e\"\"", "", 0); - /* input: e"1234" */ - test_udev_rule_parse_value_one("e\"1234\"", "1234", 0); - /* input: e"\"" */ - test_udev_rule_parse_value_one("e\"\\\"\"", "\"", 0); - /* input: e"\ */ - test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL); - /* input: e"\" */ - test_udev_rule_parse_value_one("e\"\\\"", NULL, -EINVAL); - /* input: e"\\" */ - test_udev_rule_parse_value_one("e\"\\\\\"", "\\", 0); - /* input: e"\\\" */ - test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, -EINVAL); - /* input: e"\\\"" */ - test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", 0); - /* input: e"\\\\" */ - test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", 0); - /* input: e"operand with newline\n" */ - test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", 0); - /* input: e"single\rcharacter\t\aescape\bsequence" */ - test_udev_rule_parse_value_one( - "e\"single\\rcharacter\\t\\aescape\\bsequence\"", "single\rcharacter\t\aescape\bsequence", 0); - /* input: e"reject\invalid escape sequence" */ - test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, -EINVAL); - /* input: e"\ */ - test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL); - /* input: "s\u1d1c\u1d04\u029c \u1d1c\u0274\u026a\u1d04\u1d0f\u1d05\u1d07 \U0001d568\U0001d560\U0001d568" */ - test_udev_rule_parse_value_one( - "e\"s\\u1d1c\\u1d04\\u029c \\u1d1c\\u0274\\u026a\\u1d04\\u1d0f\\u1d05\\u1d07 \\U0001d568\\U0001d560\\U0001d568\"", - "s\xe1\xb4\x9c\xe1\xb4\x84\xca\x9c \xe1\xb4\x9c\xc9\xb4\xc9\xaa\xe1\xb4\x84\xe1\xb4\x8f\xe1\xb4\x85\xe1\xb4\x87 \xf0\x9d\x95\xa8\xf0\x9d\x95\xa0\xf0\x9d\x95\xa8", - 0); -} - static void test_udev_replace_whitespace_one_len(const char *str, size_t len, const char *expected) { _cleanup_free_ char *result = NULL; int r; @@ -130,47 +60,4 @@ TEST(udev_replace_whitespace) { test_udev_replace_whitespace_one_len(" hoge hoge ", 0, ""); } -static void test_udev_resolve_subsys_kernel_one(const char *str, bool read_value, int retval, const char *expected) { - char result[PATH_MAX] = ""; - int r; - - r = udev_resolve_subsys_kernel(str, result, sizeof(result), read_value); - log_info("\"%s\" → expect: \"%s\", %d, actual: \"%s\", %d", str, strnull(expected), retval, result, r); - assert_se(r == retval); - if (r >= 0) - assert_se(streq(result, expected)); -} - -TEST(udev_resolve_subsys_kernel) { - test_udev_resolve_subsys_kernel_one("hoge", false, -EINVAL, NULL); - test_udev_resolve_subsys_kernel_one("[hoge", false, -EINVAL, NULL); - test_udev_resolve_subsys_kernel_one("[hoge/foo", false, -EINVAL, NULL); - test_udev_resolve_subsys_kernel_one("[hoge/]", false, -EINVAL, NULL); - - test_udev_resolve_subsys_kernel_one("[net/lo]", false, 0, "/sys/devices/virtual/net/lo"); - test_udev_resolve_subsys_kernel_one("[net/lo]/", false, 0, "/sys/devices/virtual/net/lo"); - test_udev_resolve_subsys_kernel_one("[net/lo]hoge", false, 0, "/sys/devices/virtual/net/lo/hoge"); - test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", false, 0, "/sys/devices/virtual/net/lo/hoge"); - - test_udev_resolve_subsys_kernel_one("[net/lo]", true, -EINVAL, NULL); - test_udev_resolve_subsys_kernel_one("[net/lo]/", true, -EINVAL, NULL); - test_udev_resolve_subsys_kernel_one("[net/lo]hoge", true, 0, ""); - test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", true, 0, ""); - test_udev_resolve_subsys_kernel_one("[net/lo]address", true, 0, "00:00:00:00:00:00"); - test_udev_resolve_subsys_kernel_one("[net/lo]/address", true, 0, "00:00:00:00:00:00"); -} - -TEST(devpath_conflict) { - assert_se(!devpath_conflict(NULL, NULL)); - assert_se(!devpath_conflict(NULL, "/devices/pci0000:00/0000:00:1c.4")); - assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", NULL)); - assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:00.0")); - assert_se(!devpath_conflict("/devices/virtual/net/veth99", "/devices/virtual/net/veth999")); - - assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4")); - assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0")); - assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1", - "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1/nvme0n1p1")); -} - DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/udev/fuzz-udev-rule-parse-value.c b/src/udev/fuzz-udev-rule-parse-value.c index 404d0cd142..c7b85044cd 100644 --- a/src/udev/fuzz-udev-rule-parse-value.c +++ b/src/udev/fuzz-udev-rule-parse-value.c @@ -4,7 +4,7 @@ #include "alloc-util.h" #include "fuzz.h" -#include "udev-util.h" +#include "udev-rules.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_free_ char *str = NULL; @@ -17,7 +17,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { str[size] = '\0'; r = udev_rule_parse_value(str, &value, &endpos); - if (r < 0) { /* not modified on failure */ assert_se(value == UINT_TO_PTR(0x12345678U)); diff --git a/src/udev/meson.build b/src/udev/meson.build index e9a3e5bcec..1828c58470 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -14,7 +14,6 @@ udevadm_sources = files( 'udevadm-verify.c', 'udevadm-wait.c', 'udevadm.c', - 'udevd.c', ) libudevd_core_sources = files( @@ -35,6 +34,7 @@ libudevd_core_sources = files( 'udev-builtin-path_id.c', 'udev-builtin-usb_id.c', 'udev-builtin.c', + 'udevd.c', ) if conf.get('HAVE_KMOD') == 1 @@ -169,10 +169,6 @@ if install_sysconfdir mkdir_p.format(sysconfdir / 'udev/rules.d')) endif -simple_fuzzers += files( - 'fuzz-udev-rule-parse-value.c', -) - fuzzer_udev_base = { 'link_with' : [libudevd_core, libshared], 'dependencies' : [threads, libacl], @@ -184,6 +180,10 @@ fuzzers += [ 'includes' : udev_includes, 'base' : fuzzer_udev_base, }, + { + 'sources' : files('fuzz-udev-rule-parse-value.c'), + 'base' : fuzzer_udev_base, + }, { 'sources' : files('fuzz-udev-rules.c'), 'base' : fuzzer_udev_base, @@ -212,12 +212,24 @@ tests += [ 'sources' : files('test-udev-builtin.c'), 'base' : test_libudev_base, }, + { + 'sources' : files('test-udev-format.c'), + 'base' : test_libudev_base, + }, { 'sources' : files('test-udev-node.c'), 'base' : test_libudev_base, }, + { + 'sources' : files('test-udev-rules.c'), + 'base' : test_libudev_base, + }, { 'sources' : files('test-udev-spawn.c'), 'base' : test_libudev_base, }, + { + 'sources' : files('test-udevd.c'), + 'base' : test_libudev_base, + }, ] diff --git a/src/udev/test-udev-format.c b/src/udev/test-udev-format.c new file mode 100644 index 0000000000..d8e38082fd --- /dev/null +++ b/src/udev/test-udev-format.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "string-util.h" +#include "tests.h" +#include "udev-format.h" + +static void test_udev_resolve_subsys_kernel_one(const char *str, bool read_value, int retval, const char *expected) { + char result[PATH_MAX] = ""; + int r; + + r = udev_resolve_subsys_kernel(str, result, sizeof(result), read_value); + log_info("\"%s\" → expect: \"%s\", %d, actual: \"%s\", %d", str, strnull(expected), retval, result, r); + assert_se(r == retval); + if (r >= 0) + assert_se(streq(result, expected)); +} + +TEST(udev_resolve_subsys_kernel) { + test_udev_resolve_subsys_kernel_one("hoge", false, -EINVAL, NULL); + test_udev_resolve_subsys_kernel_one("[hoge", false, -EINVAL, NULL); + test_udev_resolve_subsys_kernel_one("[hoge/foo", false, -EINVAL, NULL); + test_udev_resolve_subsys_kernel_one("[hoge/]", false, -EINVAL, NULL); + + test_udev_resolve_subsys_kernel_one("[net/lo]", false, 0, "/sys/devices/virtual/net/lo"); + test_udev_resolve_subsys_kernel_one("[net/lo]/", false, 0, "/sys/devices/virtual/net/lo"); + test_udev_resolve_subsys_kernel_one("[net/lo]hoge", false, 0, "/sys/devices/virtual/net/lo/hoge"); + test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", false, 0, "/sys/devices/virtual/net/lo/hoge"); + + test_udev_resolve_subsys_kernel_one("[net/lo]", true, -EINVAL, NULL); + test_udev_resolve_subsys_kernel_one("[net/lo]/", true, -EINVAL, NULL); + test_udev_resolve_subsys_kernel_one("[net/lo]hoge", true, 0, ""); + test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", true, 0, ""); + test_udev_resolve_subsys_kernel_one("[net/lo]address", true, 0, "00:00:00:00:00:00"); + test_udev_resolve_subsys_kernel_one("[net/lo]/address", true, 0, "00:00:00:00:00:00"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/udev/test-udev-rules.c b/src/udev/test-udev-rules.c new file mode 100644 index 0000000000..b62b08b56e --- /dev/null +++ b/src/udev/test-udev-rules.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "string-util.h" +#include "tests.h" +#include "udev-rules.h" + +static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, int expected_retval) { + _cleanup_free_ char *str = NULL; + char *value = UINT_TO_PTR(0x12345678U); + char *endpos = UINT_TO_PTR(0x87654321U); + + log_info("/* %s (%s, %s, %d) */", __func__, in, strnull(expected_value), expected_retval); + + assert_se(str = strdup(in)); + assert_se(udev_rule_parse_value(str, &value, &endpos) == expected_retval); + if (expected_retval < 0) { + /* not modified on failure */ + assert_se(value == UINT_TO_PTR(0x12345678U)); + assert_se(endpos == UINT_TO_PTR(0x87654321U)); + } else { + assert_se(streq_ptr(value, expected_value)); + assert_se(endpos == str + strlen(in)); + /* + * The return value must be terminated by two subsequent NULs + * so it could be safely interpreted as nulstr. + */ + assert_se(value[strlen(value) + 1] == '\0'); + } +} + +TEST(udev_rule_parse_value) { + /* input: "valid operand" + * parsed: valid operand + * use the following command to help generate textual C strings: + * python3 -c 'import json; print(json.dumps(input()))' */ + test_udev_rule_parse_value_one("\"valid operand\"", "valid operand", 0); + /* input: "va'l\'id\"op\"erand" + * parsed: va'l\'id"op"erand */ + test_udev_rule_parse_value_one("\"va'l\\'id\\\"op\\\"erand\"", "va'l\\'id\"op\"erand", 0); + test_udev_rule_parse_value_one("no quotes", NULL, -EINVAL); + test_udev_rule_parse_value_one("\"\\\\a\\b\\x\\y\"", "\\\\a\\b\\x\\y", 0); + test_udev_rule_parse_value_one("\"reject\0nul\"", NULL, -EINVAL); + /* input: e"" */ + test_udev_rule_parse_value_one("e\"\"", "", 0); + /* input: e"1234" */ + test_udev_rule_parse_value_one("e\"1234\"", "1234", 0); + /* input: e"\"" */ + test_udev_rule_parse_value_one("e\"\\\"\"", "\"", 0); + /* input: e"\ */ + test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL); + /* input: e"\" */ + test_udev_rule_parse_value_one("e\"\\\"", NULL, -EINVAL); + /* input: e"\\" */ + test_udev_rule_parse_value_one("e\"\\\\\"", "\\", 0); + /* input: e"\\\" */ + test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, -EINVAL); + /* input: e"\\\"" */ + test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", 0); + /* input: e"\\\\" */ + test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", 0); + /* input: e"operand with newline\n" */ + test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", 0); + /* input: e"single\rcharacter\t\aescape\bsequence" */ + test_udev_rule_parse_value_one( + "e\"single\\rcharacter\\t\\aescape\\bsequence\"", "single\rcharacter\t\aescape\bsequence", 0); + /* input: e"reject\invalid escape sequence" */ + test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, -EINVAL); + /* input: e"\ */ + test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL); + /* input: "s\u1d1c\u1d04\u029c \u1d1c\u0274\u026a\u1d04\u1d0f\u1d05\u1d07 \U0001d568\U0001d560\U0001d568" */ + test_udev_rule_parse_value_one( + "e\"s\\u1d1c\\u1d04\\u029c \\u1d1c\\u0274\\u026a\\u1d04\\u1d0f\\u1d05\\u1d07 \\U0001d568\\U0001d560\\U0001d568\"", + "s\xe1\xb4\x9c\xe1\xb4\x84\xca\x9c \xe1\xb4\x9c\xc9\xb4\xc9\xaa\xe1\xb4\x84\xe1\xb4\x8f\xe1\xb4\x85\xe1\xb4\x87 \xf0\x9d\x95\xa8\xf0\x9d\x95\xa0\xf0\x9d\x95\xa8", + 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/udev/test-udevd.c b/src/udev/test-udevd.c new file mode 100644 index 0000000000..58c88853a7 --- /dev/null +++ b/src/udev/test-udevd.c @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "tests.h" +#include "udevd.h" + +TEST(devpath_conflict) { + assert_se(!devpath_conflict(NULL, NULL)); + assert_se(!devpath_conflict(NULL, "/devices/pci0000:00/0000:00:1c.4")); + assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", NULL)); + assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:00.0")); + assert_se(!devpath_conflict("/devices/virtual/net/veth99", "/devices/virtual/net/veth999")); + + assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4")); + assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0")); + assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1", + "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1/nvme0n1p1")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/udev/udev-format.c b/src/udev/udev-format.c index b33d8646c6..b17b754b5e 100644 --- a/src/udev/udev-format.c +++ b/src/udev/udev-format.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "device-util.h" +#include "errno-util.h" #include "parse-util.h" #include "string-util.h" #include "strxcpyx.h" @@ -481,3 +482,67 @@ int udev_check_format(const char *value, size_t *offset, const char **hint) { return 0; } + +int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + _cleanup_free_ char *temp = NULL; + char *subsys, *sysname, *attr; + const char *val; + int r; + + assert(string); + assert(result); + + /* handle "[/]" format */ + + if (string[0] != '[') + return -EINVAL; + + temp = strdup(string); + if (!temp) + return -ENOMEM; + + subsys = &temp[1]; + + sysname = strchr(subsys, '/'); + if (!sysname) + return -EINVAL; + sysname[0] = '\0'; + sysname = &sysname[1]; + + attr = strchr(sysname, ']'); + if (!attr) + return -EINVAL; + attr[0] = '\0'; + attr = &attr[1]; + if (attr[0] == '/') + attr = &attr[1]; + if (attr[0] == '\0') + attr = NULL; + + if (read_value && !attr) + return -EINVAL; + + r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname); + if (r < 0) + return r; + + if (read_value) { + r = sd_device_get_sysattr_value(dev, attr, &val); + if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT) + return r; + if (r >= 0) + strscpy(result, maxsize, val); + else + result[0] = '\0'; + log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } else { + r = sd_device_get_syspath(dev, &val); + if (r < 0) + return r; + + strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL); + log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result); + } + return 0; +} diff --git a/src/udev/udev-format.h b/src/udev/udev-format.h index 0dbdd561b1..9914dc03b2 100644 --- a/src/udev/udev-format.h +++ b/src/udev/udev-format.h @@ -16,3 +16,5 @@ size_t udev_event_apply_format( bool replace_whitespace, bool *ret_truncated); int udev_check_format(const char *value, size_t *offset, const char **hint); + +int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index c4f2a7ffbb..52d3f354fa 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -11,6 +11,7 @@ #include "device-private.h" #include "device-util.h" #include "dirent-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" @@ -23,6 +24,7 @@ #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" +#include "socket-util.h" #include "stat-util.h" #include "strv.h" #include "strxcpyx.h" @@ -1118,6 +1120,62 @@ static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) { } } +int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) { + char *i, *j; + bool is_escaped; + + /* value must be double quotated */ + is_escaped = str[0] == 'e'; + str += is_escaped; + if (str[0] != '"') + return -EINVAL; + + if (!is_escaped) { + /* unescape double quotation '\"'->'"' */ + for (j = str, i = str + 1; *i != '"'; i++, j++) { + if (*i == '\0') + return -EINVAL; + if (i[0] == '\\' && i[1] == '"') + i++; + *j = *i; + } + j[0] = '\0'; + /* + * The return value must be terminated by two subsequent NULs + * so it could be safely interpreted as nulstr. + */ + j[1] = '\0'; + } else { + _cleanup_free_ char *unescaped = NULL; + ssize_t l; + + /* find the end position of value */ + for (i = str + 1; *i != '"'; i++) { + if (i[0] == '\\') + i++; + if (*i == '\0') + return -EINVAL; + } + i[0] = '\0'; + + l = cunescape_length(str + 1, i - (str + 1), 0, &unescaped); + if (l < 0) + return l; + + assert(l <= i - (str + 1)); + memcpy(str, unescaped, l + 1); + /* + * The return value must be terminated by two subsequent NULs + * so it could be safely interpreted as nulstr. + */ + str[l + 1] = '\0'; + } + + *ret_value = str; + *ret_endpos = i + 1; + return 0; +} + static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) { char *key_begin, *key_end, *attr, *tmp; UdevRuleOperatorType op; @@ -1850,6 +1908,22 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) { return -ENOENT; } +static size_t udev_replace_ifname(char *str) { + size_t replaced = 0; + + assert(str); + + /* See ifname_valid_full(). */ + + for (char *p = str; *p != '\0'; p++) + if (!ifname_valid_char(*p)) { + *p = '_'; + replaced++; + } + + return replaced; +} + static int udev_rule_apply_token_to_event( UdevRuleToken *token, sd_device *dev, diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 9b477fb235..075f8b5ba0 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -18,6 +18,7 @@ typedef enum { _ESCAPE_TYPE_INVALID = -EINVAL, } UdevRuleEscapeType; +int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos); int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret); unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file); UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index a2b164e933..4fd6db8047 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -889,6 +889,19 @@ static int event_run(Event *event) { return 1; /* event is now processing. */ } +bool devpath_conflict(const char *a, const char *b) { + /* This returns true when two paths are equivalent, or one is a child of another. */ + + if (!a || !b) + return false; + + for (; *a != '\0' && *b != '\0'; a++, b++) + if (*a != *b) + return false; + + return *a == '/' || *b == '/' || *a == *b; +} + static int event_is_blocked(Event *event) { Event *loop_event = NULL; int r; diff --git a/src/udev/udevd.h b/src/udev/udevd.h index 583e895225..891196c474 100644 --- a/src/udev/udevd.h +++ b/src/udev/udevd.h @@ -2,3 +2,5 @@ #pragma once int run_udevd(int argc, char *argv[]); + +bool devpath_conflict(const char *a, const char *b); From 987b90f93f59ac5f4a4240c17f7bb285e2f0859a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 02:11:16 +0900 Subject: [PATCH 07/14] udev: split-out worker code from udevd.c While doing that, the Manager object allocated by the main process is now _not_ freed in the spawned worker process, to follow our usual coding style. See discussion in #24043. --- src/udev/meson.build | 1 + src/udev/udev-worker.c | 352 ++++++++++++++++++++++++++++++++++++++++ src/udev/udev-worker.h | 52 ++++++ src/udev/udevd.c | 359 +++-------------------------------------- src/udev/udevd.h | 2 + 5 files changed, 425 insertions(+), 341 deletions(-) create mode 100644 src/udev/udev-worker.c create mode 100644 src/udev/udev-worker.h diff --git a/src/udev/meson.build b/src/udev/meson.build index 1828c58470..5687b70a1e 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -25,6 +25,7 @@ libudevd_core_sources = files( 'udev-rules.c', 'udev-spawn.c', 'udev-watch.c', + 'udev-worker.c', 'udev-builtin-btrfs.c', 'udev-builtin-hwdb.c', 'udev-builtin-input_id.c', diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c new file mode 100644 index 0000000000..996406a142 --- /dev/null +++ b/src/udev/udev-worker.c @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "blockdev-util.h" +#include "common-signal.h" +#include "device-monitor-private.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "udev-event.h" +#include "udev-spawn.h" +#include "udev-trace.h" +#include "udev-util.h" +#include "udev-watch.h" +#include "udev-worker.h" + +void udev_worker_done(UdevWorker *worker) { + assert(worker); + + sd_event_unref(worker->event); + sd_netlink_unref(worker->rtnl); + sd_device_monitor_unref(worker->monitor); + hashmap_free(worker->properties); + udev_rules_free(worker->rules); + safe_close(worker->pipe_fd); +} + +int udev_get_whole_disk(sd_device *dev, sd_device **ret_device, const char **ret_devname) { + const char *val; + int r; + + assert(dev); + + if (device_for_action(dev, SD_DEVICE_REMOVE)) + goto irrelevant; + + r = sd_device_get_sysname(dev, &val); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get sysname: %m"); + + /* Exclude the following devices: + * For "dm-", see the comment added by e918a1b5a94f270186dca59156354acd2a596494. + * For "md", see the commit message of 2e5b17d01347d3c3118be2b8ad63d20415dbb1f0, + * but not sure the assumption is still valid even when partitions are created on the md + * devices, surprisingly which seems to be possible, see PR #22973. + * For "drbd", see the commit message of fee854ee8ccde0cd28e0f925dea18cce35f3993d. */ + if (STARTSWITH_SET(val, "dm-", "md", "drbd")) + goto irrelevant; + + r = block_device_get_whole_disk(dev, &dev); + if (IN_SET(r, + -ENOTBLK, /* The device is not a block device. */ + -ENODEV /* The whole disk device was not found, it may already be removed. */)) + goto irrelevant; + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get whole disk device: %m"); + + r = sd_device_get_devname(dev, &val); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get devname: %m"); + + if (ret_device) + *ret_device = dev; + if (ret_devname) + *ret_devname = val; + return 1; + +irrelevant: + if (ret_device) + *ret_device = NULL; + if (ret_devname) + *ret_devname = NULL; + return 0; +} + +static int worker_lock_whole_disk(sd_device *dev, int *ret_fd) { + _cleanup_close_ int fd = -EBADF; + sd_device *dev_whole_disk; + const char *val; + int r; + + assert(dev); + assert(ret_fd); + + /* Take a shared lock on the device node; this establishes a concept of device "ownership" to + * serialize device access. External processes holding an exclusive lock will cause udev to skip the + * event handling; in the case udev acquired the lock, the external process can block until udev has + * finished its event handling. */ + + r = udev_get_whole_disk(dev, &dev_whole_disk, &val); + if (r < 0) + return r; + if (r == 0) + goto nolock; + + fd = sd_device_open(dev_whole_disk, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + bool ignore = ERRNO_IS_DEVICE_ABSENT(fd); + + log_device_debug_errno(dev, fd, "Failed to open '%s'%s: %m", val, ignore ? ", ignoring" : ""); + if (!ignore) + return fd; + + goto nolock; + } + + if (flock(fd, LOCK_SH|LOCK_NB) < 0) + return log_device_debug_errno(dev, errno, "Failed to flock(%s): %m", val); + + *ret_fd = TAKE_FD(fd); + return 1; + +nolock: + *ret_fd = -EBADF; + return 0; +} + +static int worker_mark_block_device_read_only(sd_device *dev) { + _cleanup_close_ int fd = -EBADF; + const char *val; + int state = 1, r; + + assert(dev); + + /* Do this only once, when the block device is new. If the device is later retriggered let's not + * toggle the bit again, so that people can boot up with full read-only mode and then unset the bit + * for specific devices only. */ + if (!device_for_action(dev, SD_DEVICE_ADD)) + return 0; + + r = sd_device_get_subsystem(dev, &val); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get subsystem: %m"); + + if (!streq(val, "block")) + return 0; + + r = sd_device_get_sysname(dev, &val); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get sysname: %m"); + + /* Exclude synthetic devices for now, this is supposed to be a safety feature to avoid modification + * of physical devices, and what sits on top of those doesn't really matter if we don't allow the + * underlying block devices to receive changes. */ + if (STARTSWITH_SET(val, "dm-", "md", "drbd", "loop", "nbd", "zram")) + return 0; + + fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_device_debug_errno(dev, fd, "Failed to open '%s', ignoring: %m", val); + + if (ioctl(fd, BLKROSET, &state) < 0) + return log_device_warning_errno(dev, errno, "Failed to mark block device '%s' read-only: %m", val); + + log_device_info(dev, "Successfully marked block device '%s' read-only.", val); + return 0; +} + +static int worker_process_device(UdevWorker *worker, sd_device *dev) { + _cleanup_(udev_event_freep) UdevEvent *udev_event = NULL; + _cleanup_close_ int fd_lock = -EBADF; + int r; + + assert(worker); + assert(dev); + + log_device_uevent(dev, "Processing device"); + + udev_event = udev_event_new(dev, worker->exec_delay_usec, worker->rtnl, worker->log_level); + if (!udev_event) + return -ENOMEM; + + /* If this is a block device and the device is locked currently via the BSD advisory locks, + * someone else is using it exclusively. We don't run our udev rules now to not interfere. + * Instead of processing the event, we requeue the event and will try again after a delay. + * + * The user-facing side of this: https://systemd.io/BLOCK_DEVICE_LOCKING */ + r = worker_lock_whole_disk(dev, &fd_lock); + if (r == -EAGAIN) + return EVENT_RESULT_TRY_AGAIN; + if (r < 0) + return r; + + if (worker->blockdev_read_only) + (void) worker_mark_block_device_read_only(dev); + + /* apply rules, create node, symlinks */ + r = udev_event_execute_rules( + udev_event, + worker->inotify_fd, + worker->timeout_usec, + worker->timeout_signal, + worker->properties, + worker->rules); + if (r < 0) + return r; + + udev_event_execute_run(udev_event, worker->timeout_usec, worker->timeout_signal); + + if (!worker->rtnl) + /* in case rtnl was initialized */ + worker->rtnl = sd_netlink_ref(udev_event->rtnl); + + if (udev_event->inotify_watch) { + r = udev_watch_begin(worker->inotify_fd, dev); + if (r < 0 && r != -ENOENT) /* The device may be already removed, ignore -ENOENT. */ + log_device_warning_errno(dev, r, "Failed to add inotify watch, ignoring: %m"); + } + + log_device_uevent(dev, "Device processed"); + return 0; +} + +void udev_broadcast_result(sd_device_monitor *monitor, sd_device *dev, EventResult result) { + int r; + + assert(dev); + + /* On exit, manager->monitor is already NULL. */ + if (!monitor) + return; + + if (result != EVENT_RESULT_SUCCESS) { + (void) device_add_property(dev, "UDEV_WORKER_FAILED", "1"); + + switch (result) { + case EVENT_RESULT_NERRNO_MIN ... EVENT_RESULT_NERRNO_MAX: { + const char *str; + + (void) device_add_propertyf(dev, "UDEV_WORKER_ERRNO", "%i", -result); + + str = errno_to_name(result); + if (str) + (void) device_add_property(dev, "UDEV_WORKER_ERRNO_NAME", str); + break; + } + case EVENT_RESULT_EXIT_STATUS_BASE ... EVENT_RESULT_EXIT_STATUS_MAX: + (void) device_add_propertyf(dev, "UDEV_WORKER_EXIT_STATUS", "%i", result - EVENT_RESULT_EXIT_STATUS_BASE); + break; + + case EVENT_RESULT_TRY_AGAIN: + assert_not_reached(); + break; + + case EVENT_RESULT_SIGNAL_BASE ... EVENT_RESULT_SIGNAL_MAX: { + const char *str; + + (void) device_add_propertyf(dev, "UDEV_WORKER_SIGNAL", "%i", result - EVENT_RESULT_SIGNAL_BASE); + + str = signal_to_string(result - EVENT_RESULT_SIGNAL_BASE); + if (str) + (void) device_add_property(dev, "UDEV_WORKER_SIGNAL_NAME", str); + break; + } + default: + log_device_warning(dev, "Unknown event result \"%i\", ignoring.", result); + } + } + + r = device_monitor_send_device(monitor, NULL, dev); + if (r < 0) + log_device_warning_errno(dev, r, + "Failed to broadcast event to libudev listeners, ignoring: %m"); +} + +static int worker_send_result(UdevWorker *worker, EventResult result) { + assert(worker); + assert(worker->pipe_fd >= 0); + + return loop_write(worker->pipe_fd, &result, sizeof(result), /* do_poll = */ false); +} + +static int worker_device_monitor_handler(sd_device_monitor *monitor, sd_device *dev, void *userdata) { + UdevWorker *worker = ASSERT_PTR(userdata); + int r; + + assert(dev); + + r = worker_process_device(worker, dev); + if (r == EVENT_RESULT_TRY_AGAIN) + /* if we couldn't acquire the flock(), then requeue the event */ + log_device_debug(dev, "Block device is currently locked, requeueing the event."); + else { + if (r < 0) + log_device_warning_errno(dev, r, "Failed to process device, ignoring: %m"); + + /* send processed event back to libudev listeners */ + udev_broadcast_result(monitor, dev, r); + } + + /* send udevd the result of the event execution */ + r = worker_send_result(worker, r); + if (r < 0) + log_device_warning_errno(dev, r, "Failed to send signal to main daemon, ignoring: %m"); + + /* Reset the log level, as it might be changed by "OPTIONS=log_level=". */ + log_set_max_level(worker->log_level); + + return 1; +} + +int udev_worker_main(UdevWorker *worker, sd_device *dev) { + int r; + + assert(worker); + assert(worker->monitor); + assert(dev); + + DEVICE_TRACE_POINT(worker_spawned, dev, getpid_cached()); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, -1) >= 0); + + /* Reset OOM score, we only protect the main daemon. */ + r = set_oom_score_adjust(0); + if (r < 0) + log_debug_errno(r, "Failed to reset OOM score, ignoring: %m"); + + r = sd_event_new(&worker->event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = sd_event_add_signal(worker->event, NULL, SIGTERM, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set SIGTERM event: %m"); + + r = sd_device_monitor_attach_event(worker->monitor, worker->event); + if (r < 0) + return log_error_errno(r, "Failed to attach event loop to device monitor: %m"); + + r = sd_device_monitor_start(worker->monitor, worker_device_monitor_handler, worker); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + /* Process first device */ + (void) worker_device_monitor_handler(worker->monitor, dev, worker); + + r = sd_event_loop(worker->event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + + return 0; +} diff --git a/src/udev/udev-worker.h b/src/udev/udev-worker.h new file mode 100644 index 0000000000..05c319e309 --- /dev/null +++ b/src/udev/udev-worker.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include + +#include "sd-device.h" +#include "sd-event.h" +#include "sd-netlink.h" + +#include "errno-list.h" +#include "hashmap.h" +#include "time-util.h" + +typedef struct UdevRules UdevRules; + +typedef struct UdevWorker { + sd_event *event; + sd_netlink *rtnl; + sd_device_monitor *monitor; + + Hashmap *properties; + UdevRules *rules; + + int pipe_fd; + int inotify_fd; /* Do not close! */ + + usec_t exec_delay_usec; + usec_t timeout_usec; + int timeout_signal; + int log_level; + bool blockdev_read_only; +} UdevWorker; + +/* passed from worker to main process */ +typedef enum EventResult { + EVENT_RESULT_NERRNO_MIN = -ERRNO_MAX, + EVENT_RESULT_NERRNO_MAX = -1, + EVENT_RESULT_SUCCESS = 0, + EVENT_RESULT_EXIT_STATUS_BASE = 0, + EVENT_RESULT_EXIT_STATUS_MAX = 255, + EVENT_RESULT_TRY_AGAIN = 256, /* when the block device is locked by another process. */ + EVENT_RESULT_SIGNAL_BASE = 257, + EVENT_RESULT_SIGNAL_MAX = EVENT_RESULT_SIGNAL_BASE + _NSIG, + _EVENT_RESULT_MAX, + _EVENT_RESULT_INVALID = -EINVAL, +} EventResult; + +void udev_worker_done(UdevWorker *worker); +int udev_worker_main(UdevWorker *worker, sd_device *dev); + +void udev_broadcast_result(sd_device_monitor *monitor, sd_device *dev, EventResult result); +int udev_get_whole_disk(sd_device *dev, sd_device **ret_device, const char **ret_devname); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 4fd6db8047..8c0ceccfd9 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -72,6 +72,7 @@ #include "udev-trace.h" #include "udev-util.h" #include "udev-watch.h" +#include "udev-worker.h" #include "udevd.h" #include "user-util.h" #include "version.h" @@ -174,20 +175,6 @@ typedef struct Worker { Event *event; } Worker; -/* passed from worker to main process */ -typedef enum EventResult { - EVENT_RESULT_NERRNO_MIN = -ERRNO_MAX, - EVENT_RESULT_NERRNO_MAX = -1, - EVENT_RESULT_SUCCESS = 0, - EVENT_RESULT_EXIT_STATUS_BASE = 0, - EVENT_RESULT_EXIT_STATUS_MAX = 255, - EVENT_RESULT_TRY_AGAIN = 256, /* when the block device is locked by another process. */ - EVENT_RESULT_SIGNAL_BASE = 257, - EVENT_RESULT_SIGNAL_MAX = EVENT_RESULT_SIGNAL_BASE + _NSIG, - _EVENT_RESULT_MAX, - _EVENT_RESULT_INVALID = -EINVAL, -} EventResult; - static Event *event_free(Event *event) { if (!event) return NULL; @@ -423,327 +410,6 @@ static int on_kill_workers_event(sd_event_source *s, uint64_t usec, void *userda return 1; } -static void device_broadcast(sd_device_monitor *monitor, sd_device *dev, EventResult result) { - int r; - - assert(dev); - - /* On exit, manager->monitor is already NULL. */ - if (!monitor) - return; - - if (result != EVENT_RESULT_SUCCESS) { - (void) device_add_property(dev, "UDEV_WORKER_FAILED", "1"); - - switch (result) { - case EVENT_RESULT_NERRNO_MIN ... EVENT_RESULT_NERRNO_MAX: { - const char *str; - - (void) device_add_propertyf(dev, "UDEV_WORKER_ERRNO", "%i", -result); - - str = errno_to_name(result); - if (str) - (void) device_add_property(dev, "UDEV_WORKER_ERRNO_NAME", str); - break; - } - case EVENT_RESULT_EXIT_STATUS_BASE ... EVENT_RESULT_EXIT_STATUS_MAX: - (void) device_add_propertyf(dev, "UDEV_WORKER_EXIT_STATUS", "%i", result - EVENT_RESULT_EXIT_STATUS_BASE); - break; - - case EVENT_RESULT_TRY_AGAIN: - assert_not_reached(); - break; - - case EVENT_RESULT_SIGNAL_BASE ... EVENT_RESULT_SIGNAL_MAX: { - const char *str; - - (void) device_add_propertyf(dev, "UDEV_WORKER_SIGNAL", "%i", result - EVENT_RESULT_SIGNAL_BASE); - - str = signal_to_string(result - EVENT_RESULT_SIGNAL_BASE); - if (str) - (void) device_add_property(dev, "UDEV_WORKER_SIGNAL_NAME", str); - break; - } - default: - log_device_warning(dev, "Unknown event result \"%i\", ignoring.", result); - } - } - - r = device_monitor_send_device(monitor, NULL, dev); - if (r < 0) - log_device_warning_errno(dev, r, - "Failed to broadcast event to libudev listeners, ignoring: %m"); -} - -static int worker_send_result(Manager *manager, EventResult result) { - assert(manager); - assert(manager->worker_watch[WRITE_END] >= 0); - - return loop_write(manager->worker_watch[WRITE_END], &result, sizeof(result), false); -} - -static int device_get_whole_disk(sd_device *dev, sd_device **ret_device, const char **ret_devname) { - const char *val; - int r; - - assert(dev); - - if (device_for_action(dev, SD_DEVICE_REMOVE)) - goto irrelevant; - - r = sd_device_get_sysname(dev, &val); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get sysname: %m"); - - /* Exclude the following devices: - * For "dm-", see the comment added by e918a1b5a94f270186dca59156354acd2a596494. - * For "md", see the commit message of 2e5b17d01347d3c3118be2b8ad63d20415dbb1f0, - * but not sure the assumption is still valid even when partitions are created on the md - * devices, surprisingly which seems to be possible, see PR #22973. - * For "drbd", see the commit message of fee854ee8ccde0cd28e0f925dea18cce35f3993d. */ - if (STARTSWITH_SET(val, "dm-", "md", "drbd")) - goto irrelevant; - - r = block_device_get_whole_disk(dev, &dev); - if (IN_SET(r, - -ENOTBLK, /* The device is not a block device. */ - -ENODEV /* The whole disk device was not found, it may already be removed. */)) - goto irrelevant; - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get whole disk device: %m"); - - r = sd_device_get_devname(dev, &val); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get devname: %m"); - - if (ret_device) - *ret_device = dev; - if (ret_devname) - *ret_devname = val; - return 1; - -irrelevant: - if (ret_device) - *ret_device = NULL; - if (ret_devname) - *ret_devname = NULL; - return 0; -} - -static int worker_lock_whole_disk(sd_device *dev, int *ret_fd) { - _cleanup_close_ int fd = -EBADF; - sd_device *dev_whole_disk; - const char *val; - int r; - - assert(dev); - assert(ret_fd); - - /* Take a shared lock on the device node; this establishes a concept of device "ownership" to - * serialize device access. External processes holding an exclusive lock will cause udev to skip the - * event handling; in the case udev acquired the lock, the external process can block until udev has - * finished its event handling. */ - - r = device_get_whole_disk(dev, &dev_whole_disk, &val); - if (r < 0) - return r; - if (r == 0) - goto nolock; - - fd = sd_device_open(dev_whole_disk, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) { - bool ignore = ERRNO_IS_DEVICE_ABSENT(fd); - - log_device_debug_errno(dev, fd, "Failed to open '%s'%s: %m", val, ignore ? ", ignoring" : ""); - if (!ignore) - return fd; - - goto nolock; - } - - if (flock(fd, LOCK_SH|LOCK_NB) < 0) - return log_device_debug_errno(dev, errno, "Failed to flock(%s): %m", val); - - *ret_fd = TAKE_FD(fd); - return 1; - -nolock: - *ret_fd = -EBADF; - return 0; -} - -static int worker_mark_block_device_read_only(sd_device *dev) { - _cleanup_close_ int fd = -EBADF; - const char *val; - int state = 1, r; - - assert(dev); - - if (!arg_blockdev_read_only) - return 0; - - /* Do this only once, when the block device is new. If the device is later retriggered let's not - * toggle the bit again, so that people can boot up with full read-only mode and then unset the bit - * for specific devices only. */ - if (!device_for_action(dev, SD_DEVICE_ADD)) - return 0; - - r = sd_device_get_subsystem(dev, &val); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get subsystem: %m"); - - if (!streq(val, "block")) - return 0; - - r = sd_device_get_sysname(dev, &val); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get sysname: %m"); - - /* Exclude synthetic devices for now, this is supposed to be a safety feature to avoid modification - * of physical devices, and what sits on top of those doesn't really matter if we don't allow the - * underlying block devices to receive changes. */ - if (STARTSWITH_SET(val, "dm-", "md", "drbd", "loop", "nbd", "zram")) - return 0; - - fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return log_device_debug_errno(dev, fd, "Failed to open '%s', ignoring: %m", val); - - if (ioctl(fd, BLKROSET, &state) < 0) - return log_device_warning_errno(dev, errno, "Failed to mark block device '%s' read-only: %m", val); - - log_device_info(dev, "Successfully marked block device '%s' read-only.", val); - return 0; -} - -static int worker_process_device(Manager *manager, sd_device *dev) { - _cleanup_(udev_event_freep) UdevEvent *udev_event = NULL; - _cleanup_close_ int fd_lock = -EBADF; - int r; - - assert(manager); - assert(dev); - - log_device_uevent(dev, "Processing device"); - - udev_event = udev_event_new(dev, arg_exec_delay_usec, manager->rtnl, manager->log_level); - if (!udev_event) - return -ENOMEM; - - /* If this is a block device and the device is locked currently via the BSD advisory locks, - * someone else is using it exclusively. We don't run our udev rules now to not interfere. - * Instead of processing the event, we requeue the event and will try again after a delay. - * - * The user-facing side of this: https://systemd.io/BLOCK_DEVICE_LOCKING */ - r = worker_lock_whole_disk(dev, &fd_lock); - if (r == -EAGAIN) - return EVENT_RESULT_TRY_AGAIN; - if (r < 0) - return r; - - (void) worker_mark_block_device_read_only(dev); - - /* apply rules, create node, symlinks */ - r = udev_event_execute_rules( - udev_event, - manager->inotify_fd, - arg_event_timeout_usec, - arg_timeout_signal, - manager->properties, - manager->rules); - if (r < 0) - return r; - - udev_event_execute_run(udev_event, arg_event_timeout_usec, arg_timeout_signal); - - if (!manager->rtnl) - /* in case rtnl was initialized */ - manager->rtnl = sd_netlink_ref(udev_event->rtnl); - - if (udev_event->inotify_watch) { - r = udev_watch_begin(manager->inotify_fd, dev); - if (r < 0 && r != -ENOENT) /* The device may be already removed, ignore -ENOENT. */ - log_device_warning_errno(dev, r, "Failed to add inotify watch, ignoring: %m"); - } - - log_device_uevent(dev, "Device processed"); - return 0; -} - -static int worker_device_monitor_handler(sd_device_monitor *monitor, sd_device *dev, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - int r; - - assert(dev); - - r = worker_process_device(manager, dev); - if (r == EVENT_RESULT_TRY_AGAIN) - /* if we couldn't acquire the flock(), then requeue the event */ - log_device_debug(dev, "Block device is currently locked, requeueing the event."); - else { - if (r < 0) - log_device_warning_errno(dev, r, "Failed to process device, ignoring: %m"); - - /* send processed event back to libudev listeners */ - device_broadcast(monitor, dev, r); - } - - /* send udevd the result of the event execution */ - r = worker_send_result(manager, r); - if (r < 0) - log_device_warning_errno(dev, r, "Failed to send signal to main daemon, ignoring: %m"); - - /* Reset the log level, as it might be changed by "OPTIONS=log_level=". */ - log_set_max_level(manager->log_level); - - return 1; -} - -static int worker_main(Manager *_manager, sd_device_monitor *monitor, sd_device *first_device) { - _cleanup_(sd_device_unrefp) sd_device *dev = first_device; - _cleanup_(manager_freep) Manager *manager = _manager; - int r; - - assert(manager); - assert(monitor); - assert(dev); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, -1) >= 0); - - /* Reset OOM score, we only protect the main daemon. */ - r = set_oom_score_adjust(0); - if (r < 0) - log_debug_errno(r, "Failed to reset OOM score, ignoring: %m"); - - /* Clear unnecessary data in Manager object. */ - manager_clear_for_worker(manager); - - r = sd_event_new(&manager->event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - r = sd_event_add_signal(manager->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to set SIGTERM event: %m"); - - r = sd_device_monitor_attach_event(monitor, manager->event); - if (r < 0) - return log_error_errno(r, "Failed to attach event loop to device monitor: %m"); - - r = sd_device_monitor_start(monitor, worker_device_monitor_handler, manager); - if (r < 0) - return log_error_errno(r, "Failed to start device monitor: %m"); - - /* Process first device */ - (void) worker_device_monitor_handler(monitor, dev, manager); - - r = sd_event_loop(manager->event); - if (r < 0) - return log_error_errno(r, "Event loop failed: %m"); - - return 0; -} - static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) { Event *event = ASSERT_PTR(userdata); @@ -820,10 +486,21 @@ static int worker_spawn(Manager *manager, Event *event) { return log_error_errno(r, "Failed to fork() worker: %m"); } if (r == 0) { - DEVICE_TRACE_POINT(worker_spawned, event->dev, getpid_cached()); + _cleanup_(udev_worker_done) UdevWorker w = { + .monitor = TAKE_PTR(worker_monitor), + .properties = TAKE_PTR(manager->properties), + .rules = TAKE_PTR(manager->rules), + .pipe_fd = TAKE_FD(manager->worker_watch[WRITE_END]), + .inotify_fd = TAKE_FD(manager->inotify_fd), + .exec_delay_usec = arg_exec_delay_usec, + .timeout_usec = arg_event_timeout_usec, + .timeout_signal = arg_timeout_signal, + .log_level = manager->log_level, + .blockdev_read_only = arg_blockdev_read_only, + }; /* Worker process */ - r = worker_main(manager, worker_monitor, sd_device_ref(event->dev)); + r = udev_worker_main(&w, event->dev); log_close(); _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } @@ -1083,7 +760,7 @@ static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device * device is not locked anymore. The assumption may not be true, but that should not cause any * issues, as in that case events will be requeued soon. */ - r = device_get_whole_disk(dev, NULL, &devname); + r = udev_get_whole_disk(dev, NULL, &devname); if (r <= 0) return r; @@ -1096,7 +773,7 @@ static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device if (event->retry_again_next_usec == 0) continue; - if (device_get_whole_disk(event->dev, NULL, &event_devname) <= 0) + if (udev_get_whole_disk(event->dev, NULL, &event_devname) <= 0) continue; if (!streq(devname, event_devname)) @@ -1249,7 +926,7 @@ static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdat /* worker returned */ if (result == EVENT_RESULT_TRY_AGAIN && event_requeue(worker->event) < 0) - device_broadcast(manager->monitor, worker->event->dev, -ETIMEDOUT); + udev_broadcast_result(manager->monitor, worker->event->dev, -ETIMEDOUT); /* When event_requeue() succeeds, worker->event is NULL, and event_free() handles NULL gracefully. */ event_free(worker->event); @@ -1533,7 +1210,7 @@ static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { device_tag_index(dev, NULL, false); /* Forward kernel event to libudev listeners */ - device_broadcast(manager->monitor, dev, result); + udev_broadcast_result(manager->monitor, dev, result); } worker_free(worker); diff --git a/src/udev/udevd.h b/src/udev/udevd.h index 891196c474..0af0bb8086 100644 --- a/src/udev/udevd.h +++ b/src/udev/udevd.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include + int run_udevd(int argc, char *argv[]); bool devpath_conflict(const char *a, const char *b); From 8ebc7314e83c3d98a519b702c05270870fb61c13 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 03:09:12 +0900 Subject: [PATCH 08/14] udev: merge manager_clear_for_worker() with manager_free() The function manager_clear_for_worker() is now not called by workers, and only called from manager_free(). Let's merge them. This also drops unused Manager.rtnl, and outdated comment in event_free(). --- src/udev/udevd.c | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 8c0ceccfd9..c93dc6905e 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -51,7 +51,6 @@ #include "list.h" #include "main-func.h" #include "mkdir.h" -#include "netlink-util.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" @@ -104,8 +103,6 @@ typedef struct Manager { UdevRules *rules; Hashmap *properties; - sd_netlink *rtnl; - sd_device_monitor *monitor; UdevCtrl *ctrl; int worker_watch[2]; @@ -184,8 +181,6 @@ static Event *event_free(Event *event) { LIST_REMOVE(event, event->manager->events, event); sd_device_unref(event->dev); - /* Do not use sd_event_source_disable_unref() here, as this is called by both workers and the - * main process. */ sd_event_source_unref(event->retry_event_source); sd_event_source_unref(event->timeout_warning_event); sd_event_source_unref(event->timeout_event); @@ -222,43 +217,29 @@ static Worker *worker_free(Worker *worker) { DEFINE_TRIVIAL_CLEANUP_FUNC(Worker*, worker_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(worker_hash_op, void, trivial_hash_func, trivial_compare_func, Worker, worker_free); -static void manager_clear_for_worker(Manager *manager) { - assert(manager); - - /* Do not use sd_event_source_disable_unref() here, as this is called by both workers and the - * main process. */ - manager->inotify_event = sd_event_source_unref(manager->inotify_event); - manager->kill_workers_event = sd_event_source_unref(manager->kill_workers_event); - - manager->event = sd_event_unref(manager->event); - - manager->workers = hashmap_free(manager->workers); - event_queue_cleanup(manager, EVENT_UNDEF); - - manager->monitor = sd_device_monitor_unref(manager->monitor); - manager->ctrl = udev_ctrl_unref(manager->ctrl); - - manager->worker_watch[READ_END] = safe_close(manager->worker_watch[READ_END]); -} - static Manager* manager_free(Manager *manager) { if (!manager) return NULL; udev_builtin_exit(); - manager_clear_for_worker(manager); - - sd_netlink_unref(manager->rtnl); - hashmap_free_free_free(manager->properties); udev_rules_free(manager->rules); + hashmap_free(manager->workers); + event_queue_cleanup(manager, EVENT_UNDEF); + safe_close(manager->inotify_fd); safe_close_pair(manager->worker_watch); + sd_device_monitor_unref(manager->monitor); + udev_ctrl_unref(manager->ctrl); + + sd_event_source_unref(manager->inotify_event); + sd_event_source_unref(manager->kill_workers_event); sd_event_source_unref(manager->memory_pressure_event_source); sd_event_source_unref(manager->sigrtmin18_event_source); + sd_event_unref(manager->event); free(manager->cgroup); return mfree(manager); From 79d2ecaf63ac4b3b9e35f29b070412152633ca24 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 03:16:38 +0900 Subject: [PATCH 09/14] udev: drop mostly unused Manager.pid It is only used by the assertion in event_queue_insert(). We have similar check e.g. in sd-bus or sd-event, but udevd.c is not a library code, hence such check is not necessary. --- src/udev/udevd.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/udev/udevd.c b/src/udev/udevd.c index c93dc6905e..214fc17861 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -97,7 +97,6 @@ typedef struct Manager { Hashmap *workers; LIST_HEAD(Event, events); char *cgroup; - pid_t pid; /* the process that originally allocated the manager object */ int log_level; UdevRules *rules; @@ -776,9 +775,6 @@ static int event_queue_insert(Manager *manager, sd_device *dev) { assert(manager); assert(dev); - /* only one process can add events to the queue */ - assert(manager->pid == getpid_cached()); - /* We only accepts devices received by device monitor. */ r = sd_device_get_seqnum(dev, &seqnum); if (r < 0) @@ -1532,8 +1528,6 @@ static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent) { static int main_loop(Manager *manager) { int fd_worker, r; - manager->pid = getpid_cached(); - /* unnamed socket from workers to the main daemon */ r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, manager->worker_watch); if (r < 0) From 6bd4bad6b943b2ae7e2b0528e090207736422f27 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 05:15:57 +0900 Subject: [PATCH 10/14] udev: move arg_xyz into Manager No functional changes, just preparation for later commits. --- src/udev/udevd.c | 138 +++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 57 deletions(-) diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 214fc17861..85d064cb39 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -82,12 +82,6 @@ static bool arg_debug = false; static int arg_daemonize = false; -static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; -static unsigned arg_children_max = 0; -static usec_t arg_exec_delay_usec = 0; -static usec_t arg_event_timeout_usec = 180 * USEC_PER_SEC; -static int arg_timeout_signal = SIGKILL; -static bool arg_blockdev_read_only = false; typedef struct Event Event; typedef struct Worker Worker; @@ -117,6 +111,13 @@ typedef struct Manager { usec_t last_usec; + ResolveNameTiming resolve_name_timing; + unsigned children_max; + usec_t exec_delay_usec; + usec_t timeout_usec; + int timeout_signal; + bool blockdev_read_only; + bool udev_node_needs_cleanup; bool stop_exec_queue; bool exit; @@ -322,12 +323,14 @@ static void manager_exit(Manager *manager) { manager_kill_workers(manager, true); } -static void notify_ready(void) { +static void notify_ready(Manager *manager) { int r; + assert(manager); + r = sd_notifyf(/* unset= */ false, "READY=1\n" - "STATUS=Processing with %u children at max", arg_children_max); + "STATUS=Processing with %u children at max", manager->children_max); if (r < 0) log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); } @@ -371,14 +374,14 @@ static void manager_reload(Manager *manager, bool force) { udev_builtin_exit(); udev_builtin_init(); - r = udev_rules_load(&rules, arg_resolve_name_timing); + r = udev_rules_load(&rules, manager->resolve_name_timing); if (r < 0) log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); else udev_rules_free_and_replace(manager->rules, rules); } - notify_ready(); + notify_ready(manager); } static int on_kill_workers_event(sd_event_source *s, uint64_t usec, void *userdata) { @@ -393,9 +396,10 @@ static int on_kill_workers_event(sd_event_source *s, uint64_t usec, void *userda static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) { Event *event = ASSERT_PTR(userdata); + assert(event->manager); assert(event->worker); - kill_and_sigcont(event->worker->pid, arg_timeout_signal); + kill_and_sigcont(event->worker->pid, event->manager->timeout_signal); event->worker->state = WORKER_KILLED; log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed", event->worker->pid, event->seqnum); @@ -414,6 +418,7 @@ static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *use } static void worker_attach_event(Worker *worker, Event *event) { + Manager *manager; sd_event *e; assert(worker); @@ -427,14 +432,15 @@ static void worker_attach_event(Worker *worker, Event *event) { event->state = EVENT_RUNNING; event->worker = worker; - e = worker->manager->event; + manager = worker->manager; + e = manager->event; (void) sd_event_add_time_relative(e, &event->timeout_warning_event, CLOCK_MONOTONIC, - udev_warn_timeout(arg_event_timeout_usec), USEC_PER_SEC, + udev_warn_timeout(manager->timeout_usec), USEC_PER_SEC, on_event_timeout_warning, event); (void) sd_event_add_time_relative(e, &event->timeout_event, CLOCK_MONOTONIC, - arg_event_timeout_usec, USEC_PER_SEC, + manager->timeout_usec, USEC_PER_SEC, on_event_timeout, event); } @@ -472,11 +478,11 @@ static int worker_spawn(Manager *manager, Event *event) { .rules = TAKE_PTR(manager->rules), .pipe_fd = TAKE_FD(manager->worker_watch[WRITE_END]), .inotify_fd = TAKE_FD(manager->inotify_fd), - .exec_delay_usec = arg_exec_delay_usec, - .timeout_usec = arg_event_timeout_usec, - .timeout_signal = arg_timeout_signal, + .exec_delay_usec = manager->exec_delay_usec, + .timeout_usec = manager->timeout_usec, + .timeout_signal = manager->timeout_signal, .log_level = manager->log_level, - .blockdev_read_only = arg_blockdev_read_only, + .blockdev_read_only = manager->blockdev_read_only, }; /* Worker process */ @@ -525,10 +531,10 @@ static int event_run(Event *event) { return 1; /* event is now processing. */ } - if (hashmap_size(manager->workers) >= arg_children_max) { + if (hashmap_size(manager->workers) >= manager->children_max) { /* Avoid spamming the debug logs if the limit is already reached and * many events still need to be processed */ - if (log_children_max_reached && arg_children_max > 1) { + if (log_children_max_reached && manager->children_max > 1) { log_debug("Maximum number (%u) of children reached.", hashmap_size(manager->workers)); log_children_max_reached = false; } @@ -1010,9 +1016,9 @@ static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrl } log_debug("Received udev control message (SET_MAX_CHILDREN), setting children_max=%i", value->intval); - arg_children_max = value->intval; + manager->children_max = value->intval; - notify_ready(); + notify_ready(manager); break; case UDEV_CTRL_PING: log_debug("Received udev control message (PING)"); @@ -1286,6 +1292,7 @@ static int listen_fds(int *ret_ctrl, int *ret_netlink) { * udev.blockdev_read_only<=bool> mark all block devices read-only when they appear */ static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + Manager *manager = ASSERT_PTR(data); int r; assert(key); @@ -1305,21 +1312,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (proc_cmdline_value_missing(key, value)) return 0; - r = parse_sec(value, &arg_event_timeout_usec); + r = parse_sec(value, &manager->timeout_usec); } else if (proc_cmdline_key_streq(key, "udev.children_max")) { if (proc_cmdline_value_missing(key, value)) return 0; - r = safe_atou(value, &arg_children_max); + r = safe_atou(value, &manager->children_max); } else if (proc_cmdline_key_streq(key, "udev.exec_delay")) { if (proc_cmdline_value_missing(key, value)) return 0; - r = parse_sec(value, &arg_exec_delay_usec); + r = parse_sec(value, &manager->exec_delay_usec); } else if (proc_cmdline_key_streq(key, "udev.timeout_signal")) { @@ -1328,21 +1335,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat r = signal_from_string(value); if (r > 0) - arg_timeout_signal = r; + manager->timeout_signal = r; } else if (proc_cmdline_key_streq(key, "udev.blockdev_read_only")) { if (!value) - arg_blockdev_read_only = true; + manager->blockdev_read_only = true; else { r = parse_boolean(value); if (r < 0) log_warning_errno(r, "Failed to parse udev.blockdev-read-only argument, ignoring: %s", value); else - arg_blockdev_read_only = r; + manager->blockdev_read_only = r; } - if (arg_blockdev_read_only) + if (manager->blockdev_read_only) log_notice("All physical block devices will be marked read-only."); return 0; @@ -1386,7 +1393,7 @@ static int help(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int parse_argv(int argc, char *argv[], Manager *manager) { enum { ARG_TIMEOUT_SIGNAL, }; @@ -1408,6 +1415,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); + assert(manager); while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { switch (c) { @@ -1416,12 +1424,12 @@ static int parse_argv(int argc, char *argv[]) { arg_daemonize = true; break; case 'c': - r = safe_atou(optarg, &arg_children_max); + r = safe_atou(optarg, &manager->children_max); if (r < 0) log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", optarg); break; case 'e': - r = parse_sec(optarg, &arg_exec_delay_usec); + r = parse_sec(optarg, &manager->exec_delay_usec); if (r < 0) log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", optarg); break; @@ -1430,11 +1438,11 @@ static int parse_argv(int argc, char *argv[]) { if (r <= 0) log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", optarg); else - arg_timeout_signal = r; + manager->timeout_signal = r; break; case 't': - r = parse_sec(optarg, &arg_event_timeout_usec); + r = parse_sec(optarg, &manager->timeout_usec); if (r < 0) log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", optarg); break; @@ -1448,7 +1456,7 @@ static int parse_argv(int argc, char *argv[]) { if (t < 0) log_warning("Invalid --resolve-names= value '%s', ignoring.", optarg); else - arg_resolve_name_timing = t; + manager->resolve_name_timing = t; break; } case 'h': @@ -1467,22 +1475,31 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent) { - _cleanup_(manager_freep) Manager *manager = NULL; - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(ret); +static Manager* manager_new(void) { + Manager *manager; manager = new(Manager, 1); if (!manager) - return log_oom(); + return NULL; *manager = (Manager) { .inotify_fd = -EBADF, .worker_watch = PIPE_EBADF, + .log_level = LOG_INFO, + .resolve_name_timing = RESOLVE_NAME_EARLY, + .timeout_usec = 180 * USEC_PER_SEC, + .timeout_signal = SIGKILL, }; + return manager; +} + +static int manager_init(Manager *manager, int fd_ctrl, int fd_uevent) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(manager); + r = udev_ctrl_new_from_fd(&manager->ctrl, fd_ctrl); if (r < 0) return log_error_errno(r, "Failed to initialize udev control socket: %m"); @@ -1520,12 +1537,10 @@ static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent) { manager->cgroup = TAKE_PTR(cgroup); } - *ret = TAKE_PTR(manager); - return 0; } -static int main_loop(Manager *manager) { +static int manager_main(Manager *manager) { int fd_worker, r; /* unnamed socket from workers to the main daemon */ @@ -1618,7 +1633,7 @@ static int main_loop(Manager *manager) { udev_builtin_init(); - r = udev_rules_load(&manager->rules, arg_resolve_name_timing); + r = udev_rules_load(&manager->rules, manager->resolve_name_timing); if (r < 0) return log_error_errno(r, "Failed to read udev rules: %m"); @@ -1626,7 +1641,7 @@ static int main_loop(Manager *manager) { if (r < 0) log_warning_errno(r, "Failed to apply permissions on static device nodes, ignoring: %m"); - notify_ready(); + notify_ready(manager); r = sd_event_loop(manager->event); if (r < 0) @@ -1643,15 +1658,26 @@ int run_udevd(int argc, char *argv[]) { log_set_target(LOG_TARGET_AUTO); log_open(); - udev_parse_config_full(&arg_children_max, &arg_exec_delay_usec, &arg_event_timeout_usec, &arg_resolve_name_timing, &arg_timeout_signal); + + manager = manager_new(); + if (!manager) + return log_oom(); + + udev_parse_config_full( + &manager->children_max, + &manager->exec_delay_usec, + &manager->timeout_usec, + &manager->resolve_name_timing, + &manager->timeout_signal); + log_parse_environment(); log_open(); /* Done again to update after reading configuration. */ - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, manager); if (r <= 0) return r; - r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + r = proc_cmdline_parse(parse_proc_cmdline_item, manager, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); @@ -1664,7 +1690,7 @@ int run_udevd(int argc, char *argv[]) { if (r < 0) return r; - if (arg_children_max == 0) { + if (manager->children_max == 0) { unsigned long cpu_limit, mem_limit, cpu_count = 1; r = cpus_in_affinity_mask(); @@ -1676,10 +1702,8 @@ int run_udevd(int argc, char *argv[]) { cpu_limit = cpu_count * 2 + 16; mem_limit = MAX(physical_memory() / (128UL*1024*1024), 10U); - arg_children_max = MIN(cpu_limit, mem_limit); - arg_children_max = MIN(WORKER_NUM_MAX, arg_children_max); - - log_debug("Set children_max to %u", arg_children_max); + manager->children_max = MIN3(cpu_limit, mem_limit, WORKER_NUM_MAX); + log_debug("Set children_max to %u", manager->children_max); } /* set umask before creating any file/directory */ @@ -1697,7 +1721,7 @@ int run_udevd(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to listen on fds: %m"); - r = manager_new(&manager, fd_ctrl, fd_uevent); + r = manager_init(manager, fd_ctrl, fd_uevent); if (r < 0) return log_error_errno(r, "Failed to create manager: %m"); @@ -1724,5 +1748,5 @@ int run_udevd(int argc, char *argv[]) { (void) setsid(); } - return main_loop(manager); + return manager_main(manager); } From 81c32b1d418626c301e661965104675b6b939d68 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 05:17:03 +0900 Subject: [PATCH 11/14] udev: split udevd.c into two --- src/udev/meson.build | 3 +- src/udev/test-udevd.c | 2 +- src/udev/udev-manager.c | 1336 +++++++++++++++++++++++++++++++++++++ src/udev/udev-manager.h | 62 ++ src/udev/udevd.c | 1398 +-------------------------------------- src/udev/udevd.h | 4 - 6 files changed, 1403 insertions(+), 1402 deletions(-) create mode 100644 src/udev/udev-manager.c create mode 100644 src/udev/udev-manager.h diff --git a/src/udev/meson.build b/src/udev/meson.build index 5687b70a1e..2cb504d7f6 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -14,6 +14,7 @@ udevadm_sources = files( 'udevadm-verify.c', 'udevadm-wait.c', 'udevadm.c', + 'udevd.c', ) libudevd_core_sources = files( @@ -21,6 +22,7 @@ libudevd_core_sources = files( 'udev-ctrl.c', 'udev-event.c', 'udev-format.c', + 'udev-manager.c', 'udev-node.c', 'udev-rules.c', 'udev-spawn.c', @@ -35,7 +37,6 @@ libudevd_core_sources = files( 'udev-builtin-path_id.c', 'udev-builtin-usb_id.c', 'udev-builtin.c', - 'udevd.c', ) if conf.get('HAVE_KMOD') == 1 diff --git a/src/udev/test-udevd.c b/src/udev/test-udevd.c index 58c88853a7..d444b0bf00 100644 --- a/src/udev/test-udevd.c +++ b/src/udev/test-udevd.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "tests.h" -#include "udevd.h" +#include "udev-manager.h" TEST(devpath_conflict) { assert_se(!devpath_conflict(NULL, NULL)); diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c new file mode 100644 index 0000000000..b97ccab3b0 --- /dev/null +++ b/src/udev/udev-manager.c @@ -0,0 +1,1336 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "blockdev-util.h" +#include "cgroup-util.h" +#include "common-signal.h" +#include "daemon-util.h" +#include "device-monitor-private.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-list.h" +#include "event-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "inotify-util.h" +#include "io-util.h" +#include "list.h" +#include "mkdir.h" +#include "process-util.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "syslog-util.h" +#include "udev-builtin.h" +#include "udev-ctrl.h" +#include "udev-event.h" +#include "udev-manager.h" +#include "udev-node.h" +#include "udev-spawn.h" +#include "udev-trace.h" +#include "udev-util.h" +#include "udev-watch.h" +#include "udev-worker.h" + +#define EVENT_RETRY_INTERVAL_USEC (200 * USEC_PER_MSEC) +#define EVENT_RETRY_TIMEOUT_USEC (3 * USEC_PER_MINUTE) + +typedef enum EventState { + EVENT_UNDEF, + EVENT_QUEUED, + EVENT_RUNNING, +} EventState; + +typedef struct Event { + Manager *manager; + Worker *worker; + EventState state; + + sd_device *dev; + + sd_device_action_t action; + uint64_t seqnum; + uint64_t blocker_seqnum; + const char *id; + const char *devpath; + const char *devpath_old; + const char *devnode; + + /* Used when the device is locked by another program. */ + usec_t retry_again_next_usec; + usec_t retry_again_timeout_usec; + sd_event_source *retry_event_source; + + sd_event_source *timeout_warning_event; + sd_event_source *timeout_event; + + LIST_FIELDS(Event, event); +} Event; + +typedef enum WorkerState { + WORKER_UNDEF, + WORKER_RUNNING, + WORKER_IDLE, + WORKER_KILLED, + WORKER_KILLING, +} WorkerState; + +typedef struct Worker { + Manager *manager; + pid_t pid; + sd_event_source *child_event_source; + sd_device_monitor *monitor; + WorkerState state; + Event *event; +} Worker; + +static Event *event_free(Event *event) { + if (!event) + return NULL; + + assert(event->manager); + + LIST_REMOVE(event, event->manager->events, event); + sd_device_unref(event->dev); + + sd_event_source_unref(event->retry_event_source); + sd_event_source_unref(event->timeout_warning_event); + sd_event_source_unref(event->timeout_event); + + if (event->worker) + event->worker->event = NULL; + + return mfree(event); +} + +static void event_queue_cleanup(Manager *manager, EventState match_state) { + LIST_FOREACH(event, event, manager->events) { + if (match_state != EVENT_UNDEF && match_state != event->state) + continue; + + event_free(event); + } +} + +static Worker *worker_free(Worker *worker) { + if (!worker) + return NULL; + + if (worker->manager) + hashmap_remove(worker->manager->workers, PID_TO_PTR(worker->pid)); + + sd_event_source_unref(worker->child_event_source); + sd_device_monitor_unref(worker->monitor); + event_free(worker->event); + + return mfree(worker); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Worker*, worker_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(worker_hash_op, void, trivial_hash_func, trivial_compare_func, Worker, worker_free); + +Manager* manager_free(Manager *manager) { + if (!manager) + return NULL; + + udev_builtin_exit(); + + hashmap_free_free_free(manager->properties); + udev_rules_free(manager->rules); + + hashmap_free(manager->workers); + event_queue_cleanup(manager, EVENT_UNDEF); + + safe_close(manager->inotify_fd); + safe_close_pair(manager->worker_watch); + + sd_device_monitor_unref(manager->monitor); + udev_ctrl_unref(manager->ctrl); + + sd_event_source_unref(manager->inotify_event); + sd_event_source_unref(manager->kill_workers_event); + sd_event_source_unref(manager->memory_pressure_event_source); + sd_event_source_unref(manager->sigrtmin18_event_source); + sd_event_unref(manager->event); + + free(manager->cgroup); + return mfree(manager); +} + +static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata); + +static int worker_new(Worker **ret, Manager *manager, sd_device_monitor *worker_monitor, pid_t pid) { + _cleanup_(worker_freep) Worker *worker = NULL; + int r; + + assert(ret); + assert(manager); + assert(worker_monitor); + assert(pid > 1); + + /* close monitor, but keep address around */ + device_monitor_disconnect(worker_monitor); + + worker = new(Worker, 1); + if (!worker) + return -ENOMEM; + + *worker = (Worker) { + .monitor = sd_device_monitor_ref(worker_monitor), + .pid = pid, + }; + + r = sd_event_add_child(manager->event, &worker->child_event_source, pid, WEXITED, on_sigchld, worker); + if (r < 0) + return r; + + r = hashmap_ensure_put(&manager->workers, &worker_hash_op, PID_TO_PTR(pid), worker); + if (r < 0) + return r; + + worker->manager = manager; + + *ret = TAKE_PTR(worker); + return 0; +} + +static void manager_kill_workers(Manager *manager, bool force) { + Worker *worker; + + assert(manager); + + HASHMAP_FOREACH(worker, manager->workers) { + if (worker->state == WORKER_KILLED) + continue; + + if (worker->state == WORKER_RUNNING && !force) { + worker->state = WORKER_KILLING; + continue; + } + + worker->state = WORKER_KILLED; + (void) kill(worker->pid, SIGTERM); + } +} + +static void manager_exit(Manager *manager) { + assert(manager); + + manager->exit = true; + + (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); + + /* close sources of new events and discard buffered events */ + manager->ctrl = udev_ctrl_unref(manager->ctrl); + + manager->inotify_event = sd_event_source_disable_unref(manager->inotify_event); + manager->inotify_fd = safe_close(manager->inotify_fd); + + manager->monitor = sd_device_monitor_unref(manager->monitor); + + /* discard queued events and kill workers */ + event_queue_cleanup(manager, EVENT_QUEUED); + manager_kill_workers(manager, true); +} + +static void notify_ready(Manager *manager) { + int r; + + assert(manager); + + r = sd_notifyf(/* unset= */ false, + "READY=1\n" + "STATUS=Processing with %u children at max", manager->children_max); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); +} + +/* reload requested, HUP signal received, rules changed, builtin changed */ +static void manager_reload(Manager *manager, bool force) { + _cleanup_(udev_rules_freep) UdevRules *rules = NULL; + usec_t now_usec; + int r; + + assert(manager); + + assert_se(sd_event_now(manager->event, CLOCK_MONOTONIC, &now_usec) >= 0); + if (!force && now_usec < usec_add(manager->last_usec, 3 * USEC_PER_SEC)) + /* check for changed config, every 3 seconds at most */ + return; + manager->last_usec = now_usec; + + /* Reload SELinux label database, to make the child inherit the up-to-date database. */ + mac_selinux_maybe_reload(); + + /* Nothing changed. It is not necessary to reload. */ + if (!udev_rules_should_reload(manager->rules) && !udev_builtin_should_reload()) { + + if (!force) + return; + + /* If we eat this up, then tell our service manager to just continue */ + (void) sd_notifyf(/* unset= */ false, + "RELOADING=1\n" + "STATUS=Skipping configuration reloading, nothing changed.\n" + "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + } else { + (void) sd_notifyf(/* unset= */ false, + "RELOADING=1\n" + "STATUS=Flushing configuration...\n" + "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + + manager_kill_workers(manager, false); + + udev_builtin_exit(); + udev_builtin_init(); + + r = udev_rules_load(&rules, manager->resolve_name_timing); + if (r < 0) + log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); + else + udev_rules_free_and_replace(manager->rules, rules); + } + + notify_ready(manager); +} + +static int on_kill_workers_event(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + log_debug("Cleanup idle workers"); + manager_kill_workers(manager, false); + + return 1; +} + +static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Event *event = ASSERT_PTR(userdata); + + assert(event->manager); + assert(event->worker); + + kill_and_sigcont(event->worker->pid, event->manager->timeout_signal); + event->worker->state = WORKER_KILLED; + + log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed", event->worker->pid, event->seqnum); + + return 1; +} + +static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { + Event *event = ASSERT_PTR(userdata); + + assert(event->worker); + + log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time", event->worker->pid, event->seqnum); + + return 1; +} + +static void worker_attach_event(Worker *worker, Event *event) { + Manager *manager; + sd_event *e; + + assert(worker); + assert(worker->manager); + assert(event); + assert(!event->worker); + assert(!worker->event); + + worker->state = WORKER_RUNNING; + worker->event = event; + event->state = EVENT_RUNNING; + event->worker = worker; + + manager = worker->manager; + e = manager->event; + + (void) sd_event_add_time_relative(e, &event->timeout_warning_event, CLOCK_MONOTONIC, + udev_warn_timeout(manager->timeout_usec), USEC_PER_SEC, + on_event_timeout_warning, event); + + (void) sd_event_add_time_relative(e, &event->timeout_event, CLOCK_MONOTONIC, + manager->timeout_usec, USEC_PER_SEC, + on_event_timeout, event); +} + +static int worker_spawn(Manager *manager, Event *event) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *worker_monitor = NULL; + Worker *worker; + pid_t pid; + int r; + + /* listen for new events */ + r = device_monitor_new_full(&worker_monitor, MONITOR_GROUP_NONE, -1); + if (r < 0) + return r; + + (void) sd_device_monitor_set_description(worker_monitor, "worker"); + + /* allow the main daemon netlink address to send devices to the worker */ + r = device_monitor_allow_unicast_sender(worker_monitor, manager->monitor); + if (r < 0) + return log_error_errno(r, "Worker: Failed to set unicast sender: %m"); + + r = device_monitor_enable_receiving(worker_monitor); + if (r < 0) + return log_error_errno(r, "Worker: Failed to enable receiving of device: %m"); + + r = safe_fork("(udev-worker)", FORK_DEATHSIG, &pid); + if (r < 0) { + event->state = EVENT_QUEUED; + return log_error_errno(r, "Failed to fork() worker: %m"); + } + if (r == 0) { + _cleanup_(udev_worker_done) UdevWorker w = { + .monitor = TAKE_PTR(worker_monitor), + .properties = TAKE_PTR(manager->properties), + .rules = TAKE_PTR(manager->rules), + .pipe_fd = TAKE_FD(manager->worker_watch[WRITE_END]), + .inotify_fd = TAKE_FD(manager->inotify_fd), + .exec_delay_usec = manager->exec_delay_usec, + .timeout_usec = manager->timeout_usec, + .timeout_signal = manager->timeout_signal, + .log_level = manager->log_level, + .blockdev_read_only = manager->blockdev_read_only, + }; + + /* Worker process */ + r = udev_worker_main(&w, event->dev); + log_close(); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + r = worker_new(&worker, manager, worker_monitor, pid); + if (r < 0) + return log_error_errno(r, "Failed to create worker object: %m"); + + worker_attach_event(worker, event); + + log_device_debug(event->dev, "Worker ["PID_FMT"] is forked for processing SEQNUM=%"PRIu64".", pid, event->seqnum); + return 0; +} + +static int event_run(Event *event) { + static bool log_children_max_reached = true; + Manager *manager; + Worker *worker; + int r; + + assert(event); + assert(event->manager); + + log_device_uevent(event->dev, "Device ready for processing"); + + (void) event_source_disable(event->retry_event_source); + + manager = event->manager; + HASHMAP_FOREACH(worker, manager->workers) { + if (worker->state != WORKER_IDLE) + continue; + + r = device_monitor_send_device(manager->monitor, worker->monitor, event->dev); + if (r < 0) { + log_device_error_errno(event->dev, r, "Worker ["PID_FMT"] did not accept message, killing the worker: %m", + worker->pid); + (void) kill(worker->pid, SIGKILL); + worker->state = WORKER_KILLED; + continue; + } + worker_attach_event(worker, event); + return 1; /* event is now processing. */ + } + + if (hashmap_size(manager->workers) >= manager->children_max) { + /* Avoid spamming the debug logs if the limit is already reached and + * many events still need to be processed */ + if (log_children_max_reached && manager->children_max > 1) { + log_debug("Maximum number (%u) of children reached.", hashmap_size(manager->workers)); + log_children_max_reached = false; + } + return 0; /* no free worker */ + } + + /* Re-enable the debug message for the next batch of events */ + log_children_max_reached = true; + + /* start new worker and pass initial device */ + r = worker_spawn(manager, event); + if (r < 0) + return r; + + return 1; /* event is now processing. */ +} + +bool devpath_conflict(const char *a, const char *b) { + /* This returns true when two paths are equivalent, or one is a child of another. */ + + if (!a || !b) + return false; + + for (; *a != '\0' && *b != '\0'; a++, b++) + if (*a != *b) + return false; + + return *a == '/' || *b == '/' || *a == *b; +} + +static int event_is_blocked(Event *event) { + Event *loop_event = NULL; + int r; + + /* lookup event for identical, parent, child device */ + + assert(event); + assert(event->manager); + assert(event->blocker_seqnum <= event->seqnum); + + if (event->retry_again_next_usec > 0) { + usec_t now_usec; + + r = sd_event_now(event->manager->event, CLOCK_BOOTTIME, &now_usec); + if (r < 0) + return r; + + if (event->retry_again_next_usec > now_usec) + return true; + } + + if (event->blocker_seqnum == event->seqnum) + /* we have checked previously and no blocker found */ + return false; + + LIST_FOREACH(event, e, event->manager->events) { + loop_event = e; + + /* we already found a later event, earlier cannot block us, no need to check again */ + if (loop_event->seqnum < event->blocker_seqnum) + continue; + + /* event we checked earlier still exists, no need to check again */ + if (loop_event->seqnum == event->blocker_seqnum) + return true; + + /* found ourself, no later event can block us */ + if (loop_event->seqnum >= event->seqnum) + goto no_blocker; + + /* found event we have not checked */ + break; + } + + assert(loop_event); + assert(loop_event->seqnum > event->blocker_seqnum && + loop_event->seqnum < event->seqnum); + + /* check if queue contains events we depend on */ + LIST_FOREACH(event, e, loop_event) { + loop_event = e; + + /* found ourself, no later event can block us */ + if (loop_event->seqnum >= event->seqnum) + goto no_blocker; + + if (streq_ptr(loop_event->id, event->id)) + break; + + if (devpath_conflict(event->devpath, loop_event->devpath) || + devpath_conflict(event->devpath, loop_event->devpath_old) || + devpath_conflict(event->devpath_old, loop_event->devpath)) + break; + + if (event->devnode && streq_ptr(event->devnode, loop_event->devnode)) + break; + } + + assert(loop_event); + + log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, + event->seqnum, loop_event->seqnum); + + event->blocker_seqnum = loop_event->seqnum; + return true; + +no_blocker: + event->blocker_seqnum = event->seqnum; + return false; +} + +static int event_queue_start(Manager *manager) { + int r; + + assert(manager); + + if (!manager->events || manager->exit || manager->stop_exec_queue) + return 0; + + /* To make the stack directory /run/udev/links cleaned up later. */ + manager->udev_node_needs_cleanup = true; + + r = event_source_disable(manager->kill_workers_event); + if (r < 0) + log_warning_errno(r, "Failed to disable event source for cleaning up idle workers, ignoring: %m"); + + manager_reload(manager, /* force = */ false); + + LIST_FOREACH(event, event, manager->events) { + if (event->state != EVENT_QUEUED) + continue; + + /* do not start event if parent or child event is still running or queued */ + r = event_is_blocked(event); + if (r > 0) + continue; + if (r < 0) + log_device_warning_errno(event->dev, r, + "Failed to check dependencies for event (SEQNUM=%"PRIu64", ACTION=%s), " + "assuming there is no blocking event, ignoring: %m", + event->seqnum, + strna(device_action_to_string(event->action))); + + r = event_run(event); + if (r <= 0) /* 0 means there are no idle workers. Let's escape from the loop. */ + return r; + } + + return 0; +} + +static int on_event_retry(sd_event_source *s, uint64_t usec, void *userdata) { + /* This does nothing. The on_post() callback will start the event if there exists an idle worker. */ + return 1; +} + +static int event_requeue(Event *event) { + usec_t now_usec; + int r; + + assert(event); + assert(event->manager); + assert(event->manager->event); + + event->timeout_warning_event = sd_event_source_disable_unref(event->timeout_warning_event); + event->timeout_event = sd_event_source_disable_unref(event->timeout_event); + + /* add a short delay to suppress busy loop */ + r = sd_event_now(event->manager->event, CLOCK_BOOTTIME, &now_usec); + if (r < 0) + return log_device_warning_errno(event->dev, r, + "Failed to get current time, " + "skipping event (SEQNUM=%"PRIu64", ACTION=%s): %m", + event->seqnum, strna(device_action_to_string(event->action))); + + if (event->retry_again_timeout_usec > 0 && event->retry_again_timeout_usec <= now_usec) + return log_device_warning_errno(event->dev, SYNTHETIC_ERRNO(ETIMEDOUT), + "The underlying block device is locked by a process more than %s, " + "skipping event (SEQNUM=%"PRIu64", ACTION=%s).", + FORMAT_TIMESPAN(EVENT_RETRY_TIMEOUT_USEC, USEC_PER_MINUTE), + event->seqnum, strna(device_action_to_string(event->action))); + + event->retry_again_next_usec = usec_add(now_usec, EVENT_RETRY_INTERVAL_USEC); + if (event->retry_again_timeout_usec == 0) + event->retry_again_timeout_usec = usec_add(now_usec, EVENT_RETRY_TIMEOUT_USEC); + + r = event_reset_time_relative(event->manager->event, &event->retry_event_source, + CLOCK_MONOTONIC, EVENT_RETRY_INTERVAL_USEC, 0, + on_event_retry, NULL, + 0, "retry-event", true); + if (r < 0) + return log_device_warning_errno(event->dev, r, "Failed to reset timer event source for retrying event, " + "skipping event (SEQNUM=%"PRIu64", ACTION=%s): %m", + event->seqnum, strna(device_action_to_string(event->action))); + + if (event->worker && event->worker->event == event) + event->worker->event = NULL; + event->worker = NULL; + + event->state = EVENT_QUEUED; + return 0; +} + +static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device *dev) { + const char *devname; + int r; + + /* When a new event for a block device is queued or we get an inotify event, assume that the + * device is not locked anymore. The assumption may not be true, but that should not cause any + * issues, as in that case events will be requeued soon. */ + + r = udev_get_whole_disk(dev, NULL, &devname); + if (r <= 0) + return r; + + LIST_FOREACH(event, event, manager->events) { + const char *event_devname; + + if (event->state != EVENT_QUEUED) + continue; + + if (event->retry_again_next_usec == 0) + continue; + + if (udev_get_whole_disk(event->dev, NULL, &event_devname) <= 0) + continue; + + if (!streq(devname, event_devname)) + continue; + + event->retry_again_next_usec = 0; + } + + return 0; +} + +static int event_queue_insert(Manager *manager, sd_device *dev) { + const char *devpath, *devpath_old = NULL, *id = NULL, *devnode = NULL; + sd_device_action_t action; + uint64_t seqnum; + Event *event; + int r; + + assert(manager); + assert(dev); + + /* We only accepts devices received by device monitor. */ + r = sd_device_get_seqnum(dev, &seqnum); + if (r < 0) + return r; + + r = sd_device_get_action(dev, &action); + if (r < 0) + return r; + + r = sd_device_get_devpath(dev, &devpath); + if (r < 0) + return r; + + r = sd_device_get_property_value(dev, "DEVPATH_OLD", &devpath_old); + if (r < 0 && r != -ENOENT) + return r; + + r = device_get_device_id(dev, &id); + if (r < 0 && r != -ENOENT) + return r; + + r = sd_device_get_devname(dev, &devnode); + if (r < 0 && r != -ENOENT) + return r; + + event = new(Event, 1); + if (!event) + return -ENOMEM; + + *event = (Event) { + .manager = manager, + .dev = sd_device_ref(dev), + .seqnum = seqnum, + .action = action, + .id = id, + .devpath = devpath, + .devpath_old = devpath_old, + .devnode = devnode, + .state = EVENT_QUEUED, + }; + + if (!manager->events) { + r = touch("/run/udev/queue"); + if (r < 0) + log_warning_errno(r, "Failed to touch /run/udev/queue, ignoring: %m"); + } + + LIST_APPEND(event, manager->events, event); + + log_device_uevent(dev, "Device is queued"); + + return 0; +} + +static int on_uevent(sd_device_monitor *monitor, sd_device *dev, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + DEVICE_TRACE_POINT(kernel_uevent_received, dev); + + device_ensure_usec_initialized(dev, NULL); + + r = event_queue_insert(manager, dev); + if (r < 0) { + log_device_error_errno(dev, r, "Failed to insert device into event queue: %m"); + return 1; + } + + (void) event_queue_assume_block_device_unlocked(manager, dev); + + return 1; +} + +static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + for (;;) { + EventResult result; + struct iovec iovec = IOVEC_MAKE(&result, sizeof(result)); + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + ssize_t size; + struct ucred *ucred; + Worker *worker; + + size = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT); + if (size == -EINTR) + continue; + if (size == -EAGAIN) + /* nothing more to read */ + break; + if (size < 0) + return log_error_errno(size, "Failed to receive message: %m"); + + cmsg_close_all(&msghdr); + + if (size != sizeof(result)) { + log_warning("Ignoring worker message with invalid size %zi bytes", size); + continue; + } + + ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred); + if (!ucred || ucred->pid <= 0) { + log_warning("Ignoring worker message without valid PID"); + continue; + } + + /* lookup worker who sent the signal */ + worker = hashmap_get(manager->workers, PID_TO_PTR(ucred->pid)); + if (!worker) { + log_debug("Worker ["PID_FMT"] returned, but is no longer tracked", ucred->pid); + continue; + } + + if (worker->state == WORKER_KILLING) { + worker->state = WORKER_KILLED; + (void) kill(worker->pid, SIGTERM); + } else if (worker->state != WORKER_KILLED) + worker->state = WORKER_IDLE; + + /* worker returned */ + if (result == EVENT_RESULT_TRY_AGAIN && + event_requeue(worker->event) < 0) + udev_broadcast_result(manager->monitor, worker->event->dev, -ETIMEDOUT); + + /* When event_requeue() succeeds, worker->event is NULL, and event_free() handles NULL gracefully. */ + event_free(worker->event); + } + + return 1; +} + +/* receive the udevd message from userspace */ +static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrlMessageValue *value, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(value); + + switch (type) { + case UDEV_CTRL_SET_LOG_LEVEL: + if ((value->intval & LOG_PRIMASK) != value->intval) { + log_debug("Received invalid udev control message (SET_LOG_LEVEL, %i), ignoring.", value->intval); + break; + } + + log_debug("Received udev control message (SET_LOG_LEVEL), setting log_level=%i", value->intval); + + r = log_get_max_level(); + if (r == value->intval) + break; + + log_set_max_level(value->intval); + manager->log_level = value->intval; + manager_kill_workers(manager, false); + break; + case UDEV_CTRL_STOP_EXEC_QUEUE: + log_debug("Received udev control message (STOP_EXEC_QUEUE)"); + manager->stop_exec_queue = true; + break; + case UDEV_CTRL_START_EXEC_QUEUE: + log_debug("Received udev control message (START_EXEC_QUEUE)"); + manager->stop_exec_queue = false; + /* It is not necessary to call event_queue_start() here, as it will be called in on_post() if necessary. */ + break; + case UDEV_CTRL_RELOAD: + log_debug("Received udev control message (RELOAD)"); + manager_reload(manager, /* force = */ true); + break; + case UDEV_CTRL_SET_ENV: { + _unused_ _cleanup_free_ char *old_val = NULL; + _cleanup_free_ char *key = NULL, *val = NULL, *old_key = NULL; + const char *eq; + + eq = strchr(value->buf, '='); + if (!eq) { + log_error("Invalid key format '%s'", value->buf); + return 1; + } + + key = strndup(value->buf, eq - value->buf); + if (!key) { + log_oom(); + return 1; + } + + old_val = hashmap_remove2(manager->properties, key, (void **) &old_key); + + r = hashmap_ensure_allocated(&manager->properties, &string_hash_ops); + if (r < 0) { + log_oom(); + return 1; + } + + eq++; + if (isempty(eq)) { + log_debug("Received udev control message (ENV), unsetting '%s'", key); + + r = hashmap_put(manager->properties, key, NULL); + if (r < 0) { + log_oom(); + return 1; + } + } else { + val = strdup(eq); + if (!val) { + log_oom(); + return 1; + } + + log_debug("Received udev control message (ENV), setting '%s=%s'", key, val); + + r = hashmap_put(manager->properties, key, val); + if (r < 0) { + log_oom(); + return 1; + } + } + + key = val = NULL; + manager_kill_workers(manager, false); + break; + } + case UDEV_CTRL_SET_CHILDREN_MAX: + if (value->intval <= 0) { + log_debug("Received invalid udev control message (SET_MAX_CHILDREN, %i), ignoring.", value->intval); + return 0; + } + + log_debug("Received udev control message (SET_MAX_CHILDREN), setting children_max=%i", value->intval); + manager->children_max = value->intval; + + notify_ready(manager); + break; + case UDEV_CTRL_PING: + log_debug("Received udev control message (PING)"); + break; + case UDEV_CTRL_EXIT: + log_debug("Received udev control message (EXIT)"); + manager_exit(manager); + break; + default: + log_debug("Received unknown udev control message, ignoring"); + } + + return 1; +} + +static int synthesize_change_one(sd_device *dev, sd_device *target) { + int r; + + 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(sd_device *dev) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + bool part_table_read; + const char *sysname; + int r, k; + + 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); + + r = blockdev_reread_partition_table(dev); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m"); + part_table_read = r >= 0; + + /* search for partitions */ + r = partition_enumerator_new(dev, &e); + if (r < 0) + return r; + + /* 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) { + k = synthesize_change_one(dev, d); + if (k < 0 && r >= 0) + r = k; + } + + return r; +} + +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(dev); + } + + return 0; +} + +static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + manager_exit(manager); + + return 1; +} + +static int on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + manager_reload(manager, /* force = */ true); + + return 1; +} + +static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { + Worker *worker = ASSERT_PTR(userdata); + Manager *manager = ASSERT_PTR(worker->manager); + sd_device *dev = worker->event ? ASSERT_PTR(worker->event->dev) : NULL; + EventResult result; + + assert(si); + + switch (si->si_code) { + case CLD_EXITED: + if (si->si_status == 0) + log_device_debug(dev, "Worker ["PID_FMT"] exited.", si->si_pid); + else + log_device_warning(dev, "Worker ["PID_FMT"] exited with return code %i.", + si->si_pid, si->si_status); + result = EVENT_RESULT_EXIT_STATUS_BASE + si->si_status; + break; + + case CLD_KILLED: + case CLD_DUMPED: + log_device_warning(dev, "Worker ["PID_FMT"] terminated by signal %i (%s).", + si->si_pid, si->si_status, signal_to_string(si->si_status)); + result = EVENT_RESULT_SIGNAL_BASE + si->si_status; + break; + + default: + assert_not_reached(); + } + + if (result != EVENT_RESULT_SUCCESS && dev) { + /* delete state from disk */ + device_delete_db(dev); + device_tag_index(dev, NULL, false); + + /* Forward kernel event to libudev listeners */ + udev_broadcast_result(manager->monitor, dev, result); + } + + worker_free(worker); + + return 1; +} + +static int on_post(sd_event_source *s, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + if (manager->events) { + /* Try to process pending events if idle workers exist. Why is this necessary? + * When a worker finished an event and became idle, even if there was a pending event, + * the corresponding device might have been locked and the processing of the event + * delayed for a while, preventing the worker from processing the event immediately. + * Now, the device may be unlocked. Let's try again! */ + event_queue_start(manager); + return 1; + } + + /* There are no queued events. Let's remove /run/udev/queue and clean up the idle processes. */ + + if (unlink("/run/udev/queue") < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to unlink /run/udev/queue, ignoring: %m"); + } else + log_debug("No events are queued, removing /run/udev/queue."); + + if (!hashmap_isempty(manager->workers)) { + /* There are idle workers */ + (void) event_reset_time_relative(manager->event, &manager->kill_workers_event, + CLOCK_MONOTONIC, 3 * USEC_PER_SEC, USEC_PER_SEC, + on_kill_workers_event, manager, + 0, "kill-workers-event", false); + return 1; + } + + /* There are no idle workers. */ + + if (manager->udev_node_needs_cleanup) { + (void) udev_node_cleanup(); + manager->udev_node_needs_cleanup = false; + } + + if (manager->exit) + return sd_event_exit(manager->event, 0); + + if (manager->cgroup) + /* cleanup possible left-over processes in our cgroup */ + (void) cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, CGROUP_IGNORE_SELF, NULL, NULL, NULL); + + return 1; +} + +Manager* manager_new(void) { + Manager *manager; + + manager = new(Manager, 1); + if (!manager) + return NULL; + + *manager = (Manager) { + .inotify_fd = -EBADF, + .worker_watch = PIPE_EBADF, + .log_level = LOG_INFO, + .resolve_name_timing = RESOLVE_NAME_EARLY, + .timeout_usec = 180 * USEC_PER_SEC, + .timeout_signal = SIGKILL, + }; + + return manager; +} + +int manager_init(Manager *manager, int fd_ctrl, int fd_uevent) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(manager); + + r = udev_ctrl_new_from_fd(&manager->ctrl, fd_ctrl); + if (r < 0) + return log_error_errno(r, "Failed to initialize udev control socket: %m"); + + r = udev_ctrl_enable_receiving(manager->ctrl); + if (r < 0) + return log_error_errno(r, "Failed to bind udev control socket: %m"); + + r = device_monitor_new_full(&manager->monitor, MONITOR_GROUP_KERNEL, fd_uevent); + if (r < 0) + return log_error_errno(r, "Failed to initialize device monitor: %m"); + + /* Bump receiver buffer, but only if we are not called via socket activation, as in that + * case systemd sets the receive buffer size for us, and the value in the .socket unit + * should take full effect. */ + if (fd_uevent < 0) { + r = sd_device_monitor_set_receive_buffer_size(manager->monitor, 128 * 1024 * 1024); + if (r < 0) + log_warning_errno(r, "Failed to set receive buffer size for device monitor, ignoring: %m"); + } + + (void) sd_device_monitor_set_description(manager->monitor, "manager"); + + r = device_monitor_enable_receiving(manager->monitor); + if (r < 0) + return log_error_errno(r, "Failed to bind netlink socket: %m"); + + manager->log_level = log_get_max_level(); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) + log_debug_errno(r, "Failed to get cgroup, ignoring: %m"); + else if (endswith(cgroup, "/udev")) { /* If we are in a subcgroup /udev/ we assume it was delegated to us */ + log_debug("Running in delegated subcgroup '%s'.", cgroup); + manager->cgroup = TAKE_PTR(cgroup); + } + + return 0; +} + +int manager_main(Manager *manager) { + int fd_worker, r; + + /* unnamed socket from workers to the main daemon */ + r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, manager->worker_watch); + if (r < 0) + return log_error_errno(errno, "Failed to create socketpair for communicating with workers: %m"); + + fd_worker = manager->worker_watch[READ_END]; + + r = setsockopt_int(fd_worker, SOL_SOCKET, SO_PASSCRED, true); + if (r < 0) + return log_error_errno(r, "Failed to enable SO_PASSCRED: %m"); + + manager->inotify_fd = inotify_init1(IN_CLOEXEC); + if (manager->inotify_fd < 0) + return log_error_errno(errno, "Failed to create inotify descriptor: %m"); + + udev_watch_restore(manager->inotify_fd); + + /* block and listen to all signals on signalfd */ + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, SIGRTMIN+18, -1) >= 0); + + r = sd_event_default(&manager->event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGINT, on_sigterm, manager); + if (r < 0) + return log_error_errno(r, "Failed to create SIGINT event source: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGTERM, on_sigterm, manager); + if (r < 0) + return log_error_errno(r, "Failed to create SIGTERM event source: %m"); + + r = sd_event_add_signal(manager->event, NULL, SIGHUP, on_sighup, manager); + if (r < 0) + return log_error_errno(r, "Failed to create SIGHUP event source: %m"); + + r = sd_event_set_watchdog(manager->event, true); + if (r < 0) + return log_error_errno(r, "Failed to create watchdog event source: %m"); + + r = udev_ctrl_attach_event(manager->ctrl, manager->event); + if (r < 0) + return log_error_errno(r, "Failed to attach event to udev control: %m"); + + r = udev_ctrl_start(manager->ctrl, on_ctrl_msg, manager); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + /* This needs to be after the inotify and uevent handling, to make sure + * that the ping is send back after fully processing the pending uevents + * (including the synthetic ones we may create due to inotify events). + */ + r = sd_event_source_set_priority(udev_ctrl_get_event_source(manager->ctrl), SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return log_error_errno(r, "Failed to set IDLE event priority for udev control event source: %m"); + + r = sd_event_add_io(manager->event, &manager->inotify_event, manager->inotify_fd, EPOLLIN, on_inotify, manager); + if (r < 0) + return log_error_errno(r, "Failed to create inotify event source: %m"); + + r = sd_device_monitor_attach_event(manager->monitor, manager->event); + if (r < 0) + return log_error_errno(r, "Failed to attach event to device monitor: %m"); + + r = sd_device_monitor_start(manager->monitor, on_uevent, manager); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + r = sd_event_add_io(manager->event, NULL, fd_worker, EPOLLIN, on_worker, manager); + if (r < 0) + return log_error_errno(r, "Failed to create worker event source: %m"); + + r = sd_event_add_post(manager->event, NULL, on_post, manager); + if (r < 0) + return log_error_errno(r, "Failed to create post event source: %m"); + + /* Eventually, we probably want to do more here on memory pressure, for example, kill idle workers immediately */ + r = sd_event_add_memory_pressure(manager->event, &manager->memory_pressure_event_source, NULL, NULL); + if (r < 0) + log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || (r == -EHOSTDOWN) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to allocate memory pressure watch, ignoring: %m"); + + r = sd_event_add_signal(manager->event, &manager->memory_pressure_event_source, SIGRTMIN+18, sigrtmin18_handler, NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate SIGRTMIN+18 event source, ignoring: %m"); + + manager->last_usec = now(CLOCK_MONOTONIC); + + udev_builtin_init(); + + r = udev_rules_load(&manager->rules, manager->resolve_name_timing); + if (r < 0) + return log_error_errno(r, "Failed to read udev rules: %m"); + + r = udev_rules_apply_static_dev_perms(manager->rules); + if (r < 0) + log_warning_errno(r, "Failed to apply permissions on static device nodes, ignoring: %m"); + + notify_ready(manager); + + r = sd_event_loop(manager->event); + if (r < 0) + log_error_errno(r, "Event loop failed: %m"); + + (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); + return r; +} diff --git a/src/udev/udev-manager.h b/src/udev/udev-manager.h new file mode 100644 index 0000000000..afbc67f696 --- /dev/null +++ b/src/udev/udev-manager.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include + +#include "sd-device.h" +#include "sd-event.h" + +#include "hashmap.h" +#include "macro.h" +#include "time-util.h" +#include "udev-ctrl.h" +#include "udev-rules.h" + +typedef struct Event Event; +typedef struct Worker Worker; + +typedef struct Manager { + sd_event *event; + Hashmap *workers; + LIST_HEAD(Event, events); + char *cgroup; + int log_level; + + UdevRules *rules; + Hashmap *properties; + + sd_device_monitor *monitor; + UdevCtrl *ctrl; + int worker_watch[2]; + + /* used by udev-watch */ + int inotify_fd; + sd_event_source *inotify_event; + + sd_event_source *kill_workers_event; + + sd_event_source *memory_pressure_event_source; + sd_event_source *sigrtmin18_event_source; + + usec_t last_usec; + + ResolveNameTiming resolve_name_timing; + unsigned children_max; + usec_t exec_delay_usec; + usec_t timeout_usec; + int timeout_signal; + bool blockdev_read_only; + + bool udev_node_needs_cleanup; + bool stop_exec_queue; + bool exit; +} Manager; + +Manager* manager_new(void); +Manager* manager_free(Manager *manager); +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +int manager_init(Manager *manager, int fd_ctrl, int fd_uevent); +int manager_main(Manager *manager); + +bool devpath_conflict(const char *a, const char *b); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 85d064cb39..588f8b3b56 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -5,1249 +5,31 @@ * Copyright © 2009 Scott James Remnant */ -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "sd-daemon.h" -#include "sd-event.h" -#include "alloc-util.h" -#include "blockdev-util.h" -#include "cgroup-setup.h" -#include "cgroup-util.h" -#include "common-signal.h" #include "cpu-set-util.h" -#include "daemon-util.h" -#include "dev-setup.h" -#include "device-monitor-private.h" -#include "device-private.h" -#include "device-util.h" -#include "errno-list.h" -#include "event-util.h" +#include "errno-util.h" #include "fd-util.h" -#include "fileio.h" -#include "format-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "inotify-util.h" -#include "io-util.h" #include "limits-util.h" -#include "list.h" -#include "main-func.h" #include "mkdir.h" #include "parse-util.h" -#include "path-util.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "selinux-util.h" #include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" #include "syslog-util.h" -#include "udev-builtin.h" -#include "udev-ctrl.h" -#include "udev-event.h" -#include "udev-node.h" -#include "udev-spawn.h" -#include "udev-trace.h" -#include "udev-util.h" -#include "udev-watch.h" -#include "udev-worker.h" +#include "udev-manager.h" #include "udevd.h" -#include "user-util.h" #include "version.h" #define WORKER_NUM_MAX 2048U -#define EVENT_RETRY_INTERVAL_USEC (200 * USEC_PER_MSEC) -#define EVENT_RETRY_TIMEOUT_USEC (3 * USEC_PER_MINUTE) static bool arg_debug = false; static int arg_daemonize = false; -typedef struct Event Event; -typedef struct Worker Worker; - -typedef struct Manager { - sd_event *event; - Hashmap *workers; - LIST_HEAD(Event, events); - char *cgroup; - int log_level; - - UdevRules *rules; - Hashmap *properties; - - sd_device_monitor *monitor; - UdevCtrl *ctrl; - int worker_watch[2]; - - /* used by udev-watch */ - int inotify_fd; - sd_event_source *inotify_event; - - sd_event_source *kill_workers_event; - - sd_event_source *memory_pressure_event_source; - sd_event_source *sigrtmin18_event_source; - - usec_t last_usec; - - ResolveNameTiming resolve_name_timing; - unsigned children_max; - usec_t exec_delay_usec; - usec_t timeout_usec; - int timeout_signal; - bool blockdev_read_only; - - bool udev_node_needs_cleanup; - bool stop_exec_queue; - bool exit; -} Manager; - -typedef enum EventState { - EVENT_UNDEF, - EVENT_QUEUED, - EVENT_RUNNING, -} EventState; - -typedef struct Event { - Manager *manager; - Worker *worker; - EventState state; - - sd_device *dev; - - sd_device_action_t action; - uint64_t seqnum; - uint64_t blocker_seqnum; - const char *id; - const char *devpath; - const char *devpath_old; - const char *devnode; - - /* Used when the device is locked by another program. */ - usec_t retry_again_next_usec; - usec_t retry_again_timeout_usec; - sd_event_source *retry_event_source; - - sd_event_source *timeout_warning_event; - sd_event_source *timeout_event; - - LIST_FIELDS(Event, event); -} Event; - -typedef enum WorkerState { - WORKER_UNDEF, - WORKER_RUNNING, - WORKER_IDLE, - WORKER_KILLED, - WORKER_KILLING, -} WorkerState; - -typedef struct Worker { - Manager *manager; - pid_t pid; - sd_event_source *child_event_source; - sd_device_monitor *monitor; - WorkerState state; - Event *event; -} Worker; - -static Event *event_free(Event *event) { - if (!event) - return NULL; - - assert(event->manager); - - LIST_REMOVE(event, event->manager->events, event); - sd_device_unref(event->dev); - - sd_event_source_unref(event->retry_event_source); - sd_event_source_unref(event->timeout_warning_event); - sd_event_source_unref(event->timeout_event); - - if (event->worker) - event->worker->event = NULL; - - return mfree(event); -} - -static void event_queue_cleanup(Manager *manager, EventState match_state) { - LIST_FOREACH(event, event, manager->events) { - if (match_state != EVENT_UNDEF && match_state != event->state) - continue; - - event_free(event); - } -} - -static Worker *worker_free(Worker *worker) { - if (!worker) - return NULL; - - if (worker->manager) - hashmap_remove(worker->manager->workers, PID_TO_PTR(worker->pid)); - - sd_event_source_unref(worker->child_event_source); - sd_device_monitor_unref(worker->monitor); - event_free(worker->event); - - return mfree(worker); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Worker*, worker_free); -DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(worker_hash_op, void, trivial_hash_func, trivial_compare_func, Worker, worker_free); - -static Manager* manager_free(Manager *manager) { - if (!manager) - return NULL; - - udev_builtin_exit(); - - hashmap_free_free_free(manager->properties); - udev_rules_free(manager->rules); - - hashmap_free(manager->workers); - event_queue_cleanup(manager, EVENT_UNDEF); - - safe_close(manager->inotify_fd); - safe_close_pair(manager->worker_watch); - - sd_device_monitor_unref(manager->monitor); - udev_ctrl_unref(manager->ctrl); - - sd_event_source_unref(manager->inotify_event); - sd_event_source_unref(manager->kill_workers_event); - sd_event_source_unref(manager->memory_pressure_event_source); - sd_event_source_unref(manager->sigrtmin18_event_source); - sd_event_unref(manager->event); - - free(manager->cgroup); - return mfree(manager); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata); - -static int worker_new(Worker **ret, Manager *manager, sd_device_monitor *worker_monitor, pid_t pid) { - _cleanup_(worker_freep) Worker *worker = NULL; - int r; - - assert(ret); - assert(manager); - assert(worker_monitor); - assert(pid > 1); - - /* close monitor, but keep address around */ - device_monitor_disconnect(worker_monitor); - - worker = new(Worker, 1); - if (!worker) - return -ENOMEM; - - *worker = (Worker) { - .monitor = sd_device_monitor_ref(worker_monitor), - .pid = pid, - }; - - r = sd_event_add_child(manager->event, &worker->child_event_source, pid, WEXITED, on_sigchld, worker); - if (r < 0) - return r; - - r = hashmap_ensure_put(&manager->workers, &worker_hash_op, PID_TO_PTR(pid), worker); - if (r < 0) - return r; - - worker->manager = manager; - - *ret = TAKE_PTR(worker); - return 0; -} - -static void manager_kill_workers(Manager *manager, bool force) { - Worker *worker; - - assert(manager); - - HASHMAP_FOREACH(worker, manager->workers) { - if (worker->state == WORKER_KILLED) - continue; - - if (worker->state == WORKER_RUNNING && !force) { - worker->state = WORKER_KILLING; - continue; - } - - worker->state = WORKER_KILLED; - (void) kill(worker->pid, SIGTERM); - } -} - -static void manager_exit(Manager *manager) { - assert(manager); - - manager->exit = true; - - (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); - - /* close sources of new events and discard buffered events */ - manager->ctrl = udev_ctrl_unref(manager->ctrl); - - manager->inotify_event = sd_event_source_disable_unref(manager->inotify_event); - manager->inotify_fd = safe_close(manager->inotify_fd); - - manager->monitor = sd_device_monitor_unref(manager->monitor); - - /* discard queued events and kill workers */ - event_queue_cleanup(manager, EVENT_QUEUED); - manager_kill_workers(manager, true); -} - -static void notify_ready(Manager *manager) { - int r; - - assert(manager); - - r = sd_notifyf(/* unset= */ false, - "READY=1\n" - "STATUS=Processing with %u children at max", manager->children_max); - if (r < 0) - log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); -} - -/* reload requested, HUP signal received, rules changed, builtin changed */ -static void manager_reload(Manager *manager, bool force) { - _cleanup_(udev_rules_freep) UdevRules *rules = NULL; - usec_t now_usec; - int r; - - assert(manager); - - assert_se(sd_event_now(manager->event, CLOCK_MONOTONIC, &now_usec) >= 0); - if (!force && now_usec < usec_add(manager->last_usec, 3 * USEC_PER_SEC)) - /* check for changed config, every 3 seconds at most */ - return; - manager->last_usec = now_usec; - - /* Reload SELinux label database, to make the child inherit the up-to-date database. */ - mac_selinux_maybe_reload(); - - /* Nothing changed. It is not necessary to reload. */ - if (!udev_rules_should_reload(manager->rules) && !udev_builtin_should_reload()) { - - if (!force) - return; - - /* If we eat this up, then tell our service manager to just continue */ - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Skipping configuration reloading, nothing changed.\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); - } else { - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Flushing configuration...\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); - - manager_kill_workers(manager, false); - - udev_builtin_exit(); - udev_builtin_init(); - - r = udev_rules_load(&rules, manager->resolve_name_timing); - if (r < 0) - log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); - else - udev_rules_free_and_replace(manager->rules, rules); - } - - notify_ready(manager); -} - -static int on_kill_workers_event(sd_event_source *s, uint64_t usec, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - log_debug("Cleanup idle workers"); - manager_kill_workers(manager, false); - - return 1; -} - -static int on_event_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - Event *event = ASSERT_PTR(userdata); - - assert(event->manager); - assert(event->worker); - - kill_and_sigcont(event->worker->pid, event->manager->timeout_signal); - event->worker->state = WORKER_KILLED; - - log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed", event->worker->pid, event->seqnum); - - return 1; -} - -static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { - Event *event = ASSERT_PTR(userdata); - - assert(event->worker); - - log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time", event->worker->pid, event->seqnum); - - return 1; -} - -static void worker_attach_event(Worker *worker, Event *event) { - Manager *manager; - sd_event *e; - - assert(worker); - assert(worker->manager); - assert(event); - assert(!event->worker); - assert(!worker->event); - - worker->state = WORKER_RUNNING; - worker->event = event; - event->state = EVENT_RUNNING; - event->worker = worker; - - manager = worker->manager; - e = manager->event; - - (void) sd_event_add_time_relative(e, &event->timeout_warning_event, CLOCK_MONOTONIC, - udev_warn_timeout(manager->timeout_usec), USEC_PER_SEC, - on_event_timeout_warning, event); - - (void) sd_event_add_time_relative(e, &event->timeout_event, CLOCK_MONOTONIC, - manager->timeout_usec, USEC_PER_SEC, - on_event_timeout, event); -} - -static int worker_spawn(Manager *manager, Event *event) { - _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *worker_monitor = NULL; - Worker *worker; - pid_t pid; - int r; - - /* listen for new events */ - r = device_monitor_new_full(&worker_monitor, MONITOR_GROUP_NONE, -1); - if (r < 0) - return r; - - (void) sd_device_monitor_set_description(worker_monitor, "worker"); - - /* allow the main daemon netlink address to send devices to the worker */ - r = device_monitor_allow_unicast_sender(worker_monitor, manager->monitor); - if (r < 0) - return log_error_errno(r, "Worker: Failed to set unicast sender: %m"); - - r = device_monitor_enable_receiving(worker_monitor); - if (r < 0) - return log_error_errno(r, "Worker: Failed to enable receiving of device: %m"); - - r = safe_fork("(udev-worker)", FORK_DEATHSIG, &pid); - if (r < 0) { - event->state = EVENT_QUEUED; - return log_error_errno(r, "Failed to fork() worker: %m"); - } - if (r == 0) { - _cleanup_(udev_worker_done) UdevWorker w = { - .monitor = TAKE_PTR(worker_monitor), - .properties = TAKE_PTR(manager->properties), - .rules = TAKE_PTR(manager->rules), - .pipe_fd = TAKE_FD(manager->worker_watch[WRITE_END]), - .inotify_fd = TAKE_FD(manager->inotify_fd), - .exec_delay_usec = manager->exec_delay_usec, - .timeout_usec = manager->timeout_usec, - .timeout_signal = manager->timeout_signal, - .log_level = manager->log_level, - .blockdev_read_only = manager->blockdev_read_only, - }; - - /* Worker process */ - r = udev_worker_main(&w, event->dev); - log_close(); - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - r = worker_new(&worker, manager, worker_monitor, pid); - if (r < 0) - return log_error_errno(r, "Failed to create worker object: %m"); - - worker_attach_event(worker, event); - - log_device_debug(event->dev, "Worker ["PID_FMT"] is forked for processing SEQNUM=%"PRIu64".", pid, event->seqnum); - return 0; -} - -static int event_run(Event *event) { - static bool log_children_max_reached = true; - Manager *manager; - Worker *worker; - int r; - - assert(event); - assert(event->manager); - - log_device_uevent(event->dev, "Device ready for processing"); - - (void) event_source_disable(event->retry_event_source); - - manager = event->manager; - HASHMAP_FOREACH(worker, manager->workers) { - if (worker->state != WORKER_IDLE) - continue; - - r = device_monitor_send_device(manager->monitor, worker->monitor, event->dev); - if (r < 0) { - log_device_error_errno(event->dev, r, "Worker ["PID_FMT"] did not accept message, killing the worker: %m", - worker->pid); - (void) kill(worker->pid, SIGKILL); - worker->state = WORKER_KILLED; - continue; - } - worker_attach_event(worker, event); - return 1; /* event is now processing. */ - } - - if (hashmap_size(manager->workers) >= manager->children_max) { - /* Avoid spamming the debug logs if the limit is already reached and - * many events still need to be processed */ - if (log_children_max_reached && manager->children_max > 1) { - log_debug("Maximum number (%u) of children reached.", hashmap_size(manager->workers)); - log_children_max_reached = false; - } - return 0; /* no free worker */ - } - - /* Re-enable the debug message for the next batch of events */ - log_children_max_reached = true; - - /* start new worker and pass initial device */ - r = worker_spawn(manager, event); - if (r < 0) - return r; - - return 1; /* event is now processing. */ -} - -bool devpath_conflict(const char *a, const char *b) { - /* This returns true when two paths are equivalent, or one is a child of another. */ - - if (!a || !b) - return false; - - for (; *a != '\0' && *b != '\0'; a++, b++) - if (*a != *b) - return false; - - return *a == '/' || *b == '/' || *a == *b; -} - -static int event_is_blocked(Event *event) { - Event *loop_event = NULL; - int r; - - /* lookup event for identical, parent, child device */ - - assert(event); - assert(event->manager); - assert(event->blocker_seqnum <= event->seqnum); - - if (event->retry_again_next_usec > 0) { - usec_t now_usec; - - r = sd_event_now(event->manager->event, CLOCK_BOOTTIME, &now_usec); - if (r < 0) - return r; - - if (event->retry_again_next_usec > now_usec) - return true; - } - - if (event->blocker_seqnum == event->seqnum) - /* we have checked previously and no blocker found */ - return false; - - LIST_FOREACH(event, e, event->manager->events) { - loop_event = e; - - /* we already found a later event, earlier cannot block us, no need to check again */ - if (loop_event->seqnum < event->blocker_seqnum) - continue; - - /* event we checked earlier still exists, no need to check again */ - if (loop_event->seqnum == event->blocker_seqnum) - return true; - - /* found ourself, no later event can block us */ - if (loop_event->seqnum >= event->seqnum) - goto no_blocker; - - /* found event we have not checked */ - break; - } - - assert(loop_event); - assert(loop_event->seqnum > event->blocker_seqnum && - loop_event->seqnum < event->seqnum); - - /* check if queue contains events we depend on */ - LIST_FOREACH(event, e, loop_event) { - loop_event = e; - - /* found ourself, no later event can block us */ - if (loop_event->seqnum >= event->seqnum) - goto no_blocker; - - if (streq_ptr(loop_event->id, event->id)) - break; - - if (devpath_conflict(event->devpath, loop_event->devpath) || - devpath_conflict(event->devpath, loop_event->devpath_old) || - devpath_conflict(event->devpath_old, loop_event->devpath)) - break; - - if (event->devnode && streq_ptr(event->devnode, loop_event->devnode)) - break; - } - - assert(loop_event); - - log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, - event->seqnum, loop_event->seqnum); - - event->blocker_seqnum = loop_event->seqnum; - return true; - -no_blocker: - event->blocker_seqnum = event->seqnum; - return false; -} - -static int event_queue_start(Manager *manager) { - int r; - - assert(manager); - - if (!manager->events || manager->exit || manager->stop_exec_queue) - return 0; - - /* To make the stack directory /run/udev/links cleaned up later. */ - manager->udev_node_needs_cleanup = true; - - r = event_source_disable(manager->kill_workers_event); - if (r < 0) - log_warning_errno(r, "Failed to disable event source for cleaning up idle workers, ignoring: %m"); - - manager_reload(manager, /* force = */ false); - - LIST_FOREACH(event, event, manager->events) { - if (event->state != EVENT_QUEUED) - continue; - - /* do not start event if parent or child event is still running or queued */ - r = event_is_blocked(event); - if (r > 0) - continue; - if (r < 0) - log_device_warning_errno(event->dev, r, - "Failed to check dependencies for event (SEQNUM=%"PRIu64", ACTION=%s), " - "assuming there is no blocking event, ignoring: %m", - event->seqnum, - strna(device_action_to_string(event->action))); - - r = event_run(event); - if (r <= 0) /* 0 means there are no idle workers. Let's escape from the loop. */ - return r; - } - - return 0; -} - -static int on_event_retry(sd_event_source *s, uint64_t usec, void *userdata) { - /* This does nothing. The on_post() callback will start the event if there exists an idle worker. */ - return 1; -} - -static int event_requeue(Event *event) { - usec_t now_usec; - int r; - - assert(event); - assert(event->manager); - assert(event->manager->event); - - event->timeout_warning_event = sd_event_source_disable_unref(event->timeout_warning_event); - event->timeout_event = sd_event_source_disable_unref(event->timeout_event); - - /* add a short delay to suppress busy loop */ - r = sd_event_now(event->manager->event, CLOCK_BOOTTIME, &now_usec); - if (r < 0) - return log_device_warning_errno(event->dev, r, - "Failed to get current time, " - "skipping event (SEQNUM=%"PRIu64", ACTION=%s): %m", - event->seqnum, strna(device_action_to_string(event->action))); - - if (event->retry_again_timeout_usec > 0 && event->retry_again_timeout_usec <= now_usec) - return log_device_warning_errno(event->dev, SYNTHETIC_ERRNO(ETIMEDOUT), - "The underlying block device is locked by a process more than %s, " - "skipping event (SEQNUM=%"PRIu64", ACTION=%s).", - FORMAT_TIMESPAN(EVENT_RETRY_TIMEOUT_USEC, USEC_PER_MINUTE), - event->seqnum, strna(device_action_to_string(event->action))); - - event->retry_again_next_usec = usec_add(now_usec, EVENT_RETRY_INTERVAL_USEC); - if (event->retry_again_timeout_usec == 0) - event->retry_again_timeout_usec = usec_add(now_usec, EVENT_RETRY_TIMEOUT_USEC); - - r = event_reset_time_relative(event->manager->event, &event->retry_event_source, - CLOCK_MONOTONIC, EVENT_RETRY_INTERVAL_USEC, 0, - on_event_retry, NULL, - 0, "retry-event", true); - if (r < 0) - return log_device_warning_errno(event->dev, r, "Failed to reset timer event source for retrying event, " - "skipping event (SEQNUM=%"PRIu64", ACTION=%s): %m", - event->seqnum, strna(device_action_to_string(event->action))); - - if (event->worker && event->worker->event == event) - event->worker->event = NULL; - event->worker = NULL; - - event->state = EVENT_QUEUED; - return 0; -} - -static int event_queue_assume_block_device_unlocked(Manager *manager, sd_device *dev) { - const char *devname; - int r; - - /* When a new event for a block device is queued or we get an inotify event, assume that the - * device is not locked anymore. The assumption may not be true, but that should not cause any - * issues, as in that case events will be requeued soon. */ - - r = udev_get_whole_disk(dev, NULL, &devname); - if (r <= 0) - return r; - - LIST_FOREACH(event, event, manager->events) { - const char *event_devname; - - if (event->state != EVENT_QUEUED) - continue; - - if (event->retry_again_next_usec == 0) - continue; - - if (udev_get_whole_disk(event->dev, NULL, &event_devname) <= 0) - continue; - - if (!streq(devname, event_devname)) - continue; - - event->retry_again_next_usec = 0; - } - - return 0; -} - -static int event_queue_insert(Manager *manager, sd_device *dev) { - const char *devpath, *devpath_old = NULL, *id = NULL, *devnode = NULL; - sd_device_action_t action; - uint64_t seqnum; - Event *event; - int r; - - assert(manager); - assert(dev); - - /* We only accepts devices received by device monitor. */ - r = sd_device_get_seqnum(dev, &seqnum); - if (r < 0) - return r; - - r = sd_device_get_action(dev, &action); - if (r < 0) - return r; - - r = sd_device_get_devpath(dev, &devpath); - if (r < 0) - return r; - - r = sd_device_get_property_value(dev, "DEVPATH_OLD", &devpath_old); - if (r < 0 && r != -ENOENT) - return r; - - r = device_get_device_id(dev, &id); - if (r < 0 && r != -ENOENT) - return r; - - r = sd_device_get_devname(dev, &devnode); - if (r < 0 && r != -ENOENT) - return r; - - event = new(Event, 1); - if (!event) - return -ENOMEM; - - *event = (Event) { - .manager = manager, - .dev = sd_device_ref(dev), - .seqnum = seqnum, - .action = action, - .id = id, - .devpath = devpath, - .devpath_old = devpath_old, - .devnode = devnode, - .state = EVENT_QUEUED, - }; - - if (!manager->events) { - r = touch("/run/udev/queue"); - if (r < 0) - log_warning_errno(r, "Failed to touch /run/udev/queue, ignoring: %m"); - } - - LIST_APPEND(event, manager->events, event); - - log_device_uevent(dev, "Device is queued"); - - return 0; -} - -static int on_uevent(sd_device_monitor *monitor, sd_device *dev, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - int r; - - DEVICE_TRACE_POINT(kernel_uevent_received, dev); - - device_ensure_usec_initialized(dev, NULL); - - r = event_queue_insert(manager, dev); - if (r < 0) { - log_device_error_errno(dev, r, "Failed to insert device into event queue: %m"); - return 1; - } - - (void) event_queue_assume_block_device_unlocked(manager, dev); - - return 1; -} - -static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - for (;;) { - EventResult result; - struct iovec iovec = IOVEC_MAKE(&result, sizeof(result)); - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control; - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - ssize_t size; - struct ucred *ucred; - Worker *worker; - - size = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT); - if (size == -EINTR) - continue; - if (size == -EAGAIN) - /* nothing more to read */ - break; - if (size < 0) - return log_error_errno(size, "Failed to receive message: %m"); - - cmsg_close_all(&msghdr); - - if (size != sizeof(result)) { - log_warning("Ignoring worker message with invalid size %zi bytes", size); - continue; - } - - ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred); - if (!ucred || ucred->pid <= 0) { - log_warning("Ignoring worker message without valid PID"); - continue; - } - - /* lookup worker who sent the signal */ - worker = hashmap_get(manager->workers, PID_TO_PTR(ucred->pid)); - if (!worker) { - log_debug("Worker ["PID_FMT"] returned, but is no longer tracked", ucred->pid); - continue; - } - - if (worker->state == WORKER_KILLING) { - worker->state = WORKER_KILLED; - (void) kill(worker->pid, SIGTERM); - } else if (worker->state != WORKER_KILLED) - worker->state = WORKER_IDLE; - - /* worker returned */ - if (result == EVENT_RESULT_TRY_AGAIN && - event_requeue(worker->event) < 0) - udev_broadcast_result(manager->monitor, worker->event->dev, -ETIMEDOUT); - - /* When event_requeue() succeeds, worker->event is NULL, and event_free() handles NULL gracefully. */ - event_free(worker->event); - } - - return 1; -} - -/* receive the udevd message from userspace */ -static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrlMessageValue *value, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - int r; - - assert(value); - - switch (type) { - case UDEV_CTRL_SET_LOG_LEVEL: - if ((value->intval & LOG_PRIMASK) != value->intval) { - log_debug("Received invalid udev control message (SET_LOG_LEVEL, %i), ignoring.", value->intval); - break; - } - - log_debug("Received udev control message (SET_LOG_LEVEL), setting log_level=%i", value->intval); - - r = log_get_max_level(); - if (r == value->intval) - break; - - log_set_max_level(value->intval); - manager->log_level = value->intval; - manager_kill_workers(manager, false); - break; - case UDEV_CTRL_STOP_EXEC_QUEUE: - log_debug("Received udev control message (STOP_EXEC_QUEUE)"); - manager->stop_exec_queue = true; - break; - case UDEV_CTRL_START_EXEC_QUEUE: - log_debug("Received udev control message (START_EXEC_QUEUE)"); - manager->stop_exec_queue = false; - /* It is not necessary to call event_queue_start() here, as it will be called in on_post() if necessary. */ - break; - case UDEV_CTRL_RELOAD: - log_debug("Received udev control message (RELOAD)"); - manager_reload(manager, /* force = */ true); - break; - case UDEV_CTRL_SET_ENV: { - _unused_ _cleanup_free_ char *old_val = NULL; - _cleanup_free_ char *key = NULL, *val = NULL, *old_key = NULL; - const char *eq; - - eq = strchr(value->buf, '='); - if (!eq) { - log_error("Invalid key format '%s'", value->buf); - return 1; - } - - key = strndup(value->buf, eq - value->buf); - if (!key) { - log_oom(); - return 1; - } - - old_val = hashmap_remove2(manager->properties, key, (void **) &old_key); - - r = hashmap_ensure_allocated(&manager->properties, &string_hash_ops); - if (r < 0) { - log_oom(); - return 1; - } - - eq++; - if (isempty(eq)) { - log_debug("Received udev control message (ENV), unsetting '%s'", key); - - r = hashmap_put(manager->properties, key, NULL); - if (r < 0) { - log_oom(); - return 1; - } - } else { - val = strdup(eq); - if (!val) { - log_oom(); - return 1; - } - - log_debug("Received udev control message (ENV), setting '%s=%s'", key, val); - - r = hashmap_put(manager->properties, key, val); - if (r < 0) { - log_oom(); - return 1; - } - } - - key = val = NULL; - manager_kill_workers(manager, false); - break; - } - case UDEV_CTRL_SET_CHILDREN_MAX: - if (value->intval <= 0) { - log_debug("Received invalid udev control message (SET_MAX_CHILDREN, %i), ignoring.", value->intval); - return 0; - } - - log_debug("Received udev control message (SET_MAX_CHILDREN), setting children_max=%i", value->intval); - manager->children_max = value->intval; - - notify_ready(manager); - break; - case UDEV_CTRL_PING: - log_debug("Received udev control message (PING)"); - break; - case UDEV_CTRL_EXIT: - log_debug("Received udev control message (EXIT)"); - manager_exit(manager); - break; - default: - log_debug("Received unknown udev control message, ignoring"); - } - - return 1; -} - -static int synthesize_change_one(sd_device *dev, sd_device *target) { - int r; - - 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(sd_device *dev) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; - bool part_table_read; - const char *sysname; - int r, k; - - 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); - - r = blockdev_reread_partition_table(dev); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m"); - part_table_read = r >= 0; - - /* search for partitions */ - r = partition_enumerator_new(dev, &e); - if (r < 0) - return r; - - /* 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) { - k = synthesize_change_one(dev, d); - if (k < 0 && r >= 0) - r = k; - } - - return r; -} - -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(dev); - } - - return 0; -} - -static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - manager_exit(manager); - - return 1; -} - -static int on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - manager_reload(manager, /* force = */ true); - - return 1; -} - -static int on_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { - Worker *worker = ASSERT_PTR(userdata); - Manager *manager = ASSERT_PTR(worker->manager); - sd_device *dev = worker->event ? ASSERT_PTR(worker->event->dev) : NULL; - EventResult result; - - assert(si); - - switch (si->si_code) { - case CLD_EXITED: - if (si->si_status == 0) - log_device_debug(dev, "Worker ["PID_FMT"] exited.", si->si_pid); - else - log_device_warning(dev, "Worker ["PID_FMT"] exited with return code %i.", - si->si_pid, si->si_status); - result = EVENT_RESULT_EXIT_STATUS_BASE + si->si_status; - break; - - case CLD_KILLED: - case CLD_DUMPED: - log_device_warning(dev, "Worker ["PID_FMT"] terminated by signal %i (%s).", - si->si_pid, si->si_status, signal_to_string(si->si_status)); - result = EVENT_RESULT_SIGNAL_BASE + si->si_status; - break; - - default: - assert_not_reached(); - } - - if (result != EVENT_RESULT_SUCCESS && dev) { - /* delete state from disk */ - device_delete_db(dev); - device_tag_index(dev, NULL, false); - - /* Forward kernel event to libudev listeners */ - udev_broadcast_result(manager->monitor, dev, result); - } - - worker_free(worker); - - return 1; -} - -static int on_post(sd_event_source *s, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - if (manager->events) { - /* Try to process pending events if idle workers exist. Why is this necessary? - * When a worker finished an event and became idle, even if there was a pending event, - * the corresponding device might have been locked and the processing of the event - * delayed for a while, preventing the worker from processing the event immediately. - * Now, the device may be unlocked. Let's try again! */ - event_queue_start(manager); - return 1; - } - - /* There are no queued events. Let's remove /run/udev/queue and clean up the idle processes. */ - - if (unlink("/run/udev/queue") < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to unlink /run/udev/queue, ignoring: %m"); - } else - log_debug("No events are queued, removing /run/udev/queue."); - - if (!hashmap_isempty(manager->workers)) { - /* There are idle workers */ - (void) event_reset_time_relative(manager->event, &manager->kill_workers_event, - CLOCK_MONOTONIC, 3 * USEC_PER_SEC, USEC_PER_SEC, - on_kill_workers_event, manager, - 0, "kill-workers-event", false); - return 1; - } - - /* There are no idle workers. */ - - if (manager->udev_node_needs_cleanup) { - (void) udev_node_cleanup(); - manager->udev_node_needs_cleanup = false; - } - - if (manager->exit) - return sd_event_exit(manager->event, 0); - - if (manager->cgroup) - /* cleanup possible left-over processes in our cgroup */ - (void) cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, CGROUP_IGNORE_SELF, NULL, NULL, NULL); - - return 1; -} - static int listen_fds(int *ret_ctrl, int *ret_netlink) { int ctrl_fd = -EBADF, netlink_fd = -EBADF; int fd, n; @@ -1475,182 +257,6 @@ static int parse_argv(int argc, char *argv[], Manager *manager) { return 1; } -static Manager* manager_new(void) { - Manager *manager; - - manager = new(Manager, 1); - if (!manager) - return NULL; - - *manager = (Manager) { - .inotify_fd = -EBADF, - .worker_watch = PIPE_EBADF, - .log_level = LOG_INFO, - .resolve_name_timing = RESOLVE_NAME_EARLY, - .timeout_usec = 180 * USEC_PER_SEC, - .timeout_signal = SIGKILL, - }; - - return manager; -} - -static int manager_init(Manager *manager, int fd_ctrl, int fd_uevent) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(manager); - - r = udev_ctrl_new_from_fd(&manager->ctrl, fd_ctrl); - if (r < 0) - return log_error_errno(r, "Failed to initialize udev control socket: %m"); - - r = udev_ctrl_enable_receiving(manager->ctrl); - if (r < 0) - return log_error_errno(r, "Failed to bind udev control socket: %m"); - - r = device_monitor_new_full(&manager->monitor, MONITOR_GROUP_KERNEL, fd_uevent); - if (r < 0) - return log_error_errno(r, "Failed to initialize device monitor: %m"); - - /* Bump receiver buffer, but only if we are not called via socket activation, as in that - * case systemd sets the receive buffer size for us, and the value in the .socket unit - * should take full effect. */ - if (fd_uevent < 0) { - r = sd_device_monitor_set_receive_buffer_size(manager->monitor, 128 * 1024 * 1024); - if (r < 0) - log_warning_errno(r, "Failed to set receive buffer size for device monitor, ignoring: %m"); - } - - (void) sd_device_monitor_set_description(manager->monitor, "manager"); - - r = device_monitor_enable_receiving(manager->monitor); - if (r < 0) - return log_error_errno(r, "Failed to bind netlink socket: %m"); - - manager->log_level = log_get_max_level(); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); - if (r < 0) - log_debug_errno(r, "Failed to get cgroup, ignoring: %m"); - else if (endswith(cgroup, "/udev")) { /* If we are in a subcgroup /udev/ we assume it was delegated to us */ - log_debug("Running in delegated subcgroup '%s'.", cgroup); - manager->cgroup = TAKE_PTR(cgroup); - } - - return 0; -} - -static int manager_main(Manager *manager) { - int fd_worker, r; - - /* unnamed socket from workers to the main daemon */ - r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, manager->worker_watch); - if (r < 0) - return log_error_errno(errno, "Failed to create socketpair for communicating with workers: %m"); - - fd_worker = manager->worker_watch[READ_END]; - - r = setsockopt_int(fd_worker, SOL_SOCKET, SO_PASSCRED, true); - if (r < 0) - return log_error_errno(r, "Failed to enable SO_PASSCRED: %m"); - - manager->inotify_fd = inotify_init1(IN_CLOEXEC); - if (manager->inotify_fd < 0) - return log_error_errno(errno, "Failed to create inotify descriptor: %m"); - - udev_watch_restore(manager->inotify_fd); - - /* block and listen to all signals on signalfd */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, SIGRTMIN+18, -1) >= 0); - - r = sd_event_default(&manager->event); - if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - r = sd_event_add_signal(manager->event, NULL, SIGINT, on_sigterm, manager); - if (r < 0) - return log_error_errno(r, "Failed to create SIGINT event source: %m"); - - r = sd_event_add_signal(manager->event, NULL, SIGTERM, on_sigterm, manager); - if (r < 0) - return log_error_errno(r, "Failed to create SIGTERM event source: %m"); - - r = sd_event_add_signal(manager->event, NULL, SIGHUP, on_sighup, manager); - if (r < 0) - return log_error_errno(r, "Failed to create SIGHUP event source: %m"); - - r = sd_event_set_watchdog(manager->event, true); - if (r < 0) - return log_error_errno(r, "Failed to create watchdog event source: %m"); - - r = udev_ctrl_attach_event(manager->ctrl, manager->event); - if (r < 0) - return log_error_errno(r, "Failed to attach event to udev control: %m"); - - r = udev_ctrl_start(manager->ctrl, on_ctrl_msg, manager); - if (r < 0) - return log_error_errno(r, "Failed to start device monitor: %m"); - - /* This needs to be after the inotify and uevent handling, to make sure - * that the ping is send back after fully processing the pending uevents - * (including the synthetic ones we may create due to inotify events). - */ - r = sd_event_source_set_priority(udev_ctrl_get_event_source(manager->ctrl), SD_EVENT_PRIORITY_IDLE); - if (r < 0) - return log_error_errno(r, "Failed to set IDLE event priority for udev control event source: %m"); - - r = sd_event_add_io(manager->event, &manager->inotify_event, manager->inotify_fd, EPOLLIN, on_inotify, manager); - if (r < 0) - return log_error_errno(r, "Failed to create inotify event source: %m"); - - r = sd_device_monitor_attach_event(manager->monitor, manager->event); - if (r < 0) - return log_error_errno(r, "Failed to attach event to device monitor: %m"); - - r = sd_device_monitor_start(manager->monitor, on_uevent, manager); - if (r < 0) - return log_error_errno(r, "Failed to start device monitor: %m"); - - r = sd_event_add_io(manager->event, NULL, fd_worker, EPOLLIN, on_worker, manager); - if (r < 0) - return log_error_errno(r, "Failed to create worker event source: %m"); - - r = sd_event_add_post(manager->event, NULL, on_post, manager); - if (r < 0) - return log_error_errno(r, "Failed to create post event source: %m"); - - /* Eventually, we probably want to do more here on memory pressure, for example, kill idle workers immediately */ - r = sd_event_add_memory_pressure(manager->event, &manager->memory_pressure_event_source, NULL, NULL); - if (r < 0) - log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || (r == -EHOSTDOWN) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to allocate memory pressure watch, ignoring: %m"); - - r = sd_event_add_signal(manager->event, &manager->memory_pressure_event_source, SIGRTMIN+18, sigrtmin18_handler, NULL); - if (r < 0) - return log_error_errno(r, "Failed to allocate SIGRTMIN+18 event source, ignoring: %m"); - - manager->last_usec = now(CLOCK_MONOTONIC); - - udev_builtin_init(); - - r = udev_rules_load(&manager->rules, manager->resolve_name_timing); - if (r < 0) - return log_error_errno(r, "Failed to read udev rules: %m"); - - r = udev_rules_apply_static_dev_perms(manager->rules); - if (r < 0) - log_warning_errno(r, "Failed to apply permissions on static device nodes, ignoring: %m"); - - notify_ready(manager); - - r = sd_event_loop(manager->event); - if (r < 0) - log_error_errno(r, "Event loop failed: %m"); - - (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); - return r; -} - int run_udevd(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *manager = NULL; int fd_ctrl = -EBADF, fd_uevent = -EBADF; diff --git a/src/udev/udevd.h b/src/udev/udevd.h index 0af0bb8086..583e895225 100644 --- a/src/udev/udevd.h +++ b/src/udev/udevd.h @@ -1,8 +1,4 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - int run_udevd(int argc, char *argv[]); - -bool devpath_conflict(const char *a, const char *b); From 5d7e51b10e78a3ad2b7dfb1e0fb9fa7279d3566f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 05:37:35 +0900 Subject: [PATCH 12/14] udev: rename test-udevd.c -> test-udev-manager.c --- src/udev/meson.build | 8 ++++---- src/udev/{test-udevd.c => test-udev-manager.c} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename src/udev/{test-udevd.c => test-udev-manager.c} (100%) diff --git a/src/udev/meson.build b/src/udev/meson.build index 2cb504d7f6..baedcaa2fe 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -218,6 +218,10 @@ tests += [ 'sources' : files('test-udev-format.c'), 'base' : test_libudev_base, }, + { + 'sources' : files('test-udev-manager.c'), + 'base' : test_libudev_base, + }, { 'sources' : files('test-udev-node.c'), 'base' : test_libudev_base, @@ -230,8 +234,4 @@ tests += [ 'sources' : files('test-udev-spawn.c'), 'base' : test_libudev_base, }, - { - 'sources' : files('test-udevd.c'), - 'base' : test_libudev_base, - }, ] diff --git a/src/udev/test-udevd.c b/src/udev/test-udev-manager.c similarity index 100% rename from src/udev/test-udevd.c rename to src/udev/test-udev-manager.c From 04a1ee58677a2533bf91d40f4ca871b1dae10508 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 06:44:37 +0900 Subject: [PATCH 13/14] udev: move udev_parse_config_full() to udevd.c Then, rename it to manager_parse_udev_config(). No functional change, just refactoring. --- src/shared/udev-util.c | 103 +++++++++++------------------------------ src/shared/udev-util.h | 12 +---- src/udev/udevd.c | 77 +++++++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 91 deletions(-) diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index abf3c5e955..e1f31e52ac 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -31,90 +31,43 @@ static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = { DEFINE_STRING_TABLE_LOOKUP(resolve_name_timing, ResolveNameTiming); -int udev_parse_config_full( - unsigned *ret_children_max, - usec_t *ret_exec_delay_usec, - usec_t *ret_event_timeout_usec, - ResolveNameTiming *ret_resolve_name_timing, - int *ret_timeout_signal) { +int udev_set_max_log_level(char *str) { + size_t n; - _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL, *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL; + /* This may modify input string. */ + + if (isempty(str)) + return 0; + + /* unquote */ + n = strlen(str); + if (n >= 2 && + ((str[0] == '"' && str[n - 1] == '"') || + (str[0] == '\'' && str[n - 1] == '\''))) { + str[n - 1] = '\0'; + str++; + } + + /* we set the udev log level here explicitly, this is supposed + * to regulate the code in libudev/ and udev/. */ + return log_set_max_level_from_string(str); +} + +int udev_parse_config(void) { + _cleanup_free_ char *log_val = NULL; int r; r = parse_env_file(NULL, "/etc/udev/udev.conf", - "udev_log", &log_val, - "children_max", &children_max, - "exec_delay", &exec_delay, - "event_timeout", &event_timeout, - "resolve_names", &resolve_names, - "timeout_signal", &timeout_signal); + "udev_log", &log_val); if (r == -ENOENT) return 0; if (r < 0) return r; - if (log_val) { - const char *log; - size_t n; - - /* unquote */ - n = strlen(log_val); - if (n >= 2 && - ((log_val[0] == '"' && log_val[n-1] == '"') || - (log_val[0] == '\'' && log_val[n-1] == '\''))) { - log_val[n - 1] = '\0'; - log = log_val + 1; - } else - log = log_val; - - /* we set the udev log level here explicitly, this is supposed - * to regulate the code in libudev/ and udev/. */ - r = log_set_max_level_from_string(log); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to set udev log level '%s', ignoring: %m", log); - } - - if (ret_children_max && children_max) { - r = safe_atou(children_max, ret_children_max); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to parse children_max=%s, ignoring: %m", children_max); - } - - if (ret_exec_delay_usec && exec_delay) { - r = parse_sec(exec_delay, ret_exec_delay_usec); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to parse exec_delay=%s, ignoring: %m", exec_delay); - } - - if (ret_event_timeout_usec && event_timeout) { - r = parse_sec(event_timeout, ret_event_timeout_usec); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to parse event_timeout=%s, ignoring: %m", event_timeout); - } - - if (ret_resolve_name_timing && resolve_names) { - ResolveNameTiming t; - - t = resolve_name_timing_from_string(resolve_names); - if (t < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to parse resolve_names=%s, ignoring.", resolve_names); - else - *ret_resolve_name_timing = t; - } - - if (ret_timeout_signal && timeout_signal) { - r = signal_from_string(timeout_signal); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "failed to parse timeout_signal=%s, ignoring: %m", timeout_signal); - else - *ret_timeout_signal = r; - } + r = udev_set_max_log_level(log_val); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to set udev log level '%s', ignoring: %m", log_val); return 0; } diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 97c14fb9bb..77e3ee95b0 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -20,16 +20,8 @@ typedef enum ResolveNameTiming { ResolveNameTiming resolve_name_timing_from_string(const char *s) _pure_; const char *resolve_name_timing_to_string(ResolveNameTiming i) _const_; -int udev_parse_config_full( - unsigned *ret_children_max, - usec_t *ret_exec_delay_usec, - usec_t *ret_event_timeout_usec, - ResolveNameTiming *ret_resolve_name_timing, - int *ret_timeout_signal); - -static inline int udev_parse_config(void) { - return udev_parse_config_full(NULL, NULL, NULL, NULL, NULL); -} +int udev_set_max_log_level(char *str); +int udev_parse_config(void); int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret); int device_wait_for_devlink(const char *path, const char *subsystem, usec_t timeout_usec, sd_device **ret); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 588f8b3b56..d0856b4a55 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -10,6 +10,7 @@ #include "sd-daemon.h" #include "cpu-set-util.h" +#include "env-file.h" #include "errno-util.h" #include "fd-util.h" #include "limits-util.h" @@ -22,6 +23,7 @@ #include "signal-util.h" #include "syslog-util.h" #include "udev-manager.h" +#include "udev-util.h" #include "udevd.h" #include "version.h" @@ -65,6 +67,74 @@ static int listen_fds(int *ret_ctrl, int *ret_netlink) { return 0; } +static int manager_parse_udev_config(Manager *manager) { + _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL, + *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL; + int r; + + assert(manager); + + r = parse_env_file(NULL, "/etc/udev/udev.conf", + "udev_log", &log_val, + "children_max", &children_max, + "exec_delay", &exec_delay, + "event_timeout", &event_timeout, + "resolve_names", &resolve_names, + "timeout_signal", &timeout_signal); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = udev_set_max_log_level(log_val); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to set udev log level '%s', ignoring: %m", log_val); + + if (children_max) { + r = safe_atou(children_max, &manager->children_max); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to parse children_max=%s, ignoring: %m", children_max); + } + + if (exec_delay) { + r = parse_sec(exec_delay, &manager->exec_delay_usec); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to parse exec_delay=%s, ignoring: %m", exec_delay); + } + + if (event_timeout) { + r = parse_sec(event_timeout, &manager->timeout_usec); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to parse event_timeout=%s, ignoring: %m", event_timeout); + } + + if (resolve_names) { + ResolveNameTiming t; + + t = resolve_name_timing_from_string(resolve_names); + if (t < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to parse resolve_names=%s, ignoring.", resolve_names); + else + manager->resolve_name_timing = t; + } + + if (timeout_signal) { + r = signal_from_string(timeout_signal); + if (r < 0) + log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, + "Failed to parse timeout_signal=%s, ignoring: %m", timeout_signal); + else + manager->timeout_signal = r; + } + + return 0; +} + /* * read the kernel command line, in case we need to get into debug mode * udev.log_level= syslog priority @@ -269,12 +339,7 @@ int run_udevd(int argc, char *argv[]) { if (!manager) return log_oom(); - udev_parse_config_full( - &manager->children_max, - &manager->exec_delay_usec, - &manager->timeout_usec, - &manager->resolve_name_timing, - &manager->timeout_signal); + manager_parse_udev_config(manager); log_parse_environment(); log_open(); /* Done again to update after reading configuration. */ From c0af9dee0d6dd790e749f9139d38730862288f8b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 19 Jul 2023 06:58:19 +0900 Subject: [PATCH 14/14] udev: move declaration of ResolveNameTiming to udev-rules.c It is only used by udevd and udevadm, not necessary in src/shared. This also moves UDEV_NAME_SIZE and friends. --- src/shared/udev-util.c | 9 --------- src/shared/udev-util.h | 15 --------------- src/udev/udev-event.c | 1 + src/udev/udev-rules.c | 9 +++++++++ src/udev/udev-rules.h | 16 +++++++++++++++- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index e1f31e52ac..ca286ab24f 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -18,19 +18,10 @@ #include "path-util.h" #include "signal-util.h" #include "stat-util.h" -#include "string-table.h" #include "string-util.h" #include "udev-util.h" #include "utf8.h" -static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = { - [RESOLVE_NAME_NEVER] = "never", - [RESOLVE_NAME_LATE] = "late", - [RESOLVE_NAME_EARLY] = "early", -}; - -DEFINE_STRING_TABLE_LOOKUP(resolve_name_timing, ResolveNameTiming); - int udev_set_max_log_level(char *str) { size_t n; diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 77e3ee95b0..3f9d7740e9 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -5,21 +5,6 @@ #include "time-util.h" -#define UDEV_NAME_SIZE 512 -#define UDEV_PATH_SIZE 1024 -#define UDEV_LINE_SIZE 16384 - -typedef enum ResolveNameTiming { - RESOLVE_NAME_NEVER, - RESOLVE_NAME_LATE, - RESOLVE_NAME_EARLY, - _RESOLVE_NAME_TIMING_MAX, - _RESOLVE_NAME_TIMING_INVALID = -EINVAL, -} ResolveNameTiming; - -ResolveNameTiming resolve_name_timing_from_string(const char *s) _pure_; -const char *resolve_name_timing_to_string(ResolveNameTiming i) _const_; - int udev_set_max_log_level(char *str); int udev_parse_config(void); diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index 0876e478d8..d9a69301db 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -13,6 +13,7 @@ #include "udev-event.h" #include "udev-node.h" #include "udev-trace.h" +#include "udev-util.h" #include "udev-watch.h" #include "user-util.h" diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 52d3f354fa..abb44e9924 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -26,6 +26,7 @@ #include "proc-cmdline.h" #include "socket-util.h" #include "stat-util.h" +#include "string-table.h" #include "strv.h" #include "strxcpyx.h" #include "sysctl-util.h" @@ -2955,3 +2956,11 @@ int udev_rules_apply_static_dev_perms(UdevRules *rules) { return 0; } + +static const char* const resolve_name_timing_table[_RESOLVE_NAME_TIMING_MAX] = { + [RESOLVE_NAME_NEVER] = "never", + [RESOLVE_NAME_LATE] = "late", + [RESOLVE_NAME_EARLY] = "early", +}; + +DEFINE_STRING_TABLE_LOOKUP(resolve_name_timing, ResolveNameTiming); diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 075f8b5ba0..4352312a9a 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -4,7 +4,10 @@ #include "alloc-util.h" #include "hashmap.h" #include "time-util.h" -#include "udev-util.h" + +#define UDEV_NAME_SIZE 512 +#define UDEV_PATH_SIZE 1024 +#define UDEV_LINE_SIZE 16384 typedef struct UdevRuleFile UdevRuleFile; typedef struct UdevRules UdevRules; @@ -18,6 +21,14 @@ typedef enum { _ESCAPE_TYPE_INVALID = -EINVAL, } UdevRuleEscapeType; +typedef enum ResolveNameTiming { + RESOLVE_NAME_NEVER, + RESOLVE_NAME_LATE, + RESOLVE_NAME_EARLY, + _RESOLVE_NAME_TIMING_MAX, + _RESOLVE_NAME_TIMING_INVALID = -EINVAL, +} ResolveNameTiming; + int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos); int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret); unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file); @@ -33,3 +44,6 @@ int udev_rules_apply_to_event(UdevRules *rules, UdevEvent *event, int timeout_signal, Hashmap *properties_list); int udev_rules_apply_static_dev_perms(UdevRules *rules); + +ResolveNameTiming resolve_name_timing_from_string(const char *s) _pure_; +const char *resolve_name_timing_to_string(ResolveNameTiming i) _const_;