diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2022-03-25 21:01:40 +0100 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2022-04-01 08:13:18 +0200 |
commit | aa2b0d8d291a1f1dc2b50016c076ff8196989f84 (patch) | |
tree | d3e690218cc7fd5baefae764a079c4eefb15451c /src/udev | |
parent | test: add more tests for sd_device_new_from_xxx() (diff) | |
download | systemd-aa2b0d8d291a1f1dc2b50016c076ff8196989f84.tar.xz systemd-aa2b0d8d291a1f1dc2b50016c076ff8196989f84.zip |
udevadm: introduce new 'wait' command
Prompted by https://github.com/systemd/systemd/pull/22717#issuecomment-1067348496.
The new command 'udevadm wait' waits for device or device symlink being
created. This may be useful to wait for a device is processed by udevd
after e.g. formatting or partitioning the device.
Diffstat (limited to 'src/udev')
-rw-r--r-- | src/udev/meson.build | 1 | ||||
-rw-r--r-- | src/udev/udevadm-wait.c | 378 | ||||
-rw-r--r-- | src/udev/udevadm.c | 16 | ||||
-rw-r--r-- | src/udev/udevadm.h | 1 |
4 files changed, 389 insertions, 7 deletions
diff --git a/src/udev/meson.build b/src/udev/meson.build index a4648be3b4..8a2926db30 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -13,6 +13,7 @@ udevadm_sources = files( 'udevadm-trigger.c', 'udevadm-util.c', 'udevadm-util.h', + 'udevadm-wait.c', 'udevd.c', ) diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c new file mode 100644 index 0000000000..0ea0ed501b --- /dev/null +++ b/src/udev/udevadm-wait.c @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <getopt.h> +#include <unistd.h> + +#include "sd-event.h" + +#include "alloc-util.h" +#include "chase-symlinks.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "inotify-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "static-destruct.h" +#include "string-table.h" +#include "strv.h" +#include "udev-util.h" +#include "udevadm.h" + +typedef enum WaitUntil { + WAIT_UNTIL_INITIALIZED, + WAIT_UNTIL_ADDED, + WAIT_UNTIL_REMOVED, + _WAIT_UNTIL_MAX, + _WAIT_UNTIL_INVALID = -EINVAL, +} WaitUntil; + +static WaitUntil arg_wait_until = WAIT_UNTIL_INITIALIZED; +static usec_t arg_timeout_usec = USEC_INFINITY; +static bool arg_settle = false; +static char **arg_devices = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep); + +static const char * const wait_until_table[_WAIT_UNTIL_MAX] = { + [WAIT_UNTIL_INITIALIZED] = "initialized", + [WAIT_UNTIL_ADDED] = "added", + [WAIT_UNTIL_REMOVED] = "removed", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wait_until, WaitUntil); + +static int check_device(const char *path) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + int r; + + assert(path); + + r = sd_device_new_from_path(&dev, path); + if (r == -ENODEV) + return arg_wait_until == WAIT_UNTIL_REMOVED; + if (r < 0) + return r; + + switch (arg_wait_until) { + case WAIT_UNTIL_INITIALIZED: + return sd_device_get_is_initialized(dev); + case WAIT_UNTIL_ADDED: + return true; + case WAIT_UNTIL_REMOVED: + return false; + default: + assert_not_reached(); + } +} + +static bool check(void) { + int r; + + if (arg_settle) { + r = udev_queue_is_empty(); + if (r == 0) + return false; + if (r < 0) + log_warning_errno(r, "Failed to check if udev queue is empty, assuming empty: %m"); + } + + STRV_FOREACH(p, arg_devices) { + r = check_device(*p); + if (r <= 0) { + if (r < 0) + log_warning_errno(r, "Failed to check if device \"%s\" is %s, assuming not %s: %m", + *p, + wait_until_to_string(arg_wait_until), + wait_until_to_string(arg_wait_until)); + return false; + } + } + + return true; +} + +static int check_and_exit(sd_event *event) { + assert(event); + + if (check()) + return sd_event_exit(event, 0); + + return 0; +} + +static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { + const char *name; + int r; + + assert(monitor); + assert(device); + + if (device_for_action(device, SD_DEVICE_REMOVE) != (arg_wait_until == WAIT_UNTIL_REMOVED)) + return 0; + + if (arg_wait_until == WAIT_UNTIL_REMOVED) + /* On removed event, the received device may not contain enough information. + * Let's unconditionally check all requested devices are removed. */ + return check_and_exit(sd_device_monitor_get_event(monitor)); + + /* For other events, at first check if the received device matches with the requested devices, + * to avoid calling check() so many times within a short time. */ + + r = sd_device_get_sysname(device, &name); + if (r < 0) { + log_device_warning_errno(device, r, "Failed to get sysname of received device, ignoring: %m"); + return 0; + } + + STRV_FOREACH(p, arg_devices) { + const char *s; + + if (!path_startswith(*p, "/sys")) + continue; + + r = path_find_last_component(*p, false, NULL, &s); + if (r < 0) { + log_warning_errno(r, "Failed to extract filename from \"%s\", ignoring: %m", *p); + continue; + } + if (r == 0) + continue; + + if (strneq(s, name, r)) + return check_and_exit(sd_device_monitor_get_event(monitor)); + } + + r = sd_device_get_devname(device, &name); + if (r < 0) { + if (r != -ENOENT) + log_device_warning_errno(device, r, "Failed to get devname of received device, ignoring: %m"); + return 0; + } + + if (path_strv_contains(arg_devices, name)) + return check_and_exit(sd_device_monitor_get_event(monitor)); + + STRV_FOREACH(p, arg_devices) { + const char *link; + + if (!path_startswith(*p, "/dev")) + continue; + + FOREACH_DEVICE_DEVLINK(device, link) + if (path_equal(*p, link)) + return check_and_exit(sd_device_monitor_get_event(monitor)); + } + + return 0; +} + +static int setup_monitor(sd_event *event, sd_device_monitor **ret) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + int r; + + assert(event); + assert(ret); + + r = sd_device_monitor_new(&monitor); + if (r < 0) + return r; + + (void) sd_device_monitor_set_receive_buffer_size(monitor, 128*1024*1024); + + r = sd_device_monitor_attach_event(monitor, event); + if (r < 0) + return r; + + r = sd_device_monitor_start(monitor, device_monitor_handler, NULL); + if (r < 0) + return r; + + r = sd_event_source_set_description(sd_device_monitor_get_event_source(monitor), + "device-monitor-event-source"); + if (r < 0) + return r; + + *ret = TAKE_PTR(monitor); + return 0; +} + +static int on_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) { + return check_and_exit(sd_event_source_get_event(s)); +} + +static int setup_inotify(sd_event *event) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(event); + + if (!arg_settle) + return 0; + + r = sd_event_add_inotify(event, &s, "/run/udev" , IN_CREATE | IN_DELETE, on_inotify, NULL); + if (r < 0) + return r; + + r = sd_event_source_set_description(s, "inotify-event-source"); + if (r < 0) + return r; + + return sd_event_source_set_floating(s, true); +} + +static int setup_timer(sd_event *event) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(event); + + if (arg_timeout_usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time_relative(event, &s, CLOCK_BOOTTIME, arg_timeout_usec, 0, + NULL, INT_TO_PTR(-ETIMEDOUT)); + if (r < 0) + return r; + + r = sd_event_source_set_description(s, "timeout-event-source"); + if (r < 0) + return r; + + return sd_event_source_set_floating(s, true); +} + +static int help(void) { + printf("%s wait [OPTIONS] DEVICE [DEVICEā¦]\n\n" + "Wait for devices or device symlinks being created.\n\n" + " -h --help Print this message\n" + " -V --version Print version of the program\n" + " -t --timeout=SEC Maximum time to wait for the device\n" + " --initialized=BOOL Wait for devices being initialized by systemd-udevd\n" + " --removed Wait for devices being removed\n" + " --settle Also wait for all queued events being processed\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_INITIALIZED = 0x100, + ARG_REMOVED, + ARG_SETTLE, + }; + + static const struct option options[] = { + { "timeout", required_argument, NULL, 't' }, + { "initialized", required_argument, NULL, ARG_INITIALIZED }, + { "removed", no_argument, NULL, ARG_REMOVED }, + { "settle", no_argument, NULL, ARG_SETTLE }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + + int c, r; + + while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0) + switch (c) { + case 't': + r = parse_sec(optarg, &arg_timeout_usec); + if (r < 0) + return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg); + break; + + case ARG_INITIALIZED: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg); + arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED; + break; + + case ARG_REMOVED: + arg_wait_until = WAIT_UNTIL_REMOVED; + break; + + case ARG_SETTLE: + arg_settle = true; + break; + + case 'V': + return print_version(); + + case 'h': + return help(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind >= argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too few arguments, expected at least one device path or device symlink."); + + arg_devices = strv_copy(argv + optind); + if (!arg_devices) + return log_oom(); + + return 1; /* work to do */ +} + +int wait_main(int argc, char *argv[], void *userdata) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + STRV_FOREACH(p, arg_devices) { + path_simplify(*p); + + if (!path_is_safe(*p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Device path cannot contain \"..\"."); + + if (!is_device_path(*p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Specified path \"%s\" does not start with \"/dev/\" or \"/sys/\".", *p); + } + + /* Check before configuring event sources, as devices may be already initialized. */ + if (check()) + return 0; + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to initialize sd-event: %m"); + + r = setup_timer(event); + if (r < 0) + return log_error_errno(r, "Failed to set up timeout: %m"); + + r = setup_inotify(event); + if (r < 0) + return log_error_errno(r, "Failed to set up inotify: %m"); + + r = setup_monitor(event, &monitor); + if (r < 0) + return log_error_errno(r, "Failed to set up device monitor: %m"); + + /* Check before entering the event loop, as devices may be initialized during setting up event sources. */ + if (check()) + return 0; + + r = sd_event_loop(event); + if (r == -ETIMEDOUT) + return log_error_errno(r, "Timed out for waiting devices being %s.", + wait_until_to_string(arg_wait_until)); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + + return 0; +} diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index ba17d9348b..df23a60c1a 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -19,13 +19,14 @@ static int help(void) { static const char *const short_descriptions[][2] = { - { "info", "Query sysfs or the udev database" }, - { "trigger", "Request events from the kernel" }, - { "settle", "Wait for pending udev events" }, - { "control", "Control the udev daemon" }, - { "monitor", "Listen to kernel and udev events" }, - { "test", "Test an event run" }, - { "test-builtin", "Test a built-in command" }, + { "info", "Query sysfs or the udev database" }, + { "trigger", "Request events from the kernel" }, + { "settle", "Wait for pending udev events" }, + { "control", "Control the udev daemon" }, + { "monitor", "Listen to kernel and udev events" }, + { "test", "Test an event run" }, + { "test-builtin", "Test a built-in command" }, + { "wait", "Wait for device or device symlink" }, }; _cleanup_free_ char *link = NULL; @@ -101,6 +102,7 @@ static int udevadm_main(int argc, char *argv[]) { { "hwdb", VERB_ANY, VERB_ANY, 0, hwdb_main }, { "test", VERB_ANY, VERB_ANY, 0, test_main }, { "test-builtin", VERB_ANY, VERB_ANY, 0, builtin_main }, + { "wait", VERB_ANY, VERB_ANY, 0, wait_main }, { "version", VERB_ANY, VERB_ANY, 0, version_main }, { "help", VERB_ANY, VERB_ANY, 0, help_main }, {} diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index 75ce633632..808294ec9d 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -13,6 +13,7 @@ int monitor_main(int argc, char *argv[], void *userdata); int hwdb_main(int argc, char *argv[], void *userdata); int test_main(int argc, char *argv[], void *userdata); int builtin_main(int argc, char *argv[], void *userdata); +int wait_main(int argc, char *argv[], void *userdata); static inline int print_version(void) { /* Dracut relies on the version being a single integer */ |