1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <getopt.h>
4 #include <unistd.h>
5 
6 #include "sd-event.h"
7 
8 #include "alloc-util.h"
9 #include "chase-symlinks.h"
10 #include "device-util.h"
11 #include "errno-util.h"
12 #include "fd-util.h"
13 #include "fs-util.h"
14 #include "inotify-util.h"
15 #include "parse-util.h"
16 #include "path-util.h"
17 #include "static-destruct.h"
18 #include "string-table.h"
19 #include "strv.h"
20 #include "udev-util.h"
21 #include "udevadm.h"
22 
23 typedef enum WaitUntil {
24         WAIT_UNTIL_INITIALIZED,
25         WAIT_UNTIL_ADDED,
26         WAIT_UNTIL_REMOVED,
27         _WAIT_UNTIL_MAX,
28         _WAIT_UNTIL_INVALID = -EINVAL,
29 } WaitUntil;
30 
31 static WaitUntil arg_wait_until = WAIT_UNTIL_INITIALIZED;
32 static usec_t arg_timeout_usec = USEC_INFINITY;
33 static bool arg_settle = false;
34 static char **arg_devices = NULL;
35 
36 STATIC_DESTRUCTOR_REGISTER(arg_devices, strv_freep);
37 
38 static const char * const wait_until_table[_WAIT_UNTIL_MAX] = {
39         [WAIT_UNTIL_INITIALIZED] = "initialized",
40         [WAIT_UNTIL_ADDED]       = "added",
41         [WAIT_UNTIL_REMOVED]     = "removed",
42 };
43 
44 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wait_until, WaitUntil);
45 
check_device(const char * path)46 static int check_device(const char *path) {
47         _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
48         int r;
49 
50         assert(path);
51 
52         if (arg_wait_until == WAIT_UNTIL_REMOVED) {
53                 r = laccess(path, F_OK);
54                 if (r == -ENOENT)
55                         return true;
56                 if (r < 0)
57                         return r;
58                 return false;
59         }
60 
61         r = sd_device_new_from_path(&dev, path);
62         if (r == -ENODEV)
63                 return false;
64         if (r < 0)
65                 return r;
66 
67         if (arg_wait_until == WAIT_UNTIL_INITIALIZED)
68                 return sd_device_get_is_initialized(dev);
69 
70         return true;
71 }
72 
check(void)73 static bool check(void) {
74         int r;
75 
76         if (arg_settle) {
77                 r = udev_queue_is_empty();
78                 if (r == 0)
79                         return false;
80                 if (r < 0)
81                         log_warning_errno(r, "Failed to check if udev queue is empty, assuming empty: %m");
82         }
83 
84         STRV_FOREACH(p, arg_devices) {
85                 r = check_device(*p);
86                 if (r <= 0) {
87                         if (r < 0)
88                                 log_warning_errno(r, "Failed to check if device \"%s\" is %s, assuming not %s: %m",
89                                                   *p,
90                                                   wait_until_to_string(arg_wait_until),
91                                                   wait_until_to_string(arg_wait_until));
92                         return false;
93                 }
94         }
95 
96         return true;
97 }
98 
check_and_exit(sd_event * event)99 static int check_and_exit(sd_event *event) {
100         assert(event);
101 
102         if (check())
103                 return sd_event_exit(event, 0);
104 
105         return 0;
106 }
107 
device_monitor_handler(sd_device_monitor * monitor,sd_device * device,void * userdata)108 static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) {
109         const char *name;
110         int r;
111 
112         assert(monitor);
113         assert(device);
114 
115         if (device_for_action(device, SD_DEVICE_REMOVE) != (arg_wait_until == WAIT_UNTIL_REMOVED))
116                 return 0;
117 
118         if (arg_wait_until == WAIT_UNTIL_REMOVED)
119                 /* On removed event, the received device may not contain enough information.
120                  * Let's unconditionally check all requested devices are removed. */
121                 return check_and_exit(sd_device_monitor_get_event(monitor));
122 
123         /* For other events, at first check if the received device matches with the requested devices,
124          * to avoid calling check() so many times within a short time. */
125 
126         r = sd_device_get_sysname(device, &name);
127         if (r < 0) {
128                 log_device_warning_errno(device, r, "Failed to get sysname of received device, ignoring: %m");
129                 return 0;
130         }
131 
132         STRV_FOREACH(p, arg_devices) {
133                 const char *s;
134 
135                 if (!path_startswith(*p, "/sys"))
136                         continue;
137 
138                 r = path_find_last_component(*p, false, NULL, &s);
139                 if (r < 0) {
140                         log_warning_errno(r, "Failed to extract filename from \"%s\", ignoring: %m", *p);
141                         continue;
142                 }
143                 if (r == 0)
144                         continue;
145 
146                 if (strneq(s, name, r))
147                         return check_and_exit(sd_device_monitor_get_event(monitor));
148         }
149 
150         r = sd_device_get_devname(device, &name);
151         if (r < 0) {
152                 if (r != -ENOENT)
153                         log_device_warning_errno(device, r, "Failed to get devname of received device, ignoring: %m");
154                 return 0;
155         }
156 
157         if (path_strv_contains(arg_devices, name))
158                 return check_and_exit(sd_device_monitor_get_event(monitor));
159 
160         STRV_FOREACH(p, arg_devices) {
161                 const char *link;
162 
163                 if (!path_startswith(*p, "/dev"))
164                         continue;
165 
166                 FOREACH_DEVICE_DEVLINK(device, link)
167                         if (path_equal(*p, link))
168                                 return check_and_exit(sd_device_monitor_get_event(monitor));
169         }
170 
171         return 0;
172 }
173 
setup_monitor(sd_event * event,sd_device_monitor ** ret)174 static int setup_monitor(sd_event *event, sd_device_monitor **ret) {
175         _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
176         int r;
177 
178         assert(event);
179         assert(ret);
180 
181         r = sd_device_monitor_new(&monitor);
182         if (r < 0)
183                 return r;
184 
185         (void) sd_device_monitor_set_receive_buffer_size(monitor, 128*1024*1024);
186 
187         r = sd_device_monitor_attach_event(monitor, event);
188         if (r < 0)
189                 return r;
190 
191         r = sd_device_monitor_start(monitor, device_monitor_handler, NULL);
192         if (r < 0)
193                 return r;
194 
195         r = sd_event_source_set_description(sd_device_monitor_get_event_source(monitor),
196                                             "device-monitor-event-source");
197         if (r < 0)
198                 return r;
199 
200         *ret = TAKE_PTR(monitor);
201         return 0;
202 }
203 
on_inotify(sd_event_source * s,const struct inotify_event * event,void * userdata)204 static int on_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) {
205         return check_and_exit(sd_event_source_get_event(s));
206 }
207 
setup_inotify(sd_event * event)208 static int setup_inotify(sd_event *event) {
209         _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
210         int r;
211 
212         assert(event);
213 
214         if (!arg_settle)
215                 return 0;
216 
217         r = sd_event_add_inotify(event, &s, "/run/udev" , IN_CREATE | IN_DELETE, on_inotify, NULL);
218         if (r < 0)
219                 return r;
220 
221         r = sd_event_source_set_description(s, "inotify-event-source");
222         if (r < 0)
223                 return r;
224 
225         return sd_event_source_set_floating(s, true);
226 }
227 
setup_timer(sd_event * event)228 static int setup_timer(sd_event *event) {
229         _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
230         int r;
231 
232         assert(event);
233 
234         if (arg_timeout_usec == USEC_INFINITY)
235                 return 0;
236 
237         r = sd_event_add_time_relative(event, &s, CLOCK_BOOTTIME, arg_timeout_usec, 0,
238                                        NULL, INT_TO_PTR(-ETIMEDOUT));
239         if (r < 0)
240                 return r;
241 
242         r = sd_event_source_set_description(s, "timeout-event-source");
243         if (r < 0)
244                 return r;
245 
246         return sd_event_source_set_floating(s, true);
247 }
248 
help(void)249 static int help(void) {
250         printf("%s wait [OPTIONS] DEVICE [DEVICE…]\n\n"
251                "Wait for devices or device symlinks being created.\n\n"
252                "  -h --help             Print this message\n"
253                "  -V --version          Print version of the program\n"
254                "  -t --timeout=SEC      Maximum time to wait for the device\n"
255                "     --initialized=BOOL Wait for devices being initialized by systemd-udevd\n"
256                "     --removed          Wait for devices being removed\n"
257                "     --settle           Also wait for all queued events being processed\n",
258                program_invocation_short_name);
259 
260         return 0;
261 }
262 
parse_argv(int argc,char * argv[])263 static int parse_argv(int argc, char *argv[]) {
264         enum {
265                 ARG_INITIALIZED = 0x100,
266                 ARG_REMOVED,
267                 ARG_SETTLE,
268         };
269 
270         static const struct option options[] = {
271                 { "timeout",     required_argument, NULL, 't'             },
272                 { "initialized", required_argument, NULL, ARG_INITIALIZED },
273                 { "removed",     no_argument,       NULL, ARG_REMOVED     },
274                 { "settle",      no_argument,       NULL, ARG_SETTLE      },
275                 { "help",        no_argument,       NULL, 'h'             },
276                 { "version",     no_argument,       NULL, 'V'             },
277                 {}
278         };
279 
280         int c, r;
281 
282         while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0)
283                 switch (c) {
284                 case 't':
285                         r = parse_sec(optarg, &arg_timeout_usec);
286                         if (r < 0)
287                                 return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg);
288                         break;
289 
290                 case ARG_INITIALIZED:
291                         r = parse_boolean(optarg);
292                         if (r < 0)
293                                 return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg);
294                         arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED;
295                         break;
296 
297                 case ARG_REMOVED:
298                         arg_wait_until = WAIT_UNTIL_REMOVED;
299                         break;
300 
301                 case ARG_SETTLE:
302                         arg_settle = true;
303                         break;
304 
305                 case 'V':
306                         return print_version();
307 
308                 case 'h':
309                         return help();
310 
311                 case '?':
312                         return -EINVAL;
313 
314                 default:
315                         assert_not_reached();
316                 }
317 
318         if (optind >= argc)
319                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
320                                        "Too few arguments, expected at least one device path or device symlink.");
321 
322         arg_devices = strv_copy(argv + optind);
323         if (!arg_devices)
324                 return log_oom();
325 
326         return 1; /* work to do */
327 }
328 
wait_main(int argc,char * argv[],void * userdata)329 int wait_main(int argc, char *argv[], void *userdata) {
330         _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL;
331         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
332         int r;
333 
334         r = parse_argv(argc, argv);
335         if (r <= 0)
336                 return r;
337 
338         STRV_FOREACH(p, arg_devices) {
339                 path_simplify(*p);
340 
341                 if (!path_is_safe(*p))
342                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
343                                                "Device path cannot contain \"..\".");
344 
345                 if (!is_device_path(*p))
346                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
347                                                "Specified path \"%s\" does not start with \"/dev/\" or \"/sys/\".", *p);
348         }
349 
350         /* Check before configuring event sources, as devices may be already initialized. */
351         if (check())
352                 return 0;
353 
354         r = sd_event_default(&event);
355         if (r < 0)
356                 return log_error_errno(r, "Failed to initialize sd-event: %m");
357 
358         r = setup_timer(event);
359         if (r < 0)
360                 return log_error_errno(r, "Failed to set up timeout: %m");
361 
362         r = setup_inotify(event);
363         if (r < 0)
364                 return log_error_errno(r, "Failed to set up inotify: %m");
365 
366         r = setup_monitor(event, &monitor);
367         if (r < 0)
368                 return log_error_errno(r, "Failed to set up device monitor: %m");
369 
370         /* Check before entering the event loop, as devices may be initialized during setting up event sources. */
371         if (check())
372                 return 0;
373 
374         r = sd_event_loop(event);
375         if (r == -ETIMEDOUT)
376                 return log_error_errno(r, "Timed out for waiting devices being %s.",
377                                        wait_until_to_string(arg_wait_until));
378         if (r < 0)
379                 return log_error_errno(r, "Event loop failed: %m");
380 
381         return 0;
382 }
383