diff options
Diffstat (limited to 'src/udev')
-rw-r--r-- | src/udev/meson.build | 3 | ||||
-rw-r--r-- | src/udev/test-udev-rule-runner.c | 2 | ||||
-rw-r--r-- | src/udev/udev-builtin.c | 2 | ||||
-rw-r--r-- | src/udev/udev-builtin.h | 2 | ||||
-rw-r--r-- | src/udev/udev-config.c | 64 | ||||
-rw-r--r-- | src/udev/udev-config.h | 2 | ||||
-rw-r--r-- | src/udev/udev-ctrl.h | 2 | ||||
-rw-r--r-- | src/udev/udev-def.h | 4 | ||||
-rw-r--r-- | src/udev/udev-dump.c | 130 | ||||
-rw-r--r-- | src/udev/udev-dump.h | 10 | ||||
-rw-r--r-- | src/udev/udev-event.c | 6 | ||||
-rw-r--r-- | src/udev/udev-event.h | 3 | ||||
-rw-r--r-- | src/udev/udev-manager-ctrl.c | 132 | ||||
-rw-r--r-- | src/udev/udev-manager-ctrl.h | 7 | ||||
-rw-r--r-- | src/udev/udev-manager.c | 218 | ||||
-rw-r--r-- | src/udev/udev-rules.c | 1435 | ||||
-rw-r--r-- | src/udev/udev-rules.h | 2 | ||||
-rw-r--r-- | src/udev/udev-varlink.c | 57 | ||||
-rw-r--r-- | src/udev/udev-worker.c | 1 | ||||
-rw-r--r-- | src/udev/udevadm-cat.c | 110 | ||||
-rw-r--r-- | src/udev/udevadm-control.c | 23 | ||||
-rw-r--r-- | src/udev/udevadm-monitor.c | 51 | ||||
-rw-r--r-- | src/udev/udevadm-test.c | 147 | ||||
-rw-r--r-- | src/udev/udevadm-util.c | 109 | ||||
-rw-r--r-- | src/udev/udevadm-util.h | 1 | ||||
-rw-r--r-- | src/udev/udevadm-verify.c | 85 | ||||
-rw-r--r-- | src/udev/udevadm.c | 2 | ||||
-rw-r--r-- | src/udev/udevadm.h | 1 |
28 files changed, 1635 insertions, 976 deletions
diff --git a/src/udev/meson.build b/src/udev/meson.build index f008ea1f66..697d108141 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later udevadm_sources = files( + 'udevadm-cat.c', 'udevadm-control.c', 'udevadm-hwdb.c', 'udevadm-info.c', @@ -31,9 +32,11 @@ libudevd_core_sources = files( 'udev-builtin.c', 'udev-config.c', 'udev-ctrl.c', + 'udev-dump.c', 'udev-event.c', 'udev-format.c', 'udev-manager.c', + 'udev-manager-ctrl.c', 'udev-node.c', 'udev-rules.c', 'udev-spawn.c', diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 9a04abf590..9ad446aa2d 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -136,7 +136,7 @@ static int run(int argc, char *argv[]) { usleep_safe(us); } - assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY) == 0); + assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY, /* extra = */ NULL) == 0); const char *syspath = strjoina("/sys", devpath); r = device_new_from_synthetic_event(&dev, syspath, action); diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index b190504a83..749589463d 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -25,10 +25,10 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, - [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif + [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, }; void udev_builtin_init(void) { diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index 3b5f3bd120..83cf103ab5 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -47,10 +47,10 @@ extern const UdevBuiltin udev_builtin_net_driver; extern const UdevBuiltin udev_builtin_net_id; extern const UdevBuiltin udev_builtin_net_setup_link; extern const UdevBuiltin udev_builtin_path_id; -extern const UdevBuiltin udev_builtin_usb_id; #if HAVE_ACL extern const UdevBuiltin udev_builtin_uaccess; #endif +extern const UdevBuiltin udev_builtin_usb_id; void udev_builtin_init(void); void udev_builtin_exit(void); diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index d511691ab2..6562b34d2f 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -112,6 +112,20 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; + } else if (proc_cmdline_key_streq(key, "udev.trace")) { + + if (!value) + config->trace = true; + else { + r = parse_boolean(value); + if (r < 0) + log_warning_errno(r, "Failed to parse udev.trace argument, ignoring: %s", value); + else + config->trace = r; + } + + return 0; + } else { if (startswith(key, "udev.")) log_warning("Unknown udev kernel command line option \"%s\", ignoring.", key); @@ -257,13 +271,24 @@ static int parse_argv(int argc, char *argv[], UdevConfig *config) { manager->config_by_command.name || \ manager->config_by_udev_conf.name; +static void manager_merge_config_log_level(Manager *manager) { + assert(manager); + + MERGE_BOOL(trace); + + if (manager->config.trace) + manager->config.log_level = LOG_DEBUG; + else + MERGE_NON_NEGATIVE(log_level, log_get_max_level()); +} + static void manager_merge_config(Manager *manager) { assert(manager); /* udev.conf has the lowest priority, then followed by command line arguments, kernel command line options, and values set by udev control. */ - MERGE_NON_NEGATIVE(log_level, log_get_max_level()); + manager_merge_config_log_level(manager); MERGE_NON_NEGATIVE(resolve_name_timing, RESOLVE_NAME_EARLY); MERGE_NON_ZERO(exec_delay_usec, 0); MERGE_NON_ZERO(timeout_usec, DEFAULT_WORKER_TIMEOUT_USEC); @@ -306,20 +331,40 @@ void manager_set_children_max(Manager *manager, unsigned n) { void manager_set_log_level(Manager *manager, int log_level) { assert(manager); - assert(LOG_PRI(log_level) == log_level); + assert(log_level_is_valid(log_level)); int old = log_get_max_level(); - log_set_max_level(log_level); - manager->config.log_level = manager->config_by_control.log_level = log_level; + manager->config_by_control.log_level = log_level; + manager_merge_config_log_level(manager); - if (log_level != old) - manager_kill_workers(manager, /* force = */ false); + if (manager->config.log_level == old) + return; + + log_set_max_level(manager->config.log_level); + manager_kill_workers(manager, /* force = */ false); +} + +void manager_set_trace(Manager *manager, bool enable) { + assert(manager); + + bool old = manager->config.trace; + + manager->config_by_control.trace = enable; + manager_merge_config_log_level(manager); + + if (manager->config.trace == old) + return; + + log_set_max_level(manager->config.log_level); + manager_kill_workers(manager, /* force = */ false); } static void manager_adjust_config(UdevConfig *config) { assert(config); + log_set_max_level(config->log_level); + if (config->timeout_usec < MIN_WORKER_TIMEOUT_USEC) { log_debug("Timeout (%s) for processing event is too small, using the default: %s", FORMAT_TIMESPAN(config->timeout_usec, 1), @@ -362,7 +407,7 @@ static int manager_set_environment_one(Manager *manager, const char *s) { _cleanup_free_ char *old_key = NULL, *old_value = NULL; old_value = hashmap_get2(manager->properties, key, (void**) &old_key); - r = hashmap_ensure_replace(&manager->properties, &string_hash_ops, key, value); + r = hashmap_ensure_replace(&manager->properties, &string_hash_ops_free_free, key, value); if (r < 0) { assert(!old_key); assert(!old_value); @@ -411,7 +456,6 @@ int manager_load(Manager *manager, int argc, char *argv[]) { if (arg_debug) log_set_target(LOG_TARGET_CONSOLE); - log_set_max_level(manager->config.log_level); manager_adjust_config(&manager->config); return 1; } @@ -424,7 +468,6 @@ UdevReloadFlags manager_reload_config(Manager *manager) { manager->config_by_udev_conf = UDEV_CONFIG_INIT; manager_parse_udev_config(&manager->config_by_udev_conf); manager_merge_config(manager); - log_set_max_level(manager->config.log_level); manager_adjust_config(&manager->config); if (manager->config.resolve_name_timing != old.resolve_name_timing) @@ -434,7 +477,8 @@ UdevReloadFlags manager_reload_config(Manager *manager) { manager->config.exec_delay_usec != old.exec_delay_usec || manager->config.timeout_usec != old.timeout_usec || manager->config.timeout_signal != old.timeout_signal || - manager->config.blockdev_read_only != old.blockdev_read_only) + manager->config.blockdev_read_only != old.blockdev_read_only || + manager->config.trace != old.trace) return UDEV_RELOAD_KILL_WORKERS; return 0; diff --git a/src/udev/udev-config.h b/src/udev/udev-config.h index 68bb1ea98c..fd31979f6d 100644 --- a/src/udev/udev-config.h +++ b/src/udev/udev-config.h @@ -18,6 +18,7 @@ typedef struct UdevConfig { usec_t timeout_usec; int timeout_signal; bool blockdev_read_only; + bool trace; } UdevConfig; #define UDEV_CONFIG_INIT \ @@ -28,6 +29,7 @@ typedef struct UdevConfig { void manager_set_children_max(Manager *manager, unsigned n); void manager_set_log_level(Manager *manager, int log_level); +void manager_set_trace(Manager *manager, bool enable); void manager_set_environment(Manager *manager, char * const *v); int manager_load(Manager *manager, int argc, char *argv[]); diff --git a/src/udev/udev-ctrl.h b/src/udev/udev-ctrl.h index 11fc0b62de..bcdd849d0d 100644 --- a/src/udev/udev-ctrl.h +++ b/src/udev/udev-ctrl.h @@ -30,7 +30,7 @@ typedef int (*udev_ctrl_handler_t)(UdevCtrl *udev_ctrl, UdevCtrlMessageType type int udev_ctrl_new_from_fd(UdevCtrl **ret, int fd); static inline int udev_ctrl_new(UdevCtrl **ret) { - return udev_ctrl_new_from_fd(ret, -1); + return udev_ctrl_new_from_fd(ret, -EBADF); } int udev_ctrl_enable_receiving(UdevCtrl *uctrl); diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h index ed231764bc..c157c487cf 100644 --- a/src/udev/udev-def.h +++ b/src/udev/udev-def.h @@ -50,10 +50,10 @@ typedef enum UdevBuiltinCommand { UDEV_BUILTIN_NET_ID, UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, - UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_BUILTIN_UACCESS, #endif + UDEV_BUILTIN_USB_ID, _UDEV_BUILTIN_MAX, _UDEV_BUILTIN_INVALID = -EINVAL, } UdevBuiltinCommand; @@ -73,10 +73,10 @@ typedef enum UdevReloadFlags { UDEV_RELOAD_BUILTIN_NET_ID = 1u << UDEV_BUILTIN_NET_ID, UDEV_RELOAD_BUILTIN_NET_LINK = 1u << UDEV_BUILTIN_NET_LINK, UDEV_RELOAD_BUILTIN_PATH_ID = 1u << UDEV_BUILTIN_PATH_ID, - UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_RELOAD_BUILTIN_UACCESS = 1u << UDEV_BUILTIN_UACCESS, #endif + UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, UDEV_RELOAD_KILL_WORKERS = 1u << (_UDEV_BUILTIN_MAX + 0), UDEV_RELOAD_RULES = 1u << (_UDEV_BUILTIN_MAX + 1), } UdevReloadFlags; diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c new file mode 100644 index 0000000000..918c966c4e --- /dev/null +++ b/src/udev/udev-dump.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "ansi-color.h" +#include "device-private.h" +#include "device-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "udev-builtin.h" +#include "udev-dump.h" +#include "udev-event.h" +#include "user-util.h" + +static void event_cache_written_value(Hashmap **values, const char *attr, const char *value) { + assert(values); + + _unused_ _cleanup_free_ void *key = NULL; + free(hashmap_remove2(*values, attr, &key)); + + if (hashmap_put_strdup_full(values, &path_hash_ops_free_free, attr, value) < 0) + log_oom_debug(); +} + +void event_cache_written_sysattr(UdevEvent *event, const char *attr, const char *value) { + event_cache_written_value(&event->written_sysattrs, attr, value); +} + +void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char *value) { + event_cache_written_value(&event->written_sysctls, attr, value); +} + +void dump_event(UdevEvent *event, FILE *f) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + + if (!f) + f = stdout; + + if (!hashmap_isempty(event->written_sysattrs)) { + const char *key, *value; + + fprintf(f, "%sWritten sysfs attributes:%s\n", ansi_highlight(), ansi_normal()); + HASHMAP_FOREACH_KEY(value, key, event->written_sysattrs) + fprintf(f, " %s : %s\n", key, value); + } + + if (!hashmap_isempty(event->written_sysctls)) { + const char *key, *value; + + fprintf(f, "%sWritten sysctl entries:%s\n", ansi_highlight(), ansi_normal()); + HASHMAP_FOREACH_KEY(value, key, event->written_sysctls) + fprintf(f, " %s : %s\n", key, value); + } + + fprintf(f, "%sProperties:%s\n", ansi_highlight(), ansi_normal()); + FOREACH_DEVICE_PROPERTY(dev, key, value) + fprintf(f, " %s=%s\n", key, value); + + if (sd_device_get_tag_first(dev)) { + fprintf(f, "%sTags:%s\n", ansi_highlight(), ansi_normal()); + FOREACH_DEVICE_TAG(dev, tag) + fprintf(f, " %s\n", tag); + } + + if (sd_device_get_devnum(dev, NULL) >= 0) { + + if (sd_device_get_devlink_first(dev)) { + int prio = 0; + (void) device_get_devlink_priority(dev, &prio); + fprintf(f, "%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); + FOREACH_DEVICE_DEVLINK(dev, devlink) + fprintf(f, " %s\n", devlink); + } + + fprintf(f, "%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); + + uid_t uid = event->uid; + if (!uid_is_valid(uid)) + (void) device_get_devnode_uid(dev, &uid); + if (uid_is_valid(uid)) { + _cleanup_free_ char *user = uid_to_name(uid); + fprintf(f, "%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); + } + + gid_t gid = event->gid; + if (!gid_is_valid(uid)) + (void) device_get_devnode_gid(dev, &gid); + if (gid_is_valid(gid)) { + _cleanup_free_ char *group = gid_to_name(gid); + fprintf(f, "%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); + } + + mode_t mode = event->mode; + if (mode == MODE_INVALID) + (void) device_get_devnode_mode(dev, &mode); + if (mode != MODE_INVALID) + fprintf(f, "%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); + + if (!ordered_hashmap_isempty(event->seclabel_list)) { + const char *name, *label; + fprintf(f, "%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) + fprintf(f, " %s : %s\n", name, label); + } + } + + if (sd_device_get_ifindex(dev, NULL) >= 0) { + if (!isempty(event->name)) + fprintf(f, "%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); + + if (!strv_isempty(event->altnames)) { + bool space = true; + fprintf(f, "%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); + fputstrv(f, event->altnames, "\n ", &space); + fputs("\n", f); + } + } + + if (!ordered_hashmap_isempty(event->run_list)) { + void *val; + const char *command; + fprintf(f, "%sQueued commands:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { + UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) + fprintf(f, " RUN{builtin} : %s\n", command); + else + fprintf(f, " RUN{program} : %s\n", command); + } + } +} diff --git a/src/udev/udev-dump.h b/src/udev/udev-dump.h new file mode 100644 index 0000000000..514f8267a7 --- /dev/null +++ b/src/udev/udev-dump.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include <stdio.h> + +typedef struct UdevEvent UdevEvent; + +void event_cache_written_sysattr(UdevEvent *event, const char *attr, const char *value); +void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char *value); +void dump_event(UdevEvent *event, FILE *f); diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index e3661f5bf8..7d9153061f 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -52,8 +52,10 @@ static UdevEvent* udev_event_free(UdevEvent *event) { sd_device_unref(event->dev); sd_device_unref(event->dev_db_clone); sd_netlink_unref(event->rtnl); - ordered_hashmap_free_free_key(event->run_list); - ordered_hashmap_free_free_free(event->seclabel_list); + ordered_hashmap_free(event->run_list); + ordered_hashmap_free(event->seclabel_list); + hashmap_free(event->written_sysattrs); + hashmap_free(event->written_sysctls); free(event->program_result); free(event->name); strv_free(event->altnames); diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h index b1ad2bf0c2..d18fb0978b 100644 --- a/src/udev/udev-event.h +++ b/src/udev/udev-event.h @@ -36,6 +36,8 @@ typedef struct UdevEvent { gid_t gid; OrderedHashmap *seclabel_list; OrderedHashmap *run_list; + Hashmap *written_sysattrs; + Hashmap *written_sysctls; usec_t birth_usec; unsigned builtin_run; unsigned builtin_ret; @@ -48,6 +50,7 @@ typedef struct UdevEvent { bool name_final; bool devlink_final; bool run_final; + bool trace; bool log_level_was_debug; int default_log_level; EventMode event_mode; diff --git a/src/udev/udev-manager-ctrl.c b/src/udev/udev-manager-ctrl.c new file mode 100644 index 0000000000..b39af06927 --- /dev/null +++ b/src/udev/udev-manager-ctrl.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "sd-daemon.h" +#include "sd-event.h" + +#include "syslog-util.h" +#include "udev-ctrl.h" +#include "udev-manager.h" +#include "udev-manager-ctrl.h" + +/* 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); + + assert(value); + + switch (type) { + case UDEV_CTRL_SET_LOG_LEVEL: + if (!log_level_is_valid(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); + + manager_set_log_level(manager, value->intval); + 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: + if (!udev_property_assignment_is_valid(value->buf)) { + log_debug("Received invalid udev control message(SET_ENV, %s), ignoring.", value->buf); + break; + } + + log_debug("Received udev control message(SET_ENV, %s)", value->buf); + manager_set_environment(manager, STRV_MAKE(value->buf)); + 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_set_children_max(manager, value->intval); + 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; +} + +int manager_init_ctrl(Manager *manager, int fd) { + int r; + + assert(manager); + + /* This takes passed file descriptor on success. */ + + if (fd >= 0) { + if (manager->ctrl) + return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple control socket (%i), ignoring.", fd); + + r = sd_is_socket(fd, AF_UNIX, SOCK_SEQPACKET, -1); + if (r < 0) + return log_warning_errno(r, "Failed to verify socket type of %i, ignoring: %m", fd); + if (r == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Received invalid control socket (%i), ignoring.", fd); + } else { + if (manager->ctrl) + return 0; + } + + r = udev_ctrl_new_from_fd(&manager->ctrl, fd); + if (r < 0) + return log_error_errno(r, "Failed to initialize udev control socket: %m"); + + return 0; +} + +int manager_start_ctrl(Manager *manager) { + int r; + + assert(manager); + assert(manager->event); + + r = manager_init_ctrl(manager, -EBADF); + if (r < 0) + return r; + + r = udev_ctrl_enable_receiving(manager->ctrl); + if (r < 0) + return log_error_errno(r, "Failed to bind udev control socket: %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 udev control: %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"); + + return 0; +} diff --git a/src/udev/udev-manager-ctrl.h b/src/udev/udev-manager-ctrl.h new file mode 100644 index 0000000000..507072ce27 --- /dev/null +++ b/src/udev/udev-manager-ctrl.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +typedef struct Manager Manager; + +int manager_init_ctrl(Manager *manager, int fd); +int manager_start_ctrl(Manager *manager); diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index b7f2b185c9..97465b796d 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -27,6 +27,7 @@ #include "udev-ctrl.h" #include "udev-event.h" #include "udev-manager.h" +#include "udev-manager-ctrl.h" #include "udev-node.h" #include "udev-rules.h" #include "udev-spawn.h" @@ -138,7 +139,7 @@ Manager* manager_free(Manager *manager) { udev_builtin_exit(); - hashmap_free_free_free(manager->properties); + hashmap_free(manager->properties); udev_rules_free(manager->rules); hashmap_free(manager->workers); @@ -283,7 +284,7 @@ void manager_reload(Manager *manager, bool force) { udev_builtin_reload(flags); if (FLAGS_SET(flags, UDEV_RELOAD_RULES)) { - r = udev_rules_load(&rules, manager->config.resolve_name_timing); + r = udev_rules_load(&rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); else @@ -843,69 +844,6 @@ static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdat 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); - - assert(value); - - switch (type) { - case UDEV_CTRL_SET_LOG_LEVEL: - if (LOG_PRI(value->intval) != 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); - - manager_set_log_level(manager, value->intval); - 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: - if (!udev_property_assignment_is_valid(value->buf)) { - log_debug("Received invalid udev control message(SET_ENV, %s), ignoring.", value->buf); - break; - } - - log_debug("Received udev control message(SET_ENV, %s)", value->buf); - manager_set_environment(manager, STRV_MAKE(value->buf)); - 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_set_children_max(manager, value->intval); - 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; @@ -1135,118 +1073,73 @@ Manager* manager_new(void) { return manager; } -static int listen_fds(int *ret_ctrl, int *ret_netlink) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_close_ int ctrl_fd = -EBADF, netlink_fd = -EBADF; - - assert(ret_ctrl); - assert(ret_netlink); - - int n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); - if (n < 0) - return n; - - if (strv_length(names) != (size_t) n) - return -EIO; - - for (int i = 0; i < n; i++) { - int fd = SD_LISTEN_FDS_START + i; - - if (streq(names[i], "varlink")) - continue; /* The fd will be handled by sd_varlink_server_listen_auto(). */ - - if (sd_is_socket(fd, AF_UNIX, SOCK_SEQPACKET, -1) > 0) { - if (ctrl_fd >= 0) { - log_debug("Received multiple seqpacket socket (%s), ignoring.", names[i]); - goto unused; - } - - ctrl_fd = fd; - continue; - } - - if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { - if (netlink_fd >= 0) { - log_debug("Received multiple netlink socket (%s), ignoring.", names[i]); - goto unused; - } - - netlink_fd = fd; - continue; - } - - log_debug("Received unexpected fd (%s), ignoring.", names[i]); - - unused: - close_and_notify_warn(fd, names[i]); - } - - *ret_ctrl = TAKE_FD(ctrl_fd); - *ret_netlink = TAKE_FD(netlink_fd); - return 0; -} - -static int manager_init_ctrl(Manager *manager, int fd_ctrl) { - _cleanup_(udev_ctrl_unrefp) UdevCtrl *ctrl = NULL; - _cleanup_close_ int fd = fd_ctrl; +static int manager_init_device_monitor(Manager *manager, int fd) { int r; assert(manager); - /* This consumes passed file descriptor. */ + /* This takes passed file descriptor on success. */ - r = udev_ctrl_new_from_fd(&ctrl, fd); - if (r < 0) - return log_error_errno(r, "Failed to initialize udev control socket: %m"); - TAKE_FD(fd); + if (fd >= 0) { + if (manager->monitor) + return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple netlink socket (%i), ignoring.", fd); + + r = sd_is_socket(fd, AF_NETLINK, SOCK_RAW, /* listening = */ -1); + if (r < 0) + return log_warning_errno(r, "Failed to verify socket type of %i, ignoring: %m", fd); + if (r == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Received invalid netlink socket (%i), ignoring.", fd); + } else { + if (manager->monitor) + return 0; + } - r = udev_ctrl_enable_receiving(ctrl); + r = device_monitor_new_full(&manager->monitor, MONITOR_GROUP_KERNEL, fd); if (r < 0) - return log_error_errno(r, "Failed to bind udev control socket: %m"); + return log_error_errno(r, "Failed to initialize device monitor: %m"); - manager->ctrl = TAKE_PTR(ctrl); return 0; } -static int manager_init_device_monitor(Manager *manager, int fd_uevent) { - _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; - _cleanup_close_ int fd = fd_uevent; +static int manager_listen_fds(Manager *manager) { + _cleanup_strv_free_ char **names = NULL; int r; assert(manager); - /* This consumes passed file descriptor. */ + int n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); + if (n < 0) + return n; - r = device_monitor_new_full(&monitor, MONITOR_GROUP_KERNEL, fd); - if (r < 0) - return log_error_errno(r, "Failed to initialize device monitor: %m"); - TAKE_FD(fd); + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; - (void) sd_device_monitor_set_description(monitor, "manager"); + if (streq(names[i], "varlink")) + r = 0; /* The fd will be handled by sd_varlink_server_listen_auto(). */ + else if (streq(names[i], "systemd-udevd-control.socket")) + r = manager_init_ctrl(manager, fd); + else if (streq(names[i], "systemd-udevd-kernel.socket")) + r = manager_init_device_monitor(manager, fd); + else + r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Received unexpected fd (%s), ignoring.", names[i]); + if (r < 0) + close_and_notify_warn(fd, names[i]); + } - manager->monitor = TAKE_PTR(monitor); return 0; } int manager_init(Manager *manager) { - _cleanup_close_ int fd_ctrl = -EBADF, fd_uevent = -EBADF; - _cleanup_free_ char *cgroup = NULL; int r; assert(manager); - r = listen_fds(&fd_ctrl, &fd_uevent); + r = manager_listen_fds(manager); if (r < 0) return log_error_errno(r, "Failed to listen on fds: %m"); - r = manager_init_ctrl(manager, TAKE_FD(fd_ctrl)); - if (r < 0) - return r; - - r = manager_init_device_monitor(manager, TAKE_FD(fd_uevent)); - if (r < 0) - return r; - + _cleanup_free_ char *cgroup = NULL; r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); if (r < 0) log_debug_errno(r, "Failed to get cgroup, ignoring: %m"); @@ -1258,35 +1151,16 @@ int manager_init(Manager *manager) { return 0; } -static int manager_start_ctrl(Manager *manager) { +static int manager_start_device_monitor(Manager *manager) { int r; assert(manager); - assert(manager->ctrl); - r = udev_ctrl_attach_event(manager->ctrl, manager->event); + r = manager_init_device_monitor(manager, -EBADF); 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 udev control: %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"); - - return 0; -} - -static int manager_start_device_monitor(Manager *manager) { - int r; + return r; - assert(manager); - assert(manager->monitor); + (void) sd_device_monitor_set_description(manager->monitor, "manager"); r = sd_device_monitor_attach_event(manager->monitor, manager->event); if (r < 0) @@ -1442,7 +1316,7 @@ int manager_main(Manager *manager) { udev_builtin_init(); - r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing); + r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) return log_error_errno(r, "Failed to read udev rules: %m"); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 3687748993..3c0133d55e 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -18,6 +18,7 @@ #include "fs-util.h" #include "glob-util.h" #include "list.h" +#include "memstream-util.h" #include "mkdir.h" #include "netif-naming-scheme.h" #include "nulstr-util.h" @@ -32,6 +33,7 @@ #include "sysctl-util.h" #include "syslog-util.h" #include "udev-builtin.h" +#include "udev-dump.h" #include "udev-event.h" #include "udev-format.h" #include "udev-node.h" @@ -40,6 +42,7 @@ #include "udev-trace.h" #include "udev-util.h" #include "udev-worker.h" +#include "uid-classification.h" #include "user-util.h" #include "virt.h" @@ -117,6 +120,7 @@ typedef enum { #define _TK_A_MIN _TK_M_MAX /* lvalues which take one of assign operators */ + TK_A_OPTIONS_DUMP, /* no argument */ TK_A_OPTIONS_STRING_ESCAPE_NONE, /* no argument */ TK_A_OPTIONS_STRING_ESCAPE_REPLACE, /* no argument */ TK_A_OPTIONS_DB_PERSIST, /* no argument */ @@ -166,12 +170,15 @@ struct UdevRuleToken { const char *value; void *data; + const char *token_str; /* original token string for logging */ + UdevRuleLine *rule_line; LIST_FIELDS(UdevRuleToken, tokens); }; struct UdevRuleLine { char *line; + char *line_for_logging; unsigned line_number; UdevRuleLineType type; @@ -204,83 +211,172 @@ struct UdevRules { #define LINE_GET_RULES(line) \ ASSERT_PTR(ASSERT_PTR(ASSERT_PTR(line)->rule_file)->rules) +static bool token_is_for_parents(UdevRuleToken *token) { + return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; +} + /*** Logging helpers ***/ -#define log_udev_rule_internal(device, file, line_nr, level, error, fmt, ...) \ +#define _log_udev_rule_file_full(device, device_u, file, file_u, line_nr, level, level_u, error, fmt, ...) \ ({ \ - int _lv = (level); \ - sd_device *_dev = (device); \ - UdevRuleFile *_f = (file); \ - const char *_n = _f ? _f->filename : NULL; \ + int level_u = (level); \ + sd_device *device_u = (device); \ + UdevRuleFile *file_u = (file); \ \ - if (!_dev && _f) \ - _f->issues |= (1U << _lv); \ + if (!device_u && file_u) \ + file_u->issues |= (1U << level_u); \ \ log_device_full_errno_zerook( \ - _dev, _lv, error, "%s:%u " fmt, \ - strna(_n), line_nr, \ - ##__VA_ARGS__); \ + device_u, level_u, error, "%s:%u " fmt, \ + strna(file_u ? file_u->filename : NULL), \ + line_nr, ##__VA_ARGS__); \ }) -/* Mainly used when applying tokens to the event device. */ -#define log_event_full_errno_zerook(device, token, ...) \ +#define log_udev_rule_file_full(device, file, line_nr, level, error, fmt, ...) \ + _log_udev_rule_file_full(device, UNIQ_T(d, UNIQ), file, UNIQ_T(f, UNIQ), line_nr, level, UNIQ_T(l, UNIQ), error, fmt, ##__VA_ARGS__) + +#define _log_udev_rule_line_full(device, line, line_u, ...) \ ({ \ - UdevRuleToken *_t = (token); \ - UdevRuleLine *_l = _t ? _t->rule_line : NULL; \ + UdevRuleLine *line_u = ASSERT_PTR(line); \ \ - log_udev_rule_internal( \ + log_udev_rule_file_full( \ device, \ - _l ? _l->rule_file : NULL, \ - _l ? _l->line_number : 0, \ + line_u->rule_file, line_u->line_number, \ __VA_ARGS__); \ }) -#define log_event_full_errno(device, token, level, error, ...) \ +#define log_udev_rule_line_full(device, line, ...) \ + _log_udev_rule_line_full(device, line, UNIQ_T(l, UNIQ), __VA_ARGS__) + +/* Mainly used when applying tokens to the event device. */ +#define _log_event_full_errno_zerook(event, event_u, token, token_u, level, error, fmt, ...) \ ({ \ - int _error = (error); \ - ASSERT_NON_ZERO(_error); \ + UdevEvent *event_u = ASSERT_PTR(event); \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + log_udev_rule_line_full( \ + token_is_for_parents(token_u) ? event_u->dev_parent : event_u->dev, \ + token_u->rule_line, \ + level, error, "%s: " fmt, \ + token_u->token_str, ##__VA_ARGS__); \ + }) + +#define log_event_full_errno_zerook(event, token, ...) \ + _log_event_full_errno_zerook(event, UNIQ_T(e, UNIQ), token, UNIQ_T(t, UNIQ), __VA_ARGS__) + +#define _log_event_full_errno(event, token, level, error, error_u, ...) \ + ({ \ + int error_u = (error); \ + ASSERT_NON_ZERO(error_u); \ log_event_full_errno_zerook( \ - device, token, level, _error, ##__VA_ARGS__); \ + event, token, level, error_u, \ + __VA_ARGS__); \ }) -#define log_event_full(device, token, level, ...) (void) log_event_full_errno_zerook(device, token, level, 0, __VA_ARGS__) +#define log_event_full_errno(event, token, level, error, ...) \ + _log_event_full_errno(event, token, level, error, UNIQ_T(e, UNIQ), __VA_ARGS__) -#define log_event_debug(device, token, ...) log_event_full(device, token, LOG_DEBUG, __VA_ARGS__) -#define log_event_info(device, token, ...) log_event_full(device, token, LOG_INFO, __VA_ARGS__) -#define log_event_notice(device, token, ...) log_event_full(device, token, LOG_NOTICE, __VA_ARGS__) -#define log_event_warning(device, token, ...) log_event_full(device, token, LOG_WARNING, __VA_ARGS__) -#define log_event_error(device, token, ...) log_event_full(device, token, LOG_ERR, __VA_ARGS__) +#define log_event_full(event, token, level, ...) (void) log_event_full_errno_zerook(event, token, level, 0, __VA_ARGS__) -#define log_event_debug_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_DEBUG, error, __VA_ARGS__) -#define log_event_info_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_INFO, error, __VA_ARGS__) -#define log_event_notice_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_NOTICE, error, __VA_ARGS__) -#define log_event_warning_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_WARNING, error, __VA_ARGS__) -#define log_event_error_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_ERR, error, __VA_ARGS__) +#define log_event_debug(event, token, ...) log_event_full(event, token, LOG_DEBUG, __VA_ARGS__) +#define log_event_info(event, token, ...) log_event_full(event, token, LOG_INFO, __VA_ARGS__) +#define log_event_notice(event, token, ...) log_event_full(event, token, LOG_NOTICE, __VA_ARGS__) +#define log_event_warning(event, token, ...) log_event_full(event, token, LOG_WARNING, __VA_ARGS__) +#define log_event_error(event, token, ...) log_event_full(event, token, LOG_ERR, __VA_ARGS__) -/* Mainly used when parsing .rules files. */ -#define log_file_full_errno_zerook(...) \ - log_udev_rule_internal(NULL, __VA_ARGS__) +#define log_event_debug_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_DEBUG, error, __VA_ARGS__) +#define log_event_info_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_INFO, error, __VA_ARGS__) +#define log_event_notice_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_NOTICE, error, __VA_ARGS__) +#define log_event_warning_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_WARNING, error, __VA_ARGS__) +#define log_event_error_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_ERR, error, __VA_ARGS__) -#define log_file_error(file, line_nr, ...) \ - log_file_full_errno_zerook(file, line_nr, LOG_ERR, 0, __VA_ARGS__) +#define _log_event_trace(event, event_u, ...) \ + ({ \ + UdevEvent *event_u = ASSERT_PTR(event); \ + \ + event_u->trace ? \ + log_event_debug(event_u, __VA_ARGS__) : \ + (void) 0; \ + }) + +#define log_event_trace(event, ...) \ + _log_event_trace(event, UNIQ_T(e, UNIQ), __VA_ARGS__) -#define log_line_full_errno_zerook(line, ...) \ +#define _log_event_result(event, token, result, result_u) \ ({ \ - UdevRuleLine *_l = (line); \ - log_file_full_errno_zerook( \ - _l ? _l->rule_file : NULL, \ - _l ? _l->line_number : 0, \ - __VA_ARGS__); \ + bool result_u = (result); \ + \ + log_event_trace(event, token, "%s", \ + result_u ? "PASS" : "FAIL"); \ + result_u; \ }) -#define log_line_full_errno(line, level, error, ...) \ +#define log_event_result(event, token, result) \ + _log_event_result(event, token, result, UNIQ_T(r, UNIQ)) + +#define log_event_done(event, token) \ ({ \ - int _error = (error); \ - ASSERT_NON_ZERO(_error); \ - log_line_full_errno_zerook( \ - line, level, _error, ##__VA_ARGS__); \ + log_event_trace(event, token, "DONE"); \ + true; \ }) +#define _log_event_final_set(event, token, token_u) \ + ({ \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + log_event_trace(event, token_u, \ + "Already assigned final value, ignoring further %s.", \ + token_u->op == OP_REMOVE ? "modification" : "assignment"); \ + true; \ + }) + +#define log_event_final_set(event, token) \ + _log_event_final_set(event, token, UNIQ_T(t, UNIQ)) + +#define _log_event_truncated(event, token, token_u, what, format) \ + ({ \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + token_u->type < _TK_M_MAX ? \ + log_event_debug(event, token_u, \ + "The %s is truncated while substituting into \"%s\", assuming the token fails.", \ + what, (const char*) format) : \ + log_event_warning( \ + event, token_u, \ + "The %s is truncated while substituting into \"%s\", refusing to apply the token.", \ + what, (const char*) format); \ + }) + +#define log_event_truncated(event, token, what, format) \ + _log_event_truncated(event, token, UNIQ_T(t, UNIQ), what, format) + +#define _log_event_line(event, event_u, line, ...) \ + ({ \ + UdevEvent *event_u = ASSERT_PTR(event); \ + \ + event_u->trace ? \ + log_udev_rule_line_full( \ + event_u->dev, line, \ + LOG_DEBUG, 0, __VA_ARGS__) : \ + 0; \ + }) + +#define log_event_line(event, line, ...) \ + _log_event_line(event, UNIQ_T(e, UNIQ), line, __VA_ARGS__) + +/* Mainly used when parsing .rules files. */ +#define log_file_full_errno_zerook(...) \ + log_udev_rule_file_full(NULL, __VA_ARGS__) + +#define log_file_error(file, line_nr, ...) \ + log_file_full_errno_zerook(file, line_nr, LOG_ERR, 0, __VA_ARGS__) + +#define log_line_full_errno_zerook(...) \ + log_udev_rule_line_full(NULL, __VA_ARGS__) + +#define log_line_full_errno(line, level, error, ...) \ + log_udev_rule_line_full(NULL, line, level, error, __VA_ARGS__) + #define log_line_full(line, level, ...) (void) log_line_full_errno_zerook(line, level, 0, __VA_ARGS__) #define log_line_debug(line, ...) log_line_full(line, LOG_DEBUG, __VA_ARGS__) @@ -312,38 +408,6 @@ struct UdevRules { "Invalid value \"%s\" for %s (char %zu: %s), ignoring.", \ value, key, offset, hint) -static void log_unknown_owner(sd_device *dev, UdevRuleLine *line, int error, const char *entity, const char *name) { - assert(line); - ASSERT_NON_ZERO(error); - - if (IN_SET(abs(error), ENOENT, ESRCH)) - log_udev_rule_internal(dev, line->rule_file, line->line_number, LOG_ERR, error, - "Unknown %s '%s', ignoring.", entity, name); - else - log_udev_rule_internal(dev, line->rule_file, line->line_number, LOG_ERR, error, - "Failed to resolve %s '%s', ignoring: %m", entity, name); -} - -static void log_event_truncated( - sd_device *dev, - UdevRuleToken *token, - const char *what, - const char *format, - const char *key, - bool is_match) { - - if (is_match) - log_event_debug(dev, token, - "The %s is truncated while substituting into '%s', " - "assuming the %s key does not match.", - what, format, key); - else - log_event_warning(dev, token, - "The %s is truncated while substituting into '%s', " - "refusing to apply the %s key.", - what, format, key); -} - /*** Other functions ***/ static UdevRuleToken* udev_rule_token_free(UdevRuleToken *token) { @@ -375,6 +439,7 @@ static UdevRuleLine* udev_rule_line_free(UdevRuleLine *rule_line) { LIST_REMOVE(rule_lines, rule_line->rule_file->rule_lines, rule_line); free(rule_line->line); + free(rule_line->line_for_logging); return mfree(rule_line); } @@ -403,8 +468,8 @@ UdevRules* udev_rules_free(UdevRules *rules) { LIST_FOREACH(rule_files, i, rules->rule_files) udev_rule_file_free(i); - hashmap_free_free_key(rules->known_users); - hashmap_free_free_key(rules->known_groups); + hashmap_free(rules->known_users); + hashmap_free(rules->known_groups); hashmap_free(rules->stats_by_path); return mfree(rules); } @@ -425,20 +490,28 @@ static int rule_resolve_user(UdevRuleLine *rule_line, const char *name, uid_t *r return 0; } - r = get_user_creds(&name, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); - if (r < 0) { - log_unknown_owner(NULL, rule_line, r, "user", name); - *ret = UID_INVALID; - return 0; - } + r = get_user_creds( + &name, + &uid, + /* ret_gid = */ NULL, + /* ret_home = */ NULL, + /* ret_shell = */ NULL, + USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + return log_line_error_errno(rule_line, r, "Unknown user '%s', ignoring.", name); + if (r < 0) + return log_line_error_errno(rule_line, r, "Failed to resolve user '%s', ignoring: %m", name); + if (!uid_is_system(uid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "User '%s' is not a system user (UID="UID_FMT"), ignoring.", name, uid); n = strdup(name); if (!n) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(known_users, &string_hash_ops, n, UID_TO_PTR(uid)); + r = hashmap_ensure_put(known_users, &string_hash_ops_free, n, UID_TO_PTR(uid)); if (r < 0) - return r; + return log_oom(); TAKE_PTR(n); *ret = uid; @@ -462,19 +535,21 @@ static int rule_resolve_group(UdevRuleLine *rule_line, const char *name, gid_t * } r = get_group_creds(&name, &gid, USER_CREDS_ALLOW_MISSING); - if (r < 0) { - log_unknown_owner(NULL, rule_line, r, "group", name); - *ret = GID_INVALID; - return 0; - } + if (r == -ESRCH) + return log_line_error_errno(rule_line, r, "Unknown group '%s', ignoring.", name); + if (r < 0) + return log_line_error_errno(rule_line, r, "Failed to resolve group '%s', ignoring: %m", name); + if (!gid_is_system(gid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "Group '%s' is not a system group (GID="GID_FMT"), ignoring.", name, gid); n = strdup(name); if (!n) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(known_groups, &string_hash_ops, n, GID_TO_PTR(gid)); + r = hashmap_ensure_put(known_groups, &string_hash_ops_free, n, GID_TO_PTR(gid)); if (r < 0) - return r; + return log_oom(); TAKE_PTR(n); *ret = gid; @@ -495,7 +570,15 @@ static bool type_has_nulstr_value(UdevRuleTokenType type) { return type < TK_M_TEST || type == TK_M_RESULT; } -static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, UdevRuleOperatorType op, char *value, void *data, bool is_case_insensitive) { +static int rule_line_add_token( + UdevRuleLine *rule_line, + UdevRuleTokenType type, + UdevRuleOperatorType op, + char *value, + void *data, + bool is_case_insensitive, + const char *token_str) { + _cleanup_(udev_rule_token_freep) UdevRuleToken *token = NULL; UdevRuleMatchType match_type = _MATCH_TYPE_INVALID; UdevRuleSubstituteType subst_type = _SUBST_TYPE_INVALID; @@ -583,6 +666,7 @@ static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, .data = data, .match_type = match_type, .attr_subst_type = subst_type, + .token_str = token_str, .rule_line = rule_line, }; @@ -630,7 +714,15 @@ static int check_attr_format_and_warn(UdevRuleLine *line, const char *key, const return 0; } -static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, UdevRuleOperatorType op, char *value, bool is_case_insensitive) { +static int parse_token( + UdevRuleLine *rule_line, + const char *key, + char *attr, + UdevRuleOperatorType op, + char *value, + bool is_case_insensitive, + const char *token_str) { + ResolveNameTiming resolve_name_timing = LINE_GET_RULES(rule_line)->resolve_name_timing; bool is_match = IN_SET(op, OP_MATCH, OP_NOMATCH); int r; @@ -648,29 +740,29 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DEVPATH")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "KERNEL")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SYMLINK")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "NAME")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -690,9 +782,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude "Ignoring NAME=\"\", as udev will not delete any network interfaces."); check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ENV")) { if (isempty(attr)) return log_line_invalid_attr(rule_line, key); @@ -710,15 +802,15 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "CONST")) { if (isempty(attr) || !STR_IN_SET(attr, "arch", "virt")) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "TAG")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -730,9 +822,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SUBSYSTEM")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -742,14 +834,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (STR_IN_SET(value, "bus", "class")) log_line_warning(rule_line, "\"%s\" must be specified as \"subsystem\".", value); - r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DRIVER")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ATTR")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -763,9 +855,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "SYSCTL")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -779,30 +871,30 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "KERNELS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SUBSYSTEMS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DRIVERS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ATTRS")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -815,14 +907,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (strstr(attr, "../")) log_line_warning(rule_line, "Direct reference to parent sysfs directory, may break in future kernels."); - r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "TAGS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "TEST")) { mode_t mode = MODE_INVALID; @@ -837,7 +929,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (is_case_insensitive) return log_line_invalid_prefix(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode), is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode), is_case_insensitive, token_str); } else if (streq(key, "PROGRAM")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -849,7 +941,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (is_case_insensitive) return log_line_invalid_prefix(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL, /* is_case_insensitive */ false); + r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL, /* is_case_insensitive */ false, token_str); } else if (streq(key, "IMPORT")) { if (isempty(attr)) return log_line_invalid_attr(rule_line, key); @@ -862,16 +954,16 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude return log_line_invalid_prefix(rule_line, key); if (streq(attr, "file")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "program")) { UdevBuiltinCommand cmd; cmd = udev_builtin_lookup(value); if (cmd >= 0) { log_line_debug(rule_line, "Found builtin command '%s' for %s, replacing attribute.", value, key); - r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else if (streq(attr, "builtin")) { UdevBuiltinCommand cmd; @@ -879,13 +971,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (cmd < 0) return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), "Unknown builtin command: %s", value); - r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else if (streq(attr, "db")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "cmdline")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "parent")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL, /* is_case_insensitive = */ false, token_str); else return log_line_invalid_attr(rule_line, key); } else if (streq(key, "RESULT")) { @@ -894,7 +986,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "OPTIONS")) { char *tmp; @@ -905,25 +997,27 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (op == OP_ADD) op = OP_ASSIGN; - if (streq(value, "string_escape=none")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL, /* is_case_insensitive = */ false); + if (streq(value, "dump")) + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DUMP, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); + else if (streq(value, "string_escape=none")) + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "string_escape=replace")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "db_persist")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "watch")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1), /* is_case_insensitive = */ false, token_str); else if (streq(value, "nowatch")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0), /* is_case_insensitive = */ false, token_str); else if ((tmp = startswith(value, "static_node="))) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL, /* is_case_insensitive = */ false, token_str); else if ((tmp = startswith(value, "link_priority="))) { int prio; r = safe_atoi(tmp, &prio); if (r < 0) return log_line_error_errno(rule_line, r, "Failed to parse link priority '%s': %m", tmp); - r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio), /* is_case_insensitive = */ false, token_str); } else if ((tmp = startswith(value, "log_level="))) { int level; @@ -934,7 +1028,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (level < 0) return log_line_error_errno(rule_line, level, "Failed to parse log level '%s': %m", tmp); } - r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level), /* is_case_insensitive = */ false, token_str); } else { log_line_warning(rule_line, "Invalid value for OPTIONS key, ignoring: '%s'", value); return 0; @@ -951,18 +1045,23 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - if (parse_uid(value, &uid) >= 0) - r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false); - else if (resolve_name_timing == RESOLVE_NAME_EARLY && + if (parse_uid(value, &uid) >= 0) { + if (!uid_is_system(uid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "UID="UID_FMT" is not in the system user range, ignoring.", uid); + + r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str); + } else if (resolve_name_timing == RESOLVE_NAME_EARLY && rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { + r = rule_resolve_user(rule_line, value, &uid); if (r < 0) - return log_line_error_errno(rule_line, r, "Failed to resolve user name '%s': %m", value); + return r; - r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str); } else if (resolve_name_timing != RESOLVE_NAME_NEVER) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else { log_line_debug(rule_line, "User name resolution is disabled, ignoring %s=\"%s\".", key, value); return 0; @@ -979,18 +1078,23 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - if (parse_gid(value, &gid) >= 0) - r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false); - else if (resolve_name_timing == RESOLVE_NAME_EARLY && + if (parse_gid(value, &gid) >= 0) { + if (!gid_is_system(gid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "GID="GID_FMT" is not in the system group range, ignoring.", gid); + + r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str); + } else if (resolve_name_timing == RESOLVE_NAME_EARLY && rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { + r = rule_resolve_group(rule_line, value, &gid); if (r < 0) - return log_line_error_errno(rule_line, r, "Failed to resolve group name '%s': %m", value); + return r; - r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str); } else if (resolve_name_timing != RESOLVE_NAME_NEVER) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else { log_line_debug(rule_line, "Resolving group name is disabled, ignoring GROUP=\"%s\".", value); return 0; @@ -1008,10 +1112,10 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude } if (parse_mode(value, &mode) >= 0) - r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode), /* is_case_insensitive = */ false, token_str); else { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL, /* is_case_insensitive = */ false, token_str); } } else if (streq(key, "SECLABEL")) { if (isempty(attr)) @@ -1024,13 +1128,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr, /* is_case_insensitive = */ false, token_str); } else if (streq(key, "RUN")) { if (is_match || op == OP_REMOVE) return log_line_invalid_op(rule_line, key); check_value_format_and_warn(rule_line, key, value, true); if (!attr || streq(attr, "program")) - r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "builtin")) { UdevBuiltinCommand cmd; @@ -1038,7 +1142,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (cmd < 0) return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), "Unknown builtin command '%s', ignoring.", value); - r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else return log_line_invalid_attr(rule_line, key); } else if (streq(key, "GOTO")) { @@ -1298,32 +1402,30 @@ static void sort_tokens(UdevRuleLine *rule_line) { } } -static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr, bool extra_checks) { +static int rule_add_line(UdevRuleFile *rule_file, const char *line, unsigned line_nr, bool extra_checks) { _cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL; - _cleanup_free_ char *line = NULL; char *p; int r; assert(rule_file); - assert(line_str); + assert(line); - if (isempty(line_str)) + if (isempty(line)) return 0; - line = strdup(line_str); - if (!line) - return log_oom(); - rule_line = new(UdevRuleLine, 1); if (!rule_line) return log_oom(); *rule_line = (UdevRuleLine) { - .line = TAKE_PTR(line), + .line = strdup(line), + .line_for_logging = strdup(line), .line_number = line_nr, - .rule_file = rule_file, }; + if (!rule_line->line || !rule_line->line_for_logging) + return log_oom(); + rule_line->rule_file = rule_file; LIST_APPEND(rule_lines, rule_file->rule_lines, rule_line); for (p = rule_line->line; !isempty(p); ) { @@ -1340,7 +1442,9 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned if (r == 0) break; - r = parse_token(rule_line, key, attr, op, value, is_case_insensitive); + char *token_str = rule_line->line_for_logging + (key - rule_line->line); + token_str[p - key] = '\0'; + r = parse_token(rule_line, key, attr, op, value, is_case_insensitive, token_str); if (r < 0) return r; } @@ -1540,10 +1644,8 @@ static void udev_check_rule_line(UdevRuleLine *line) { int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) { _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL; - _cleanup_free_ char *continuation = NULL, *name = NULL; + _cleanup_free_ char *name = NULL; _cleanup_fclose_ FILE *f = NULL; - bool ignore_line = false; - unsigned line_nr = 0; struct stat st; int r; @@ -1594,6 +1696,9 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che LIST_APPEND(rule_files, rules->rule_files, rule_file); + _cleanup_free_ char *continuation = NULL; + unsigned line_nr = 0, current_line_nr = 0; + bool ignore_line = false; for (;;) { _cleanup_free_ char *buf = NULL; size_t len; @@ -1605,7 +1710,10 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che if (r == 0) break; - line_nr++; + current_line_nr++; + if (!continuation) + line_nr = current_line_nr; + line = skip_leading_chars(buf, NULL); /* Lines beginning with '#' are ignored regardless of line continuation. */ @@ -1687,16 +1795,26 @@ UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) { return rules; } -int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) { +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; - _cleanup_strv_free_ char **files = NULL; + _cleanup_strv_free_ char **files = NULL, **directories = NULL; int r; rules = udev_rules_new(resolve_name_timing); if (!rules) return -ENOMEM; - r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS); + if (!strv_isempty(extra)) { + directories = strv_copy(extra); + if (!directories) + return -ENOMEM; + } + + r = strv_extend_strv(&directories, CONF_PATHS_STRV("udev/rules.d"), /* filter_duplicates = */ false); + if (r < 0) + return r; + + r = conf_files_list_strv(&files, ".rules", NULL, 0, (const char* const*) directories); if (r < 0) return log_debug_errno(r, "Failed to enumerate rules files: %m"); @@ -1731,10 +1849,59 @@ bool udev_rules_should_reload(UdevRules *rules) { return false; } -static bool token_match_string(UdevRuleToken *token, const char *str) { +static bool apply_format_full( + UdevEvent *event, + UdevRuleToken *token, + const char *format, + char *result, + size_t result_size, + bool replace_whitespace, + const char *what) { + + assert(event); + assert(token); + assert(format); + assert(result); + assert(what); + + bool truncated = false; + (void) udev_event_apply_format(event, format, result, result_size, replace_whitespace, &truncated); + if (truncated) { + log_event_truncated(event, token, what, format); + return false; + } + + if (event->trace && !streq(format, result)) + log_event_trace(event, token, "Format substitution: \"%s\" -> \"%s\"", format, result); + + return true; +} + +static bool apply_format_value( + UdevEvent *event, + UdevRuleToken *token, + char *result, + size_t result_size, + const char *what) { + + return apply_format_full(event, token, token->value, result, result_size, /* replace_whitespace = */ false, what); +} + +static bool apply_format_attr( + UdevEvent *event, + UdevRuleToken *token, + char *result, + size_t result_size, + const char *what) { + + return apply_format_full(event, token, token->data, result, result_size, /* replace_whitespace = */ false, what); +} + +static bool token_match_string(UdevEvent *event, UdevRuleToken *token, const char *str, bool log_result) { const char *value; bool match = false, case_insensitive; + assert(event); assert(token); assert(token->value); assert(token->type < _TK_M_MAX); @@ -1783,13 +1950,56 @@ static bool token_match_string(UdevRuleToken *token, const char *str) { assert_not_reached(); } - return token->op == (match ? OP_MATCH : OP_NOMATCH); + bool result = token->op == (match ? OP_MATCH : OP_NOMATCH); + + if (event->trace) + switch (token->match_type & _MATCH_TYPE_MASK) { + case MATCH_TYPE_EMPTY: + log_event_trace(event, token, "String \"%s\" is%s empty%s", + strempty(str), match ? "" : " not", + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + case MATCH_TYPE_SUBSYSTEM: + log_event_trace(event, token, + "String \"%s\" matches %s of \"subsystem\", \"class\", or \"bus\"%s", + strempty(str), match ? "one" : "neither", + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + case MATCH_TYPE_PLAIN_WITH_EMPTY: + case MATCH_TYPE_PLAIN: + case MATCH_TYPE_GLOB_WITH_EMPTY: + case MATCH_TYPE_GLOB: { + _cleanup_free_ char *joined = NULL; + unsigned c = 0; + + if (IN_SET(token->match_type & _MATCH_TYPE_MASK, MATCH_TYPE_PLAIN_WITH_EMPTY, MATCH_TYPE_GLOB_WITH_EMPTY)) { + (void) strextend_with_separator(&joined, ", ", "\"\""); + c++; + } + + NULSTR_FOREACH(i, value) { + (void) strextendf_with_separator(&joined, ", ", "\"%s\"", i); + c++; + } + + assert(c > 0); + log_event_trace(event, token, "String \"%s\" %s %s%s", + strempty(str), + match ? (c > 1 ? "matches one of" : "matches") : (c > 1 ? "matches neither of" : "does not match"), + strempty(joined), + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + } + default: + assert_not_reached(); + } + + return result; } static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *event) { char nbuf[UDEV_NAME_SIZE], vbuf[UDEV_NAME_SIZE]; const char *name, *value; - bool truncated; assert(token); assert(IN_SET(token->type, TK_M_ATTR, TK_M_PARENTS_ATTR)); @@ -1800,12 +2010,8 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev switch (token->attr_subst_type) { case SUBST_TYPE_FORMAT: - (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysfs attribute name", name, - token->type == TK_M_ATTR ? "ATTR" : "ATTRS", /* is_match = */ true); + if (!apply_format_attr(event, token, nbuf, sizeof(nbuf), "sysfs attribute name")) return false; - } name = nbuf; _fallthrough_; @@ -1819,7 +2025,7 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev value = delete_trailing_chars(vbuf, NULL); } - return token_match_string(token, value); + return token_match_string(event, token, value, /* log_result = */ true); case SUBST_TYPE_SUBSYS: if (udev_resolve_subsys_kernel(name, vbuf, sizeof(vbuf), true) < 0) @@ -1829,7 +2035,7 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev if (FLAGS_SET(token->match_type, MATCH_REMOVE_TRAILING_WHITESPACE)) delete_trailing_chars(vbuf, NULL); - return token_match_string(token, vbuf); + return token_match_string(event, token, vbuf, /* log_result = */ true); default: assert_not_reached(); @@ -1881,30 +2087,6 @@ static int get_property_from_string(char *line, char **ret_key, char **ret_value return 1; } -static int import_parent_into_properties(sd_device *dev, const char *filter) { - sd_device *parent; - int r; - - assert(dev); - assert(filter); - - r = sd_device_get_parent(dev, &parent); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - FOREACH_DEVICE_PROPERTY(parent, key, val) { - if (fnmatch(filter, key, 0) != 0) - continue; - r = device_add_property(dev, key, val); - if (r < 0) - return r; - } - - return 1; -} - static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) { _cleanup_closedir_ DIR *dir = NULL; char buf[UDEV_PATH_SIZE], *p; @@ -1949,7 +2131,7 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) { return -ENOENT; } -static size_t udev_replace_ifname(char *str) { +static size_t udev_replace_ifname_strict(char *str) { size_t replaced = 0; assert(str); @@ -1965,6 +2147,35 @@ static size_t udev_replace_ifname(char *str) { return replaced; } +static void udev_replace_ifname(UdevEvent *event, UdevRuleToken *token, char *buf) { + assert(event); + assert(token); + assert(buf); + + size_t count; + if (naming_scheme_has(NAMING_REPLACE_STRICTLY)) + count = udev_replace_ifname_strict(buf); + else + count = udev_replace_chars(buf, "/"); + if (count > 0) + log_event_trace(event, token, + "Replaced %zu character(s) from network interface name, results to \"%s\"", + count, buf); +} + +static void udev_replace_chars_and_log(UdevEvent *event, UdevRuleToken *token, char *buf, const char *allow, const char *what) { + assert(event); + assert(token); + assert(buf); + assert(what); + + size_t count = udev_replace_chars(buf, allow); + if (count > 0) + log_event_trace(event, token, + "Replaced %zu character(s) from %s, results to \"%s\"", + count, what, buf); +} + static int udev_rule_apply_token_to_event( UdevRuleToken *token, sd_device *dev, @@ -1987,18 +2198,18 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_action(dev, &a); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get uevent action type: %m"); + return log_event_error_errno(event, token, r, "Failed to get uevent action type: %m"); - return token_match_string(token, device_action_to_string(a)); + return token_match_string(event, token, device_action_to_string(a), /* log_result = */ true); } case TK_M_DEVPATH: { const char *val; r = sd_device_get_devpath(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get devpath: %m"); + return log_event_error_errno(event, token, r, "Failed to get devpath: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_KERNEL: case TK_M_PARENTS_KERNEL: { @@ -2006,23 +2217,25 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_sysname(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get sysname: %m"); + return log_event_error_errno(event, token, r, "Failed to get sysname: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_DEVLINK: FOREACH_DEVICE_DEVLINK(dev, val) - if (token_match_string(token, strempty(startswith(val, "/dev/"))) == (token->op == OP_MATCH)) - return token->op == OP_MATCH; - return token->op == OP_NOMATCH; + if (token_match_string(event, token, strempty(startswith(val, "/dev/")), /* log_result = */ false) == (token->op == OP_MATCH)) + return log_event_result(event, token, token->op == OP_MATCH); + return log_event_result(event, token, token->op == OP_NOMATCH); + case TK_M_NAME: - return token_match_string(token, event->name); + return token_match_string(event, token, event->name, /* log_result = */ true); + case TK_M_ENV: { const char *val = NULL; (void) device_get_property_value_with_fallback(dev, token->data, event->worker ? event->worker->properties : NULL, &val); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_CONST: { const char *val, *k = token->data; @@ -2035,14 +2248,15 @@ static int udev_rule_apply_token_to_event( val = confidential_virtualization_to_string(detect_confidential_virtualization()); else assert_not_reached(); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_TAG: case TK_M_PARENTS_TAG: FOREACH_DEVICE_CURRENT_TAG(dev, val) - if (token_match_string(token, val) == (token->op == OP_MATCH)) - return token->op == OP_MATCH; - return token->op == OP_NOMATCH; + if (token_match_string(event, token, val, /* log_result = */ false) == (token->op == OP_MATCH)) + return log_event_result(event, token, token->op == OP_MATCH); + return log_event_result(event, token, token->op == OP_NOMATCH); + case TK_M_SUBSYSTEM: case TK_M_PARENTS_SUBSYSTEM: { const char *val; @@ -2051,9 +2265,9 @@ static int udev_rule_apply_token_to_event( if (r == -ENOENT) val = NULL; else if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get subsystem: %m"); + return log_event_error_errno(event, token, r, "Failed to get subsystem: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_DRIVER: case TK_M_PARENTS_DRIVER: { @@ -2063,41 +2277,35 @@ static int udev_rule_apply_token_to_event( if (r == -ENOENT) val = NULL; else if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get driver: %m"); + return log_event_error_errno(event, token, r, "Failed to get driver: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_ATTR: case TK_M_PARENTS_ATTR: return token_match_attr(token, dev, event); + case TK_M_SYSCTL: { _cleanup_free_ char *value = NULL; char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ true); + if (!apply_format_attr(event, token, buf, sizeof(buf), "sysctl entry name")) return false; - } r = sysctl_read(sysctl_normalize(buf), &value); if (r < 0 && r != -ENOENT) - return log_event_error_errno(dev, token, r, "Failed to read sysctl '%s': %m", buf); + return log_event_error_errno(event, token, r, "Failed to read sysctl \"%s\": %m", buf); - return token_match_string(token, strstrip(value)); + return token_match_string(event, token, strstrip(value), /* log_result = */ true); } case TK_M_TEST: { mode_t mode = PTR_TO_MODE(token->data); char buf[UDEV_PATH_SIZE]; struct stat statbuf; - bool match, truncated; + bool match; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "file name", token->value, "TEST", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "file name")) return false; - } if (!path_is_absolute(buf) && udev_resolve_subsys_kernel(buf, buf, sizeof(buf), false) < 0) { @@ -2106,81 +2314,73 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_syspath(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get syspath: %m"); + return log_event_error_errno(event, token, r, "Failed to get syspath: %m"); + bool truncated; strscpy_full(tmp, sizeof(tmp), buf, &truncated); assert(!truncated); strscpyl_full(buf, sizeof(buf), &truncated, val, "/", tmp, NULL); if (truncated) - return false; + return log_event_result(event, token, false); } r = attr_subst_subdir(buf); if (r == -ENOENT) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to test for the existence of '%s': %m", buf); + return log_event_error_errno(event, token, r, "Failed to test for the existence of \"%s\": %m", buf); - if (stat(buf, &statbuf) < 0) - return token->op == OP_NOMATCH; + if (stat(buf, &statbuf) < 0) { + if (errno != ENOENT) + log_event_warning_errno(event, token, errno, "Failed to stat \"%s\", ignoring: %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); + } if (mode == MODE_INVALID) - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); match = (statbuf.st_mode & mode) > 0; - return token->op == (match ? OP_MATCH : OP_NOMATCH); + return log_event_result(event, token, token->op == (match ? OP_MATCH : OP_NOMATCH)); } case TK_M_PROGRAM: { char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE]; - bool truncated; - size_t count; event->program_result = mfree(event->program_result); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, "PROGRAM", /* is_match = */ true); + + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) return false; - } - log_event_debug(dev, token, "Running PROGRAM=\"%s\"", buf); + log_event_debug(event, token, "Running command \"%s\"", buf); r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof(result), NULL); if (r != 0) { if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to execute \"%s\": %m", buf); + log_event_warning_errno(event, token, r, "Failed to execute \"%s\": %m", buf); else /* returned value is positive when program fails */ - log_event_debug(dev, token, "Command \"%s\" returned %d (error)", buf, r); - return token->op == OP_NOMATCH; + log_event_debug(event, token, "Command \"%s\" returned %d (error)", buf, r); + return log_event_result(event, token, token->op == OP_NOMATCH); } delete_trailing_chars(result, "\n"); - count = udev_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) in result of \"%s\"", - count, buf); + udev_replace_chars_and_log(event, token, result, UDEV_ALLOWED_CHARS_INPUT, "command output"); event->program_result = strdup(result); - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_FILE: { _cleanup_fclose_ FILE *f = NULL; char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "file name to be imported", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "file name to be imported")) return false; - } - log_event_debug(dev, token, "Importing properties from '%s'", buf); + log_event_debug(event, token, "Importing properties from \"%s\"", buf); f = fopen(buf, "re"); if (!f) { if (errno != ENOENT) - return log_event_error_errno(dev, token, errno, "Failed to open '%s': %m", buf); - return token->op == OP_NOMATCH; + return log_event_error_errno(event, token, errno, "Failed to open \"%s\": %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); } for (;;) { @@ -2189,16 +2389,16 @@ static int udev_rule_apply_token_to_event( r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) { - log_event_debug_errno(dev, token, r, "Failed to read '%s', ignoring: %m", buf); - return token->op == OP_NOMATCH; + log_event_debug_errno(event, token, r, "Failed to read \"%s\", ignoring: %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); } if (r == 0) - break; + return log_event_result(event, token, token->op == OP_MATCH); r = get_property_from_string(line, &key, &value); if (r < 0) { - log_event_debug_errno(dev, token, r, - "Failed to parse key and value from '%s', ignoring: %m", + log_event_debug_errno(event, token, r, + "Failed to parse key and value from \"%s\", ignoring: %m", line); continue; } @@ -2207,54 +2407,55 @@ static int udev_rule_apply_token_to_event( r = device_add_property(dev, key, value); if (r < 0) - return log_event_error_errno(dev, token, r, + return log_event_error_errno(event, token, r, "Failed to add property %s=%s: %m", key, value); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } - return token->op == OP_MATCH; + assert_not_reached(); } case TK_M_IMPORT_PROGRAM: { _cleanup_strv_free_ char **lines = NULL; char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) return false; - } - log_event_debug(dev, token, "Importing properties from results of '%s'", buf); + log_event_debug(event, token, "Importing properties from results of \"%s\"", buf); r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof result, &truncated); if (r != 0) { if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to execute '%s', ignoring: %m", buf); + log_event_warning_errno(event, token, r, "Failed to execute \"%s\", ignoring: %m", buf); else /* returned value is positive when program fails */ - log_event_debug(dev, token, "Command \"%s\" returned %d (error), ignoring", buf, r); - return token->op == OP_NOMATCH; + log_event_debug(event, token, "Command \"%s\" returned %d (error), ignoring", buf, r); + return log_event_result(event, token, token->op == OP_NOMATCH); } if (truncated) { - bool found = false; + log_event_debug(event, token, "Result of \"%s\" is too long and truncated, ignoring the last line of the result.", buf); /* Drop the last line. */ + bool found = false; for (char *p = PTR_SUB1(buf + strlen(buf), buf); p; p = PTR_SUB1(p, buf)) if (strchr(NEWLINE, *p)) { *p = '\0'; found = true; - } else if (found) break; + } + if (!found) + buf[0] = '\0'; } r = strv_split_newlines_full(&lines, result, EXTRACT_RETAIN_ESCAPE); if (r == -ENOMEM) return log_oom(); if (r < 0) { - log_event_warning_errno(dev, token, r, + log_event_warning_errno(event, token, r, "Failed to extract lines from result of command \"%s\", ignoring: %m", buf); - return false; + return log_event_result(event, token, false); } STRV_FOREACH(line, lines) { @@ -2262,8 +2463,8 @@ static int udev_rule_apply_token_to_event( r = get_property_from_string(*line, &key, &value); if (r < 0) { - log_event_debug_errno(dev, token, r, - "Failed to parse key and value from '%s', ignoring: %m", + log_event_debug_errno(event, token, r, + "Failed to parse key and value from \"%s\", ignoring: %m", *line); continue; } @@ -2272,123 +2473,168 @@ static int udev_rule_apply_token_to_event( r = device_add_property(dev, key, value); if (r < 0) - return log_event_error_errno(dev, token, r, + return log_event_error_errno(event, token, r, "Failed to add property %s=%s: %m", key, value); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_BUILTIN: { UdevBuiltinCommand cmd = PTR_TO_UDEV_BUILTIN_CMD(token->data); assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX); unsigned mask = 1U << (int) cmd; char buf[UDEV_LINE_SIZE]; - bool truncated; if (udev_builtin_run_once(cmd)) { /* check if we ran already */ if (event->builtin_run & mask) { - log_event_debug(dev, token, "Skipping builtin '%s' in IMPORT key", + log_event_debug(event, token, "Builtin command \"%s\" has already run, skipping.", udev_builtin_name(cmd)); /* return the result from earlier run */ - return token->op == (event->builtin_ret & mask ? OP_NOMATCH : OP_MATCH); + return log_event_result(event, token, token->op == (event->builtin_ret & mask ? OP_NOMATCH : OP_MATCH)); } /* mark as ran */ event->builtin_run |= mask; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "builtin command", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "builtin command")) return false; - } - log_event_debug(dev, token, "Importing properties from results of builtin command '%s'", buf); + log_event_debug(event, token, "Importing properties from results of builtin command \"%s\".", buf); r = udev_builtin_run(event, cmd, buf); if (r < 0) { /* remember failure */ - log_event_debug_errno(dev, token, r, "Failed to run builtin '%s': %m", buf); + log_event_debug_errno(event, token, r, "Failed to run builtin \"%s\": %m", buf); event->builtin_ret |= mask; } - return token->op == (r >= 0 ? OP_MATCH : OP_NOMATCH); + return log_event_result(event, token, token->op == (r >= 0 ? OP_MATCH : OP_NOMATCH)); } case TK_M_IMPORT_DB: { const char *val; if (!event->dev_db_clone) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); r = sd_device_get_property_value(event->dev_db_clone, token->value, &val); if (r == -ENOENT) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to get property '%s' from database: %m", + return log_event_error_errno(event, token, r, + "Failed to get property \"%s\" from database: %m", token->value); r = device_add_property(dev, token->value, val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", token->value, val); - return token->op == OP_MATCH; + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_CMDLINE: { _cleanup_free_ char *value = NULL; r = proc_cmdline_get_key(token->value, PROC_CMDLINE_VALUE_OPTIONAL|PROC_CMDLINE_IGNORE_EFI_OPTIONS, &value); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to read '%s' option from /proc/cmdline: %m", + return log_event_error_errno(event, token, r, + "Failed to read \"%s\" option from /proc/cmdline: %m", token->value); if (r == 0) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); - r = device_add_property(dev, token->value, value ?: "1"); + const char *val = value ?: "1"; + r = device_add_property(dev, token->value, val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", - token->value, value ?: "1"); - return token->op == OP_MATCH; + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", + token->value, val); + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_PARENT: { char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "property name", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "property name")) return false; - } - r = import_parent_into_properties(dev, buf); + sd_device *parent; + r = sd_device_get_parent(dev, &parent); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to import properties '%s' from parent: %m", - buf); - return token->op == (r > 0 ? OP_MATCH : OP_NOMATCH); + return log_event_error_errno(event, token, r, "Failed to get parent device: %m"); + + bool have = false; + FOREACH_DEVICE_PROPERTY(parent, key, val) { + if (fnmatch(buf, key, 0) != 0) + continue; + + r = device_add_property(dev, key, val); + if (r < 0) + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", key, val); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, val); + have = true; + } + + return log_event_result(event, token, token->op == (have ? OP_MATCH : OP_NOMATCH)); } case TK_M_RESULT: - return token_match_string(token, event->program_result); + return token_match_string(event, token, event->program_result, /* log_result = */ true); + + case TK_A_OPTIONS_DUMP: { + log_event_info(event, token, "Dumping current state:"); + + if (event->event_mode == EVENT_UDEV_WORKER) { + _cleanup_(memstream_done) MemStream m = {}; + FILE *f = memstream_init(&m); + if (!f) + return log_oom(); + + dump_event(event, f); + + _cleanup_free_ char *buf = NULL; + r = memstream_finalize(&m, &buf, NULL); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to finalize memory stream, ignoring: %m"); + else + log_info("%s", buf); + } else { + puts("============================"); + dump_event(event, NULL); + puts("============================"); + } + + log_event_info(event, token, "DONE"); + return true; + } case TK_A_OPTIONS_STRING_ESCAPE_NONE: event->esc = ESCAPE_NONE; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_STRING_ESCAPE_REPLACE: event->esc = ESCAPE_REPLACE; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_DB_PERSIST: device_set_db_persist(dev); - break; + return log_event_done(event, token); + case TK_A_OPTIONS_INOTIFY_WATCH: if (event->inotify_watch_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->inotify_watch_final = true; event->inotify_watch = token->data; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_DEVLINK_PRIORITY: device_set_devlink_priority(dev, PTR_TO_INT(token->data)); - break; + return log_event_done(event, token); + case TK_A_OPTIONS_LOG_LEVEL: { int level = PTR_TO_INT(token->data); @@ -2400,7 +2646,7 @@ static int udev_rule_apply_token_to_event( else { _cleanup_free_ char *level_str = NULL; (void) log_level_to_string_alloc(level, &level_str); - log_event_debug(dev, token, "Running in test mode, skipping changing maximum log level to %s.", strna(level_str)); + log_event_debug(event, token, "Running in test mode, skipping changing maximum log level to %s.", strna(level_str)); } if (level == LOG_DEBUG && !event->log_level_was_debug) { @@ -2410,120 +2656,130 @@ static int udev_rule_apply_token_to_event( event->log_level_was_debug = true; } - break; + return log_event_done(event, token); } case TK_A_OWNER: { char owner[UDEV_NAME_SIZE]; const char *ow = owner; - bool truncated; if (event->owner_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->owner_final = true; - (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "user name", token->value, "OWNER", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, owner, sizeof(owner), "user name")) + return true; - r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); - if (r < 0) - log_unknown_owner(dev, token->rule_line, r, "user", owner); - else - log_event_debug(dev, token, "OWNER %s(%u)", owner, event->uid); - break; + uid_t uid; + r = get_user_creds( + &ow, + &uid, + /* ret_gid = */ NULL, + /* ret_home = */ NULL, + /* ret_shell = */ NULL, + USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + log_event_error_errno(event, token, r, "Unknown user \"%s\", ignoring.", owner); + else if (r < 0) + log_event_error_errno(event, token, r, "Failed to resolve user \"%s\", ignoring: %m", owner); + else if (!uid_is_system(uid)) + log_event_error(event, token, "User \"%s\" is not a system user (UID="UID_FMT"), ignoring.", owner, uid); + else { + event->uid = uid; + log_event_debug(event, token, "Set owner: %s(%u)", owner, event->uid); + } + return true; } case TK_A_GROUP: { char group[UDEV_NAME_SIZE]; const char *gr = group; - bool truncated; if (event->group_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->group_final = true; - (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "group name", token->value, "GROUP", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, group, sizeof(group), "group name")) + return true; - r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING); - if (r < 0) - log_unknown_owner(dev, token->rule_line, r, "group", group); - else - log_event_debug(dev, token, "GROUP %s(%u)", group, event->gid); - break; + gid_t gid; + r = get_group_creds(&gr, &gid, USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + log_event_error_errno(event, token, r, "Unknown group \"%s\", ignoring.", group); + else if (r < 0) + log_event_error_errno(event, token, r, "Failed to resolve group \"%s\", ignoring: %m", group); + else if (!gid_is_system(gid)) + log_event_error(event, token, "Group \"%s\" is not a system group (GID="GID_FMT"), ignoring.", group, gid); + else { + event->gid = gid; + log_event_debug(event, token, "Set group: %s(%u)", group, event->gid); + } + return true; } case TK_A_MODE: { char mode_str[UDEV_NAME_SIZE]; - bool truncated; if (event->mode_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->mode_final = true; - (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "mode", token->value, "MODE", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, mode_str, sizeof(mode_str), "mode")) + return true; r = parse_mode(mode_str, &event->mode); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to parse mode '%s', ignoring: %m", mode_str); + log_event_error_errno(event, token, r, "Failed to parse mode \"%s\", ignoring: %m", mode_str); else - log_event_debug(dev, token, "MODE %#o", event->mode); - break; + log_event_debug(event, token, "Set mode: %#o", event->mode); + return true; } case TK_A_OWNER_ID: if (event->owner_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->owner_final = true; - if (!token->data) - break; - event->uid = PTR_TO_UID(token->data); - log_event_debug(dev, token, "OWNER %u", event->uid); - break; + + event->uid = PTR_TO_UID(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set owner ID: %u", event->uid); + return true; + case TK_A_GROUP_ID: if (event->group_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->group_final = true; - if (!token->data) - break; - event->gid = PTR_TO_GID(token->data); - log_event_debug(dev, token, "GROUP %u", event->gid); - break; + + event->gid = PTR_TO_GID(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set group ID: %u", event->gid); + return true; + case TK_A_MODE_ID: if (event->mode_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->mode_final = true; - if (!token->data) - break; - event->mode = PTR_TO_MODE(token->data); - log_event_debug(dev, token, "MODE %#o", event->mode); - break; + + event->mode = PTR_TO_MODE(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set mode: %#o", event->mode); + return true; + case TK_A_SECLABEL: { _cleanup_free_ char *name = NULL, *label = NULL; char label_str[UDEV_LINE_SIZE] = {}; - bool truncated; name = strdup(token->data); if (!name) return log_oom(); - (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "security label", token->value, "SECLABEL", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, label_str, sizeof(label_str), "security label")) + return true; if (!isempty(label_str)) label = strdup(label_str); @@ -2533,163 +2789,141 @@ static int udev_rule_apply_token_to_event( return log_oom(); if (token->op == OP_ASSIGN) - ordered_hashmap_clear_free_free(event->seclabel_list); + ordered_hashmap_clear(event->seclabel_list); - r = ordered_hashmap_ensure_put(&event->seclabel_list, NULL, name, label); + r = ordered_hashmap_ensure_put(&event->seclabel_list, &trivial_hash_ops_free_free, name, label); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to store SECLABEL{%s}='%s': %m", name, label); + return log_event_error_errno(event, token, r, "Failed to store security label \"%s=%s\": %m", name, label); - log_event_debug(dev, token, "SECLABEL{%s}='%s'", name, label); + log_event_debug(event, token, "Set security label: %s=%s", name, label); TAKE_PTR(name); TAKE_PTR(label); - break; + return true; } case TK_A_ENV: { const char *val, *name = token->data; char value_new[UDEV_NAME_SIZE], *p = value_new; - size_t count, l = sizeof(value_new); - bool truncated; + size_t l = sizeof(value_new); if (isempty(token->value)) { if (token->op == OP_ADD) - break; + return log_event_done(event, token); + r = device_add_property(dev, name, NULL); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to remove property '%s': %m", name); - break; + return log_event_error_errno(event, token, r, "Failed to remove property \"%s\": %m", name); + log_event_trace(event, token, "Removed property \"%s\".", name); + return true; } if (token->op == OP_ADD && device_get_property_value_with_fallback(dev, name, event->worker ? event->worker->properties : NULL, &val) >= 0) { + bool truncated; l = strpcpyl_full(&p, l, &truncated, val, " ", NULL); if (truncated) { - log_event_warning(dev, token, - "The buffer for the property '%s' is full, " - "refusing to append the new value '%s'.", name, token->value); - break; + log_event_warning(event, token, + "The buffer for the property is full, refusing to append new property \"%s=%s\".", + name, token->value); + return true; } } - (void) udev_event_apply_format(event, token->value, p, l, false, &truncated); - if (truncated) { - _cleanup_free_ char *key_with_name = strjoin("ENV{", name, "}"); - log_event_truncated(dev, token, "property value", token->value, - key_with_name ?: "ENV", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, p, l, "property value")) + return true; - if (event->esc == ESCAPE_REPLACE) { - count = udev_replace_chars(p, NULL); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu slash(es) from result of ENV{%s}%s=\"%s\"", - count, name, token->op == OP_ADD ? "+" : "", token->value); - } + if (event->esc == ESCAPE_REPLACE) + udev_replace_chars_and_log(event, token, p, /* allow = */ NULL, "property value"); r = device_add_property(dev, name, value_new); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", name, value_new); - break; + return log_event_error_errno(event, token, r, "Failed to set property \"%s=%s\": %m", name, value_new); + log_event_trace(event, token, "Set property \"%s=%s\".", name, value_new); + return true; } case TK_A_TAG: { char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "tag name", token->value, "TAG", /* is_match = */ false); - break; + if (!apply_format_value(event, token, buf, sizeof(buf), "tag name")) + return true; + + if (token->op == OP_REMOVE) { + device_remove_tag(dev, buf); + log_event_trace(event, token, "Removed tag \"%s\".", buf); + return true; } + assert(IN_SET(token->op, OP_ASSIGN, OP_ADD)); + if (token->op == OP_ASSIGN) device_cleanup_tags(dev); - if (token->op == OP_REMOVE) - device_remove_tag(dev, buf); - else { - r = device_add_tag(dev, buf, true); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to add tag '%s', ignoring: %m", buf); - } - break; + r = device_add_tag(dev, buf, /* both = */ true); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to %s tag \"%s\", ignoring: %m", + token->op == OP_ASSIGN ? "set" : "add", buf); + else + log_event_trace(event, token, "%s tag \"%s\".", token->op == OP_ASSIGN ? "Set" : "Added", buf); + return true; } case TK_A_NAME: { char buf[UDEV_PATH_SIZE]; - bool truncated; - size_t count; if (event->name_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->name_final = true; if (sd_device_get_ifindex(dev, NULL) < 0) { - log_event_error(dev, token, - "Only network interfaces can be renamed, ignoring NAME=\"%s\".", - token->value); - break; + log_event_warning(event, token, "Only network interfaces can be renamed, ignoring."); + return true; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "network interface name", token->value, "NAME", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, buf, sizeof(buf), "network interface name")) + return true; + + if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) + udev_replace_ifname(event, token, buf); - if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) { - if (naming_scheme_has(NAMING_REPLACE_STRICTLY)) - count = udev_replace_ifname(buf); - else - count = udev_replace_chars(buf, "/"); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) from result of NAME=\"%s\"", - count, token->value); - } r = free_and_strdup_warn(&event->name, buf); if (r < 0) return r; - log_event_debug(dev, token, "NAME '%s'", event->name); - break; + log_event_debug(event, token, "Set network interface name: %s", event->name); + return true; } case TK_A_DEVLINK: { char buf[UDEV_PATH_SIZE]; - bool truncated; - size_t count; if (event->devlink_final) - break; - if (sd_device_get_devnum(dev, NULL) < 0) - break; + return log_event_final_set(event, token); + + if (sd_device_get_devnum(dev, NULL) < 0) { + log_event_debug(event, token, "Device does not have device node, ignoring to manage device node symlink."); + return true; + } + if (token->op == OP_ASSIGN_FINAL) event->devlink_final = true; + if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) device_cleanup_devlinks(dev); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), - /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated); - if (truncated) { - log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false); - break; - } + if (!apply_format_full(event, token, token->value, buf, sizeof(buf), + /* replace_whitespace = */ event->esc != ESCAPE_NONE, + "symbolic link path")) + return true; /* By default or string_escape=none, allow multiple symlinks separated by spaces. */ if (event->esc == ESCAPE_UNSET) - count = udev_replace_chars(buf, /* allow = */ "/ "); + udev_replace_chars_and_log(event, token, buf, /* allow = */ "/ ", "device node symlink"); else if (event->esc == ESCAPE_REPLACE) - count = udev_replace_chars(buf, /* allow = */ "/"); - else - count = 0; - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) from result of SYMLINK=\"%s\"", - count, token->value); + udev_replace_chars_and_log(event, token, buf, /* allow = */ "/", "device node symlink"); for (const char *p = buf;;) { _cleanup_free_ char *path = NULL; @@ -2698,166 +2932,219 @@ static int udev_rule_apply_token_to_event( if (r == -ENOMEM) return log_oom(); if (r < 0) { - log_warning_errno(r, "Failed to extract first path in SYMLINK=, ignoring: %m"); - break; + log_warning_errno(r, "Failed to extract first path in device node symlinks, ignoring: %m"); + return true; } if (r == 0) - break; + return true; if (token->op == OP_REMOVE) { r = device_remove_devlink(dev, path); if (r == -ENOMEM) return log_oom(); if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to remove devlink '%s', ignoring: %m", path); + log_event_warning_errno(event, token, r, "Failed to remove device node symlink \"%s\", ignoring: %m", path); else if (r > 0) - log_event_debug(dev, token, "Dropped SYMLINK '%s'", path); + log_event_debug(event, token, "Removed device node symlink \"%s\"", path); } else { r = device_add_devlink(dev, path); if (r == -ENOMEM) return log_oom(); if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to add devlink '%s', ignoring: %m", path); + log_event_warning_errno(event, token, r, "Failed to add device node symlink \"%s\", ignoring: %m", path); else if (r > 0) - log_event_debug(dev, token, "Added SYMLINK '%s'", path); + log_event_debug(event, token, "Added device node symlink \"%s\".", path); } } - break; + assert_not_reached(); } case TK_A_ATTR: { char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE]; - const char *val, *key_name = token->data; - bool truncated; + const char *key = token->data; + + /* First, try to resolve "[<SUBSYSTEM>/<KERNEL>]<attribute>" format. */ + r = udev_resolve_subsys_kernel(key, buf, sizeof(buf), false); + if (r == -ENOMEM) + return log_oom(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) { + log_event_warning_errno(event, token, r, "Failed to resolve sysfs attribute \"%s\", ignoring: %m", key); + return true; + } + if (r < 0) { + /* If not, make the path to sysfs attribute absolute, to make '*' resolvable by attr_subst_subdir(). */ + const char *syspath; + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return log_event_error_errno(event, token, r, "Failed to get syspath: %m"); - if (udev_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 && - sd_device_get_syspath(dev, &val) >= 0) { - strscpyl_full(buf, sizeof(buf), &truncated, val, "/", key_name, NULL); + bool truncated; + strscpyl_full(buf, sizeof(buf), &truncated, syspath, "/", key, NULL); if (truncated) { - log_event_warning(dev, token, - "The path to the attribute '%s/%s' is too long, refusing to set the attribute.", - val, key_name); - break; + log_event_warning(event, token, + "The path to the attribute \"%s/%s\" is too long, refusing to set the attribute.", + syspath, key); + return true; + } + + /* Resolve '*' in the path. */ + r = attr_subst_subdir(buf); + if (r < 0) { + log_event_error_errno(event, token, r, "Could not find file matches \"%s\", ignoring: %m", buf); + return true; } } - r = attr_subst_subdir(buf); + /* Then, make the path relative again. This also checks if the path being inside of the sysfs. */ + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = device_chase(dev, buf, /* flags = */ 0, &resolved, &fd); if (r < 0) { - log_event_error_errno(dev, token, r, "Could not find file matches '%s', ignoring: %m", buf); - break; - } - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "attribute value", token->value, "ATTR", /* is_match = */ false); - break; + log_event_error_errno(event, token, r, "Could not chase sysfs attribute \"%s\", ignoring: %m", buf); + return true; } + /* Apply formatting to the value. */ + if (!apply_format_value(event, token, value, sizeof(value), "attribute value")) + return true; + if (EVENT_MODE_DESTRUCTIVE(event)) { - log_event_debug(dev, token, "Writing ATTR{'%s'}=\"%s\".", buf, value); - r = write_string_file(buf, value, - WRITE_STRING_FILE_VERIFY_ON_FAILURE | - WRITE_STRING_FILE_DISABLE_BUFFER | - WRITE_STRING_FILE_AVOID_NEWLINE | - WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE); + log_event_debug(event, token, "Writing \"%s\" to sysfs attribute \"%s\".", value, resolved); + r = sd_device_set_sysattr_value(dev, resolved, value); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write ATTR{%s}=\"%s\", ignoring: %m", buf, value); - } else - log_event_debug(dev, token, "Running in test mode, skipping writing ATTR{%s}=\"%s\".", buf, value); + log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysfs attribute \"%s\", ignoring: %m", value, resolved); + else { + event_cache_written_sysattr(event, resolved, value); + log_event_done(event, token); + } + } else { + log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysfs attribute \"%s\".", value, resolved); - break; + /* We assume the attribute is writable if the path points to a regular file, and cache + * the value to make it shown by OPTIONS="dump" or obtained by later ATTR match token. */ + r = fd_verify_regular(fd); + if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + log_event_error_errno(event, token, r, "Failed to verify sysfs attribute \"%s\" is a regular file: %m", resolved); + else { + event_cache_written_sysattr(event, resolved, value); + + _cleanup_free_ char *copied = strdup(value); + if (!copied) + return log_oom(); + + r = device_cache_sysattr_value(dev, resolved, value, /* error = */ 0); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to cache sysfs attribute \"%s\", ignoring: %m", resolved); + else if (r > 0) { + TAKE_PTR(resolved); + TAKE_PTR(copied); + } + } + } + return true; } case TK_A_SYSCTL: { char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ false); - break; - } + if (!apply_format_attr(event, token, buf, sizeof(buf), "sysctl entry name")) + return true; - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); - if (truncated) { - _cleanup_free_ char *key_with_name = strjoin("SYSCTL{", buf, "}"); - log_event_truncated(dev, token, "sysctl value", token->value, - key_with_name ?: "SYSCTL", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, value, sizeof(value), "sysctl value")) + return true; sysctl_normalize(buf); if (EVENT_MODE_DESTRUCTIVE(event)) { - log_event_debug(dev, token, "Writing SYSCTL{%s}=\"%s\".", buf, value); + log_event_debug(event, token, "Writing \"%s\" to sysctl entry \"%s\".", value, buf); r = sysctl_write(buf, value); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write SYSCTL{%s}=\"%s\", ignoring: %m", buf, value); - } else - log_event_debug(dev, token, "Running in test mode, skipping writing SYSCTL{%s}=\"%s\".", buf, value); + log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysctl entry \"%s\", ignoring: %m", value, buf); + else { + event_cache_written_sysctl(event, buf, value); + log_event_done(event, token); + } + } else { + log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysctl entry \"%s\".", value, buf); - break; + _cleanup_free_ char *path = path_join("/proc/sys/", buf); + if (!path) + return log_oom(); + + r = verify_regular_at(AT_FDCWD, path, /* follow = */ true); + if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + log_event_error_errno(event, token, r, "Failed to verify sysctl entry \"%s\" is a regular file: %m", buf); + else + event_cache_written_sysctl(event, buf, value); + } + return true; } case TK_A_RUN_BUILTIN: case TK_A_RUN_PROGRAM: { _cleanup_free_ char *cmd = NULL; char buf[UDEV_LINE_SIZE]; - bool truncated; if (event->run_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->run_final = true; if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) - ordered_hashmap_clear_free_key(event->run_list); + ordered_hashmap_clear(event->run_list); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, - token->type == TK_A_RUN_BUILTIN ? "RUN{builtin}" : "RUN{program}", - /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) + return true; cmd = strdup(buf); if (!cmd) return log_oom(); - r = ordered_hashmap_ensure_put(&event->run_list, NULL, cmd, token->data); - if (r == -ENOMEM) - return log_oom(); + r = ordered_hashmap_ensure_put(&event->run_list, &trivial_hash_ops_free, cmd, token->data); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to store command '%s': %m", cmd); + return log_event_error_errno(event, token, r, "Failed to store command \"%s\": %m", cmd); + log_event_debug(event, token, "Set command: %s", cmd); TAKE_PTR(cmd); - - log_event_debug(dev, token, "RUN '%s'", token->value); - break; + return true; } case TK_A_OPTIONS_STATIC_NODE: /* do nothing for events. */ - break; + return true; + default: assert_not_reached(); } - return true; -} - -static bool token_is_for_parents(UdevRuleToken *token) { - return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; + assert_not_reached(); } static int udev_rule_apply_parent_token_to_event(UdevRuleToken *head_token, UdevEvent *event) { int r; assert(head_token); + assert(token_is_for_parents(head_token)); assert(event); + UdevRuleLine *line = head_token->rule_line; + if (event->trace) { + _cleanup_free_ char *joined = NULL; + + LIST_FOREACH(tokens, token, head_token) + if (token_is_for_parents(token)) + (void) strextend_with_separator(&joined, ", ", token->token_str); + else + break; + + log_event_line(event, line, "Checking conditions for parent devices: %s", strna(joined)); + } + event->dev_parent = ASSERT_PTR(event->dev); for (;;) { LIST_FOREACH(tokens, token, head_token) { - if (!token_is_for_parents(token)) - return true; /* All parent tokens match. */ + if (!token_is_for_parents(token)) { + r = 1; /* avoid false maybe-uninitialized warning */ + break; /* All parent tokens match. */ + } r = udev_rule_apply_token_to_event(token, event->dev_parent, event); if (r < 0) @@ -2865,12 +3152,18 @@ static int udev_rule_apply_parent_token_to_event(UdevRuleToken *head_token, Udev if (r == 0) break; } - if (r > 0) - /* All parent tokens match, and no more token (except for GOTO) in the line. */ + if (r > 0) { + if (event->trace) { + const char *s = NULL; + (void) sd_device_get_syspath(event->dev_parent, &s); + log_event_line(event, line, "Parent device \"%s\" passed all parent conditions.", strna(s)); + } return true; + } if (sd_device_get_parent(event->dev_parent, &event->dev_parent) < 0) { event->dev_parent = NULL; + log_event_line(event, line, "No parent device passed parent conditions."); return false; } } @@ -2927,8 +3220,10 @@ static int udev_rule_apply_line_to_event( return r; } - if (line->goto_line) + if (line->goto_line) { + log_event_line(event, line, "GOTO=%s", strna(line->goto_label)); *next_line = line->goto_line; /* update next_line only when the line has GOTO token. */ + } return 0; } diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 67d7e5b178..62dac5ba73 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -14,7 +14,7 @@ int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool * 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); -int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing); +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra); UdevRules* udev_rules_free(UdevRules *rules); DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRules*, udev_rules_free); #define udev_rules_free_and_replace(a, b) free_and_replace_full(a, b, udev_rules_free) diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 8faadb8bcf..41f7fe08f9 100644 --- a/src/udev/udev-varlink.c +++ b/src/udev/udev-varlink.c @@ -11,10 +11,13 @@ #define UDEV_VARLINK_ADDRESS "/run/udev/io.systemd.Udev" static int vl_method_reload(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; log_debug("Received io.systemd.service.Reload()"); manager_reload(userdata, /* force = */ true); @@ -40,6 +43,26 @@ static int vl_method_set_log_level(sd_varlink *link, sd_json_variant *parameters return sd_varlink_reply(link, NULL); } +static int vl_method_set_trace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + bool enable; + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "enable", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, 0, SD_JSON_MANDATORY }, + {} + }; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &enable); + if (r != 0) + return r; + + log_debug("Received io.systemd.service.SetTrace(%s)", yes_no(enable)); + manager_set_trace(userdata, enable); + return sd_varlink_reply(link, NULL); +} + static int vl_method_set_children_max(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { unsigned n; int r; @@ -87,8 +110,9 @@ static int vl_method_start_stop_exec_queue(sd_varlink *link, sd_json_variant *pa assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; r = sd_varlink_get_current_method(link, &method); if (r < 0) @@ -100,10 +124,13 @@ static int vl_method_start_stop_exec_queue(sd_varlink *link, sd_json_variant *pa } static int vl_method_exit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; /* Refuse further connections. */ _unused_ _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *v = sd_varlink_ref(link); @@ -149,14 +176,16 @@ int manager_start_varlink_server(Manager *manager) { r = sd_varlink_server_bind_method_many( v, - "io.systemd.service.Ping", varlink_method_ping, - "io.systemd.service.Reload", vl_method_reload, - "io.systemd.service.SetLogLevel", vl_method_set_log_level, - "io.systemd.Udev.SetChildrenMax", vl_method_set_children_max, - "io.systemd.Udev.SetEnvironment", vl_method_set_environment, - "io.systemd.Udev.StartExecQueue", vl_method_start_stop_exec_queue, - "io.systemd.Udev.StopExecQueue", vl_method_start_stop_exec_queue, - "io.systemd.Udev.Exit", vl_method_exit); + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.Reload", vl_method_reload, + "io.systemd.service.SetLogLevel", vl_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment, + "io.systemd.Udev.SetTrace", vl_method_set_trace, + "io.systemd.Udev.SetChildrenMax", vl_method_set_children_max, + "io.systemd.Udev.SetEnvironment", vl_method_set_environment, + "io.systemd.Udev.StartExecQueue", vl_method_start_stop_exec_queue, + "io.systemd.Udev.StopExecQueue", vl_method_start_stop_exec_queue, + "io.systemd.Udev.Exit", vl_method_exit); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c index 52b2203677..5c7583a9ff 100644 --- a/src/udev/udev-worker.c +++ b/src/udev/udev-worker.c @@ -182,6 +182,7 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) { udev_event = udev_event_new(dev, worker, EVENT_UDEV_WORKER); if (!udev_event) return -ENOMEM; + udev_event->trace = worker->config.trace; /* 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. diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c new file mode 100644 index 0000000000..2d7e86994d --- /dev/null +++ b/src/udev/udevadm-cat.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <getopt.h> + +#include "log.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "static-destruct.h" +#include "strv.h" +#include "udevadm.h" +#include "udevadm-util.h" + +static char *arg_root = NULL; +static CatFlags arg_cat_flags = 0; +static bool arg_config = false; + +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("udevadm", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s cat [OPTIONS] [FILE...]\n" + "\n%sShow udev rules files.%s\n\n" + " -h --help Show this help\n" + " -V --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --tldr Skip comments and empty lines\n" + " --config Show udev.conf rather than udev rules files\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_ROOT = 0x100, + ARG_TLDR, + ARG_CONFIG, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "tldr", no_argument, NULL, ARG_TLDR }, + { "config", no_argument, NULL, ARG_CONFIG }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + switch (c) { + case 'h': + return help(); + case 'V': + return print_version(); + case ARG_ROOT: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + case ARG_TLDR: + arg_cat_flags = CAT_TLDR; + break; + case ARG_CONFIG: + arg_config = true; + break; + case '?': + return -EINVAL; + default: + assert_not_reached(); + } + + if (arg_config && optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Combination of --config and FILEs is not supported."); + + return 1; +} + +int cat_main(int argc, char *argv[], void *userdata) { + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_config) + return conf_files_cat(arg_root, "udev/udev.conf", arg_cat_flags); + + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; + + /* udev rules file does not support dropin configs. So, we can safely pass multiple files as dropins. */ + return cat_files(/* file = */ NULL, /* dropins = */ files, arg_cat_flags); +} diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 3ccc621357..1fa5a42018 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -10,6 +10,7 @@ #include "creds-util.h" #include "errno-util.h" +#include "parse-argument.h" #include "parse-util.h" #include "process-util.h" #include "static-destruct.h" @@ -30,6 +31,7 @@ static bool arg_exit = false; static int arg_max_children = -1; static int arg_log_level = -1; static int arg_start_exec_queue = -1; +static int arg_trace = -1; static bool arg_load_credentials = false; STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep); @@ -42,7 +44,8 @@ static bool arg_has_control_commands(void) { arg_reload || !strv_isempty(arg_env) || arg_max_children >= 0 || - arg_ping; + arg_ping || + arg_trace >= 0; } static int help(void) { @@ -58,6 +61,7 @@ static int help(void) { " -p --property=KEY=VALUE Set a global property for all events\n" " -m --children-max=N Maximum number of children\n" " --ping Wait for udev to respond to a ping message\n" + " --trace=BOOL Enable/disable trace logging\n" " -t --timeout=SECONDS Maximum time to block for a reply\n" " --load-credentials Load udev rules from credentials\n", program_invocation_short_name); @@ -68,6 +72,7 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { enum { ARG_PING = 0x100, + ARG_TRACE, ARG_LOAD_CREDENTIALS, }; @@ -83,6 +88,7 @@ static int parse_argv(int argc, char *argv[]) { { "env", required_argument, NULL, 'p' }, /* alias for -p */ { "children-max", required_argument, NULL, 'm' }, { "ping", no_argument, NULL, ARG_PING }, + { "trace", required_argument, NULL, ARG_TRACE }, { "timeout", required_argument, NULL, 't' }, { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, { "version", no_argument, NULL, 'V' }, @@ -143,6 +149,14 @@ static int parse_argv(int argc, char *argv[]) { arg_ping = true; break; + case ARG_TRACE: + r = parse_boolean_argument("--trace=", optarg, NULL); + if (r < 0) + return r; + + arg_trace = r; + break; + case 't': r = parse_sec(optarg, &arg_timeout); if (r < 0) @@ -296,6 +310,13 @@ static int send_control_commands(void) { return r; } + if (arg_trace >= 0) { + r = varlink_callbo_and_log(link, "io.systemd.Udev.SetTrace", /* reply = */ NULL, + SD_JSON_BUILD_PAIR_BOOLEAN("enable", arg_trace)); + if (r < 0) + return r; + } + return 0; } diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 9585ac892f..76b953c917 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -15,10 +15,11 @@ #include "hashmap.h" #include "set.h" #include "signal-util.h" +#include "static-destruct.h" #include "string-util.h" +#include "time-util.h" #include "udevadm.h" #include "virt.h" -#include "time-util.h" static bool arg_show_property = false; static bool arg_print_kernel = false; @@ -26,6 +27,9 @@ static bool arg_print_udev = false; static Set *arg_tag_filter = NULL; static Hashmap *arg_subsystem_filter = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_tag_filter, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_subsystem_filter, hashmap_freep); + static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { sd_device_action_t action = _SD_DEVICE_ACTION_INVALID; const char *devpath = NULL, *subsystem = NULL; @@ -143,28 +147,27 @@ static int parse_argv(int argc, char *argv[]) { if (slash) { devtype = strdup(slash + 1); if (!devtype) - return -ENOMEM; + return log_oom(); subsystem = strndup(optarg, slash - optarg); } else subsystem = strdup(optarg); if (!subsystem) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(&arg_subsystem_filter, NULL, subsystem, devtype); + r = hashmap_ensure_put(&arg_subsystem_filter, &trivial_hash_ops_free_free, subsystem, devtype); if (r < 0) - return r; + return log_oom(); TAKE_PTR(subsystem); TAKE_PTR(devtype); break; } case 't': - /* optarg is stored in argv[], so we don't need to copy it */ - r = set_ensure_put(&arg_tag_filter, &string_hash_ops, optarg); + r = set_put_strdup(&arg_tag_filter, optarg); if (r < 0) - return r; + return log_oom(); break; case 'V': @@ -192,7 +195,7 @@ int monitor_main(int argc, char *argv[], void *userdata) { r = parse_argv(argc, argv); if (r <= 0) - goto finalize; + return r; if (running_in_chroot() > 0) { log_info("Running in chroot, ignoring request."); @@ -203,22 +206,18 @@ int monitor_main(int argc, char *argv[], void *userdata) { setlinebuf(stdout); r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to initialize event: %m"); - goto finalize; - } + if (r < 0) + return log_error_errno(r, "Failed to initialize event: %m"); r = sd_event_set_signal_exit(event, true); - if (r < 0) { - log_error_errno(r, "Failed to install SIGINT/SIGTERM handling: %m"); - goto finalize; - } + if (r < 0) + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handling: %m"); printf("monitor will print the received events for:\n"); if (arg_print_udev) { r = setup_monitor(MONITOR_GROUP_UDEV, event, &udev_monitor); if (r < 0) - goto finalize; + return r; printf("UDEV - the event which udev sends out after rule processing\n"); } @@ -226,23 +225,15 @@ int monitor_main(int argc, char *argv[], void *userdata) { if (arg_print_kernel) { r = setup_monitor(MONITOR_GROUP_KERNEL, event, &kernel_monitor); if (r < 0) - goto finalize; + return r; printf("KERNEL - the kernel uevent\n"); } printf("\n"); r = sd_event_loop(event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finalize; - } - - r = 0; - -finalize: - hashmap_free_free_free(arg_subsystem_filter); - set_free(arg_tag_filter); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); - return r; + return 0; } diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 5db991b8a6..c3f56d2d81 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -3,37 +3,30 @@ * Copyright © 2003-2004 Greg Kroah-Hartman <greg@kroah.com> */ -#include <errno.h> #include <getopt.h> #include <signal.h> -#include <stddef.h> #include <stdio.h> -#include <stdlib.h> -#include <sys/signalfd.h> -#include <unistd.h> #include "sd-device.h" -#include "ansi-color.h" #include "device-private.h" -#include "device-util.h" -#include "format-util.h" -#include "path-util.h" -#include "string-util.h" +#include "parse-argument.h" +#include "static-destruct.h" #include "strv.h" -#include "strxcpyx.h" -#include "terminal-util.h" #include "udev-builtin.h" +#include "udev-dump.h" #include "udev-event.h" -#include "udev-format.h" #include "udev-rules.h" #include "udevadm-util.h" #include "udevadm.h" -#include "user-util.h" static sd_device_action_t arg_action = SD_DEVICE_ADD; static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static const char *arg_syspath = NULL; +static char **arg_extra_rules_dir = NULL; +static bool arg_verbose = false; + +STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); static int help(void) { @@ -42,7 +35,9 @@ static int help(void) { " -h --help Show this help\n" " -V --version Show package version\n" " -a --action=ACTION|help Set action string\n" - " -N --resolve-names=early|late|never When to resolve names\n", + " -N --resolve-names=early|late|never When to resolve names\n" + " -D --extra-rules-dir=DIR Also load rules from the directory\n" + " -v --verbose Show verbose logs\n", program_invocation_short_name); return 0; @@ -50,16 +45,18 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "extra-rules-dir", required_argument, NULL, 'D' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; int r, c; - while ((c = getopt_long(argc, argv, "a:N:Vh", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) switch (c) { case 'a': r = parse_device_action(optarg, &arg_action); @@ -74,6 +71,21 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--resolve-names= must be early, late or never"); break; + case 'D': { + _cleanup_free_ char *p = NULL; + + r = parse_path_argument(optarg, /* suppress_root = */ false, &p); + if (r < 0) + return r; + + r = strv_consume(&arg_extra_rules_dir, TAKE_PTR(p)); + if (r < 0) + return log_oom(); + break; + } + case 'v': + arg_verbose = true; + break; case 'V': return print_version(); case 'h': @@ -105,20 +117,23 @@ int test_main(int argc, char *argv[], void *userdata) { if (r <= 0) return r; - printf("This program is for debugging only, it does not run any program\n" - "specified by a RUN key. It may show incorrect results, because\n" - "some values may be different, or not available at a simulation run.\n" - "\n"); + puts("This program is for debugging only, it does not run any program\n" + "specified by a RUN key. It may show incorrect results, because\n" + "some values may be different, or not available at a simulation run."); assert_se(sigprocmask(SIG_SETMASK, NULL, &sigmask_orig) >= 0); + puts("\nLoading builtins..."); udev_builtin_init(); + puts("Loading builtins done."); - r = udev_rules_load(&rules, arg_resolve_name_timing); + puts("\nLoading udev rules files..."); + r = udev_rules_load(&rules, arg_resolve_name_timing, arg_extra_rules_dir); if (r < 0) { log_error_errno(r, "Failed to read udev rules: %m"); goto out; } + puts("Loading udev rules files done."); r = find_device_with_action(arg_syspath, arg_action, &dev); if (r < 0) { @@ -134,89 +149,17 @@ int test_main(int argc, char *argv[], void *userdata) { log_oom(); goto out; } + event->trace = arg_verbose; assert_se(sigfillset(&mask) >= 0); assert_se(sigprocmask(SIG_SETMASK, &mask, &sigmask_orig) >= 0); + printf("\nProcessing udev rules%s...\n", arg_verbose ? "" : " (verbose logs can be shown by -v/--verbose)"); udev_event_execute_rules(event, rules); + puts("Processing udev rules done."); - printf("%sProperties:%s\n", ansi_highlight(), ansi_normal()); - FOREACH_DEVICE_PROPERTY(dev, key, value) - printf(" %s=%s\n", key, value); - - if (sd_device_get_tag_first(dev)) { - printf("%sTags:%s\n", ansi_highlight(), ansi_normal()); - FOREACH_DEVICE_TAG(dev, tag) - printf(" %s\n", tag); - } - - if (sd_device_get_devnum(dev, NULL) >= 0) { - - if (sd_device_get_devlink_first(dev)) { - int prio; - device_get_devlink_priority(dev, &prio); - printf("%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); - FOREACH_DEVICE_DEVLINK(dev, devlink) - printf(" %s\n", devlink); - } - - printf("%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); - - uid_t uid = event->uid; - if (!uid_is_valid(uid)) - (void) device_get_devnode_uid(dev, &uid); - if (uid_is_valid(uid)) { - _cleanup_free_ char *user = uid_to_name(uid); - printf("%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); - } - - gid_t gid = event->gid; - if (!gid_is_valid(uid)) - (void) device_get_devnode_gid(dev, &gid); - if (gid_is_valid(gid)) { - _cleanup_free_ char *group = gid_to_name(gid); - printf("%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); - } - - mode_t mode = event->mode; - if (mode == MODE_INVALID) - (void) device_get_devnode_mode(dev, &mode); - if (mode != MODE_INVALID) - printf("%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); - - if (!ordered_hashmap_isempty(event->seclabel_list)) { - const char *name, *label; - printf("%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); - ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) - printf(" %s : %s\n", name, label); - } - } - - if (sd_device_get_ifindex(dev, NULL) >= 0) { - if (!isempty(event->name)) - printf("%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); - - if (!strv_isempty(event->altnames)) { - bool space = true; - printf("%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); - fputstrv(stdout, event->altnames, "\n ", &space); - puts(""); - } - } - - if (!ordered_hashmap_isempty(event->run_list)) { - void *val; - const char *command; - printf("%sQueued commands:%s\n", ansi_highlight(), ansi_normal()); - ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { - UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); - - if (builtin_cmd != _UDEV_BUILTIN_INVALID) - printf(" RUN{builtin} : %s\n", command); - else - printf(" RUN{program} : %s\n", command); - } - } + puts(""); + dump_event(event, NULL); r = 0; out: diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 2a3e974d04..641adb4f97 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -5,6 +5,9 @@ #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" +#include "chase.h" +#include "conf-files.h" +#include "constants.h" #include "device-private.h" #include "path-util.h" #include "udev-ctrl.h" @@ -168,3 +171,109 @@ int udev_ping(usec_t timeout_usec, bool ignore_connection_failure) { return 1; /* received reply */ } + +static int search_rules_file_in_conf_dirs(const char *s, const char *root, char ***files) { + _cleanup_free_ char *filename = NULL; + int r; + + assert(s); + + if (!endswith(s, ".rules")) + filename = strjoin(s, ".rules"); + else + filename = strdup(s); + if (!filename) + return log_oom(); + + if (!filename_is_valid(filename)) + return 0; + + STRV_FOREACH(p, CONF_PATHS_STRV("udev/rules.d")) { + _cleanup_free_ char *path = NULL, *resolved = NULL; + + path = path_join(*p, filename); + if (!path) + return log_oom(); + + r = chase(path, root, CHASE_PREFIX_ROOT | CHASE_MUST_BE_REGULAR, &resolved, /* ret_fd = */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", path); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 1; /* found */ + } + + return 0; +} + +static int search_rules_file(const char *s, const char *root, char ***files) { + int r; + + assert(s); + assert(files); + + /* If the input is a file name (e.g. 99-systemd.rules), then try to find it in udev/rules.d directories. */ + r = search_rules_file_in_conf_dirs(s, root, files); + if (r != 0) + return r; + + /* If not found, or if it is a path, then chase it. */ + struct stat st; + _cleanup_free_ char *resolved = NULL; + r = chase_and_stat(s, root, CHASE_PREFIX_ROOT, &resolved, &st); + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", s); + + r = stat_verify_regular(&st); + if (r == -EISDIR) { + _cleanup_strv_free_ char **files_in_dir = NULL; + + r = conf_files_list_strv(&files_in_dir, ".rules", root, 0, (const char* const*) STRV_MAKE_CONST(s)); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files in '%s': %m", resolved); + + r = strv_extend_strv_consume(files, TAKE_PTR(files_in_dir), /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return 0; + } + if (r < 0) + return log_error_errno(r, "'%s' is neither a regular file nor a directory: %m", resolved); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 0; +} + +int search_rules_files(char * const *a, const char *root, char ***ret) { + _cleanup_strv_free_ char **files = NULL; + int r; + + assert(ret); + + if (strv_isempty(a)) { + r = conf_files_list_strv(&files, ".rules", root, 0, (const char* const*) CONF_PATHS_STRV("udev/rules.d")); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files: %m"); + + if (root && strv_isempty(files)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No rules files found in %s.", root); + + } else + STRV_FOREACH(s, a) { + r = search_rules_file(*s, root, &files); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(files); + return 0; +} diff --git a/src/udev/udevadm-util.h b/src/udev/udevadm-util.h index 4b58efbe97..0a8b31ada7 100644 --- a/src/udev/udevadm-util.h +++ b/src/udev/udevadm-util.h @@ -7,3 +7,4 @@ int find_device(const char *id, const char *prefix, sd_device **ret); int find_device_with_action(const char *id, sd_device_action_t action, sd_device **ret); int parse_device_action(const char *str, sd_device_action_t *action); int udev_ping(usec_t timeout, bool ignore_connection_failure); +int search_rules_files(char * const *a, const char *root, char ***ret); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 32202508f3..fb8cdee4f2 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -7,16 +7,15 @@ #include <stdlib.h> #include <unistd.h> -#include "conf-files.h" -#include "constants.h" +#include "errno-util.h" #include "log.h" #include "parse-argument.h" #include "pretty-print.h" -#include "stat-util.h" #include "static-destruct.h" #include "strv.h" #include "udev-rules.h" #include "udevadm.h" +#include "udevadm-util.h" static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static char *arg_root = NULL; @@ -109,10 +108,6 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - if (arg_root && optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Combination of --root= and FILEs is not supported."); - return 1; } @@ -139,57 +134,20 @@ static int verify_rules_file(UdevRules *rules, const char *fname) { return 0; } -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs); - -static int verify_rules_dir(UdevRules *rules, const char *dir, size_t *fail_count, size_t *success_count) { - int r; - _cleanup_strv_free_ char **files = NULL; - - assert(rules); - assert(dir); - assert(fail_count); - assert(success_count); - - r = conf_files_list(&files, ".rules", NULL, 0, dir); - if (r < 0) - return log_error_errno(r, "Failed to enumerate rules files: %m"); - - return verify_rules_filelist(rules, files, fail_count, success_count, /* walk_dirs */ false); -} - -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs) { - int r, rv = 0; - - assert(rules); - assert(files); - assert(fail_count); - assert(success_count); - - STRV_FOREACH(fp, files) { - if (walk_dirs && is_dir(*fp, /* follow = */ true) > 0) - r = verify_rules_dir(rules, *fp, fail_count, success_count); - else { - r = verify_rules_file(rules, *fp); - if (r < 0) - ++(*fail_count); - else - ++(*success_count); - } - if (r < 0 && rv >= 0) - rv = r; - } - - return rv; -} - static int verify_rules(UdevRules *rules, char **files) { size_t fail_count = 0, success_count = 0; - int r; + int r, ret = 0; assert(rules); - assert(files); - r = verify_rules_filelist(rules, files, &fail_count, &success_count, /* walk_dirs */ true); + STRV_FOREACH(fp, files) { + r = verify_rules_file(rules, *fp); + if (r < 0) + ++fail_count; + else + ++success_count; + RET_GATHER(ret, r); + } if (arg_summary) printf("\n%s%zu udev rules files have been checked.%s\n" @@ -203,7 +161,7 @@ static int verify_rules(UdevRules *rules, char **files) { fail_count, fail_count > 0 ? ansi_normal() : ""); - return r; + return ret; } int verify_main(int argc, char *argv[], void *userdata) { @@ -218,19 +176,10 @@ int verify_main(int argc, char *argv[], void *userdata) { if (!rules) return -ENOMEM; - if (optind == argc) { - const char* const* rules_dirs = STRV_MAKE_CONST(CONF_PATHS("udev/rules.d")); - _cleanup_strv_free_ char **files = NULL; - - r = conf_files_list_strv(&files, ".rules", arg_root, 0, rules_dirs); - if (r < 0) - return log_error_errno(r, "Failed to enumerate rules files: %m"); - if (arg_root && strv_isempty(files)) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "No rules files found in %s.", arg_root); - - return verify_rules(rules, files); - } + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; - return verify_rules(rules, strv_skip(argv, optind)); + return verify_rules(rules, files); } diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 30b6ddb728..ebf1e5190c 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -26,6 +26,7 @@ static int help(void) { { "test", "Test an event run" }, { "test-builtin", "Test a built-in command" }, { "verify", "Verify udev rules files" }, + { "cat", "Show udev rules files" }, { "wait", "Wait for device or device symlink" }, { "lock", "Lock a block device" }, }; @@ -97,6 +98,7 @@ static int help_main(int argc, char *argv[], void *userdata) { static int udevadm_main(int argc, char *argv[]) { static const Verb verbs[] = { + { "cat", VERB_ANY, VERB_ANY, 0, cat_main }, { "info", VERB_ANY, VERB_ANY, 0, info_main }, { "trigger", VERB_ANY, VERB_ANY, 0, trigger_main }, { "settle", VERB_ANY, VERB_ANY, 0, settle_main }, diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index 7920a70d5b..e39dbf655d 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -5,6 +5,7 @@ #include "macro.h" +int cat_main(int argc, char *argv[], void *userdata); int info_main(int argc, char *argv[], void *userdata); int trigger_main(int argc, char *argv[], void *userdata); int settle_main(int argc, char *argv[], void *userdata); |