/* SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #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 "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) { 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; }