| /* 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-monitor-private.h" |
| #include "device-util.h" |
| #include "errno-util.h" |
| #include "event-util.h" |
| #include "fd-util.h" |
| #include "fs-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); |
| |
| if (arg_wait_until == WAIT_UNTIL_REMOVED) { |
| r = laccess(path, F_OK); |
| if (r == -ENOENT) |
| return true; |
| if (r < 0) |
| return r; |
| return false; |
| } |
| |
| r = sd_device_new_from_path(&dev, path); |
| if (r == -ENODEV) |
| return false; |
| if (r < 0) |
| return r; |
| |
| if (arg_wait_until == WAIT_UNTIL_INITIALIZED) |
| return sd_device_get_is_initialized(dev); |
| |
| return true; |
| } |
| |
| 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) { |
| int r; |
| |
| assert(event); |
| |
| if (check()) { |
| r = sd_event_exit(event, 0); |
| if (r < 0) |
| return r; |
| |
| return 1; |
| } |
| |
| 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)); |
| |
| FOREACH_DEVICE_DEVLINK(device, name) |
| if (path_strv_contains(arg_devices, name)) |
| return check_and_exit(sd_device_monitor_get_event(monitor)); |
| |
| return 0; |
| } |
| |
| static int setup_monitor(sd_event *event, MonitorNetlinkGroup group, const char *description, sd_device_monitor **ret) { |
| _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; |
| int r; |
| |
| assert(event); |
| assert(ret); |
| |
| r = device_monitor_new_full(&monitor, group, /* fd = */ -1); |
| 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_set_description(monitor, description); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_monitor_start(monitor, device_monitor_handler, NULL); |
| 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 reset_timer(sd_event *e, sd_event_source **s); |
| |
| static int on_periodic_timer(sd_event_source *s, uint64_t usec, void *userdata) { |
| static unsigned counter = 0; |
| sd_event *e; |
| int r; |
| |
| assert(s); |
| |
| e = sd_event_source_get_event(s); |
| |
| /* Even if all devices exists, we try to wait for uevents to be emitted from kernel. */ |
| if (check()) |
| counter++; |
| else |
| counter = 0; |
| |
| if (counter >= 2) { |
| log_debug("All requested devices popped up without receiving kernel uevents."); |
| return sd_event_exit(e, 0); |
| } |
| |
| r = reset_timer(e, &s); |
| if (r < 0) |
| log_warning_errno(r, "Failed to reset periodic timer event source, ignoring: %m"); |
| |
| return 0; |
| } |
| |
| static int reset_timer(sd_event *e, sd_event_source **s) { |
| return event_reset_time_relative(e, s, CLOCK_BOOTTIME, 250 * USEC_PER_MSEC, 0, |
| on_periodic_timer, NULL, 0, "periodic-timer-event-source", false); |
| } |
| |
| static int setup_periodic_timer(sd_event *event) { |
| _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; |
| int r; |
| |
| assert(event); |
| |
| r = reset_timer(event, &s); |
| if (r < 0) |
| return r; |
| |
| /* Set the lower priority than device monitor, to make uevents always dispatched first. */ |
| r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_NORMAL + 1); |
| 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 *udev_monitor = NULL, *kernel_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_GROUP_UDEV, "udev-uevent-monitor-event-source", &udev_monitor); |
| if (r < 0) |
| return log_error_errno(r, "Failed to set up udev uevent monitor: %m"); |
| |
| if (arg_wait_until == WAIT_UNTIL_ADDED) { |
| /* If --initialized=no is specified, it is not necessary to wait uevents for the specified |
| * devices to be processed by udevd. Hence, let's listen on the kernel's uevent stream. Then, |
| * we may be able to finish this program earlier when udevd is very busy. |
| * Note, we still need to also setup udev monitor, as this may be invoked with a devlink |
| * (e.g. /dev/disk/by-id/foo). In that case, the devlink may not exist when we received a |
| * uevent from kernel, as the udevd may not finish to process the uevent yet. Hence, we need |
| * to wait until the event is processed by udevd. */ |
| r = setup_monitor(event, MONITOR_GROUP_KERNEL, "kernel-uevent-monitor-event-source", &kernel_monitor); |
| if (r < 0) |
| return log_error_errno(r, "Failed to set up kernel uevent monitor: %m"); |
| |
| /* This is a workaround for issues #24360 and #24450. |
| * For some reasons, the kernel sometimes does not emit uevents for loop block device on |
| * attach. Hence, without the periodic timer, no event source for this program will be |
| * triggered, and this will be timed out. |
| * Theoretically, inotify watch may be better, but this program typically expected to run in |
| * a short time. Hence, let's use the simpler periodic timer event source here. */ |
| r = setup_periodic_timer(event); |
| if (r < 0) |
| return log_error_errno(r, "Failed to set up periodic timer: %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; |
| } |