summaryrefslogtreecommitdiffstats
path: root/src/udev/udevadm-wait.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/udev/udevadm-wait.c456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c
new file mode 100644
index 0000000..5ade13d
--- /dev/null
+++ b/src/udev/udevadm-wait.c
@@ -0,0 +1,456 @@
+/* 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;
+
+ 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;
+}