1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <getopt.h>
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11 
12 #include "sd-device.h"
13 
14 #include "alloc-util.h"
15 #include "device-enumerator-private.h"
16 #include "device-private.h"
17 #include "device-util.h"
18 #include "dirent-util.h"
19 #include "errno-util.h"
20 #include "fd-util.h"
21 #include "fileio.h"
22 #include "glyph-util.h"
23 #include "pager.h"
24 #include "sort-util.h"
25 #include "static-destruct.h"
26 #include "string-table.h"
27 #include "string-util.h"
28 #include "terminal-util.h"
29 #include "udev-util.h"
30 #include "udevadm.h"
31 #include "udevadm-util.h"
32 
33 typedef enum ActionType {
34         ACTION_QUERY,
35         ACTION_ATTRIBUTE_WALK,
36         ACTION_DEVICE_ID_FILE,
37         ACTION_TREE,
38 } ActionType;
39 
40 typedef enum QueryType {
41         QUERY_NAME,
42         QUERY_PATH,
43         QUERY_SYMLINK,
44         QUERY_PROPERTY,
45         QUERY_ALL,
46 } QueryType;
47 
48 static char **arg_properties = NULL;
49 static bool arg_root = false;
50 static bool arg_export = false;
51 static bool arg_value = false;
52 static const char *arg_export_prefix = NULL;
53 static usec_t arg_wait_for_initialization_timeout = 0;
54 
55 /* Put a limit on --tree descent level to not exhaust our stack */
56 #define TREE_DEPTH_MAX 64
57 
skip_attribute(const char * name)58 static bool skip_attribute(const char *name) {
59         assert(name);
60 
61         /* Those are either displayed separately or should not be shown at all. */
62         return STR_IN_SET(name,
63                           "uevent",
64                           "dev",
65                           "modalias",
66                           "resource",
67                           "driver",
68                           "subsystem",
69                           "module");
70 }
71 
72 typedef struct SysAttr {
73         const char *name;
74         const char *value;
75 } SysAttr;
76 
77 STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
78 
sysattr_compare(const SysAttr * a,const SysAttr * b)79 static int sysattr_compare(const SysAttr *a, const SysAttr *b) {
80         assert(a);
81         assert(b);
82 
83         return strcmp(a->name, b->name);
84 }
85 
print_all_attributes(sd_device * device,bool is_parent)86 static int print_all_attributes(sd_device *device, bool is_parent) {
87         _cleanup_free_ SysAttr *sysattrs = NULL;
88         const char *name, *value;
89         size_t n_items = 0;
90         int r;
91 
92         assert(device);
93 
94         value = NULL;
95         (void) sd_device_get_devpath(device, &value);
96         printf("  looking at %sdevice '%s':\n", is_parent ? "parent " : "", strempty(value));
97 
98         value = NULL;
99         (void) sd_device_get_sysname(device, &value);
100         printf("    %s==\"%s\"\n", is_parent ? "KERNELS" : "KERNEL", strempty(value));
101 
102         value = NULL;
103         (void) sd_device_get_subsystem(device, &value);
104         printf("    %s==\"%s\"\n", is_parent ? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value));
105 
106         value = NULL;
107         (void) sd_device_get_driver(device, &value);
108         printf("    %s==\"%s\"\n", is_parent ? "DRIVERS" : "DRIVER", strempty(value));
109 
110         FOREACH_DEVICE_SYSATTR(device, name) {
111                 size_t len;
112 
113                 if (skip_attribute(name))
114                         continue;
115 
116                 r = sd_device_get_sysattr_value(device, name, &value);
117                 if (r >= 0) {
118                         /* skip any values that look like a path */
119                         if (value[0] == '/')
120                                 continue;
121 
122                         /* skip nonprintable attributes */
123                         len = strlen(value);
124                         while (len > 0 && isprint((unsigned char) value[len-1]))
125                                 len--;
126                         if (len > 0)
127                                 continue;
128 
129                 } else if (ERRNO_IS_PRIVILEGE(r))
130                         value = "(not readable)";
131                 else
132                         continue;
133 
134                 if (!GREEDY_REALLOC(sysattrs, n_items + 1))
135                         return log_oom();
136 
137                 sysattrs[n_items] = (SysAttr) {
138                         .name = name,
139                         .value = value,
140                 };
141                 n_items++;
142         }
143 
144         typesafe_qsort(sysattrs, n_items, sysattr_compare);
145 
146         for (size_t i = 0; i < n_items; i++)
147                 printf("    %s{%s}==\"%s\"\n", is_parent ? "ATTRS" : "ATTR", sysattrs[i].name, sysattrs[i].value);
148 
149         puts("");
150 
151         return 0;
152 }
153 
print_device_chain(sd_device * device)154 static int print_device_chain(sd_device *device) {
155         sd_device *child, *parent;
156         int r;
157 
158         assert(device);
159 
160         printf("\n"
161                "Udevadm info starts with the device specified by the devpath and then\n"
162                "walks up the chain of parent devices. It prints for every device\n"
163                "found, all possible attributes in the udev rules key format.\n"
164                "A rule to match, can be composed by the attributes of the device\n"
165                "and the attributes from one single parent device.\n"
166                "\n");
167 
168         r = print_all_attributes(device, false);
169         if (r < 0)
170                 return r;
171 
172         for (child = device; sd_device_get_parent(child, &parent) >= 0; child = parent) {
173                 r = print_all_attributes(parent, true);
174                 if (r < 0)
175                         return r;
176         }
177 
178         return 0;
179 }
180 
print_record(sd_device * device,const char * prefix)181 static int print_record(sd_device *device, const char *prefix) {
182         const char *str, *val, *subsys;
183         dev_t devnum;
184         uint64_t q;
185         int i, ifi;
186 
187         assert(device);
188 
189         prefix = strempty(prefix);
190 
191         /* We don't show syspath here, because it's identical to devpath (modulo the "/sys" prefix).
192          *
193          * We don't show action/seqnum here because that only makes sense for records synthesized from
194          * uevents, not for those synthesized from database entries.
195          *
196          * We don't show sysattrs here, because they can be expensive and potentially issue expensive driver
197          * IO.
198          *
199          * Coloring: let's be conservative with coloring. Let's use it to group related fields. Right now:
200          *
201          *     • white for fields that give the device a name
202          *     • green for fields that categorize the device into subsystem/devtype and similar
203          *     • cyan for fields about associated device nodes/symlinks/network interfaces and such
204          *     • magenta for block device diskseq
205          *     • yellow for driver info
206          *     • no color for regular properties */
207 
208         assert_se(sd_device_get_devpath(device, &str) >= 0);
209         printf("%sP: %s%s%s\n", prefix, ansi_highlight_white(), str, ansi_normal());
210 
211         if (sd_device_get_sysname(device, &str) >= 0)
212                 printf("%sM: %s%s%s\n", prefix, ansi_highlight_white(), str, ansi_normal());
213 
214         if (sd_device_get_sysnum(device, &str) >= 0)
215                 printf("%sR: %s%s%s\n", prefix, ansi_highlight_white(), str, ansi_normal());
216 
217         if (sd_device_get_subsystem(device, &subsys) >= 0)
218                 printf("%sU: %s%s%s\n", prefix, ansi_highlight_green(), subsys, ansi_normal());
219 
220         if (sd_device_get_devtype(device, &str) >= 0)
221                 printf("%sT: %s%s%s\n", prefix, ansi_highlight_green(), str, ansi_normal());
222 
223         if (sd_device_get_devnum(device, &devnum) >= 0)
224                 printf("%sD: %s%c %u:%u%s\n",
225                        prefix,
226                        ansi_highlight_cyan(),
227                        streq_ptr(subsys, "block") ? 'b' : 'c', major(devnum), minor(devnum),
228                        ansi_normal());
229 
230         if (sd_device_get_ifindex(device, &ifi) >= 0)
231                 printf("%sI: %s%i%s\n", prefix, ansi_highlight_cyan(), ifi, ansi_normal());
232 
233         if (sd_device_get_devname(device, &str) >= 0) {
234                 assert_se(val = path_startswith(str, "/dev/"));
235                 printf("%sN: %s%s%s\n", prefix, ansi_highlight_cyan(), val, ansi_normal());
236 
237                 if (device_get_devlink_priority(device, &i) >= 0)
238                         printf("%sL: %s%i%s\n", prefix, ansi_highlight_cyan(), i, ansi_normal());
239 
240                 FOREACH_DEVICE_DEVLINK(device, str) {
241                         assert_se(val = path_startswith(str, "/dev/"));
242                         printf("%sS: %s%s%s\n", prefix, ansi_highlight_cyan(), val, ansi_normal());
243                 }
244         }
245 
246         if (sd_device_get_diskseq(device, &q) >= 0)
247                 printf("%sQ: %s%" PRIu64 "%s\n", prefix, ansi_highlight_magenta(), q, ansi_normal());
248 
249         if (sd_device_get_driver(device, &str) >= 0)
250                 printf("%sV: %s%s%s\n", prefix, ansi_highlight_yellow4(), str, ansi_normal());
251 
252         FOREACH_DEVICE_PROPERTY(device, str, val)
253                 printf("%sE: %s=%s\n", prefix, str, val);
254 
255         if (isempty(prefix))
256                 puts("");
257         return 0;
258 }
259 
stat_device(const char * name,bool export,const char * prefix)260 static int stat_device(const char *name, bool export, const char *prefix) {
261         struct stat statbuf;
262 
263         assert(name);
264 
265         if (stat(name, &statbuf) != 0)
266                 return -errno;
267 
268         if (export) {
269                 if (!prefix)
270                         prefix = "INFO_";
271                 printf("%sMAJOR=%u\n"
272                        "%sMINOR=%u\n",
273                        prefix, major(statbuf.st_dev),
274                        prefix, minor(statbuf.st_dev));
275         } else
276                 printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
277         return 0;
278 }
279 
export_devices(void)280 static int export_devices(void) {
281         _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
282         sd_device *d;
283         int r;
284 
285         r = sd_device_enumerator_new(&e);
286         if (r < 0)
287                 return log_oom();
288 
289         r = sd_device_enumerator_allow_uninitialized(e);
290         if (r < 0)
291                 return log_error_errno(r, "Failed to set allowing uninitialized flag: %m");
292 
293         r = device_enumerator_scan_devices(e);
294         if (r < 0)
295                 return log_error_errno(r, "Failed to scan devices: %m");
296 
297         FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
298                 (void) print_record(d, NULL);
299 
300         return 0;
301 }
302 
cleanup_dir(DIR * dir,mode_t mask,int depth)303 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
304         assert(dir);
305 
306         if (depth <= 0)
307                 return;
308 
309         FOREACH_DIRENT_ALL(dent, dir, break) {
310                 struct stat stats;
311 
312                 if (dot_or_dot_dot(dent->d_name))
313                         continue;
314                 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
315                         continue;
316                 if ((stats.st_mode & mask) != 0)
317                         continue;
318                 if (S_ISDIR(stats.st_mode)) {
319                         _cleanup_closedir_ DIR *subdir = NULL;
320 
321                         subdir = xopendirat(dirfd(dir), dent->d_name, O_NOFOLLOW);
322                         if (!subdir)
323                                 log_debug_errno(errno, "Failed to open subdirectory '%s', ignoring: %m", dent->d_name);
324                         else
325                                 cleanup_dir(subdir, mask, depth-1);
326 
327                         (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
328                 } else
329                         (void) unlinkat(dirfd(dir), dent->d_name, 0);
330         }
331 }
332 
333 /*
334  * Assume that dir is a directory with file names matching udev data base
335  * entries for devices in /run/udev/data (such as "b8:16"), and removes
336  * all files except those that haven't been deleted in /run/udev/data
337  * (i.e. they were skipped during db cleanup because of the db_persist flag).
338  */
cleanup_dir_after_db_cleanup(DIR * dir,DIR * datadir)339 static void cleanup_dir_after_db_cleanup(DIR *dir, DIR *datadir) {
340         assert(dir);
341         assert(datadir);
342 
343         FOREACH_DIRENT_ALL(dent, dir, break) {
344                 if (dot_or_dot_dot(dent->d_name))
345                         continue;
346 
347                 if (faccessat(dirfd(datadir), dent->d_name, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
348                         /* The corresponding udev database file still exists.
349                          * Assuming the parsistent flag is set for the database. */
350                         continue;
351 
352                 (void) unlinkat(dirfd(dir), dent->d_name, 0);
353         }
354 }
355 
cleanup_dirs_after_db_cleanup(DIR * dir,DIR * datadir)356 static void cleanup_dirs_after_db_cleanup(DIR *dir, DIR *datadir) {
357         assert(dir);
358         assert(datadir);
359 
360         FOREACH_DIRENT_ALL(dent, dir, break) {
361                 struct stat stats;
362 
363                 if (dot_or_dot_dot(dent->d_name))
364                         continue;
365                 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
366                         continue;
367                 if (S_ISDIR(stats.st_mode)) {
368                         _cleanup_closedir_ DIR *subdir = NULL;
369 
370                         subdir = xopendirat(dirfd(dir), dent->d_name, O_NOFOLLOW);
371                         if (!subdir)
372                                 log_debug_errno(errno, "Failed to open subdirectory '%s', ignoring: %m", dent->d_name);
373                         else
374                                 cleanup_dir_after_db_cleanup(subdir, datadir);
375 
376                         (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
377                 } else
378                         (void) unlinkat(dirfd(dir), dent->d_name, 0);
379         }
380 }
381 
cleanup_db(void)382 static void cleanup_db(void) {
383         _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL;
384 
385         dir1 = opendir("/run/udev/data");
386         if (dir1)
387                 cleanup_dir(dir1, S_ISVTX, 1);
388 
389         dir2 = opendir("/run/udev/links");
390         if (dir2)
391                 cleanup_dirs_after_db_cleanup(dir2, dir1);
392 
393         dir3 = opendir("/run/udev/tags");
394         if (dir3)
395                 cleanup_dirs_after_db_cleanup(dir3, dir1);
396 
397         dir4 = opendir("/run/udev/static_node-tags");
398         if (dir4)
399                 cleanup_dir(dir4, 0, 2);
400 
401         /* Do not remove /run/udev/watch. It will be handled by udevd well on restart.
402          * And should not be removed by external program when udevd is running. */
403 }
404 
query_device(QueryType query,sd_device * device)405 static int query_device(QueryType query, sd_device* device) {
406         int r;
407 
408         assert(device);
409 
410         switch (query) {
411         case QUERY_NAME: {
412                 const char *node;
413 
414                 r = sd_device_get_devname(device, &node);
415                 if (r < 0)
416                         return log_error_errno(r, "No device node found: %m");
417 
418                 if (!arg_root)
419                         assert_se(node = path_startswith(node, "/dev/"));
420                 printf("%s\n", node);
421                 return 0;
422         }
423 
424         case QUERY_SYMLINK: {
425                 const char *devlink, *prefix = "";
426 
427                 FOREACH_DEVICE_DEVLINK(device, devlink) {
428                         if (!arg_root)
429                                 assert_se(devlink = path_startswith(devlink, "/dev/"));
430                         printf("%s%s", prefix, devlink);
431                         prefix = " ";
432                 }
433                 puts("");
434                 return 0;
435         }
436 
437         case QUERY_PATH: {
438                 const char *devpath;
439 
440                 r = sd_device_get_devpath(device, &devpath);
441                 if (r < 0)
442                         return log_error_errno(r, "Failed to get device path: %m");
443 
444                 printf("%s\n", devpath);
445                 return 0;
446         }
447 
448         case QUERY_PROPERTY: {
449                 const char *key, *value;
450 
451                 FOREACH_DEVICE_PROPERTY(device, key, value) {
452                         if (arg_properties && !strv_contains(arg_properties, key))
453                                 continue;
454 
455                         if (arg_export)
456                                 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
457                         else if (arg_value)
458                                 printf("%s\n", value);
459                         else
460                                 printf("%s=%s\n", key, value);
461                 }
462 
463                 return 0;
464         }
465 
466         case QUERY_ALL:
467                 return print_record(device, NULL);
468 
469         default:
470                 assert_not_reached();
471         }
472 }
473 
help(void)474 static int help(void) {
475         printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
476                "Query sysfs or the udev database.\n\n"
477                "  -h --help                   Print this message\n"
478                "  -V --version                Print version of the program\n"
479                "  -q --query=TYPE             Query device information:\n"
480                "       name                     Name of device node\n"
481                "       symlink                  Pointing to node\n"
482                "       path                     sysfs device path\n"
483                "       property                 The device properties\n"
484                "       all                      All values\n"
485                "     --property=NAME          Show only properties by this name\n"
486                "     --value                  When showing properties, print only their values\n"
487                "  -p --path=SYSPATH           sysfs device path used for query or attribute walk\n"
488                "  -n --name=NAME              Node or symlink name used for query or attribute walk\n"
489                "  -r --root                   Prepend dev directory to path names\n"
490                "  -a --attribute-walk         Print all key matches walking along the chain\n"
491                "                              of parent devices\n"
492                "  -t --tree                   Show tree of devices\n"
493                "  -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
494                "  -x --export                 Export key/value pairs\n"
495                "  -P --export-prefix          Export the key name with a prefix\n"
496                "  -e --export-db              Export the content of the udev database\n"
497                "  -c --cleanup-db             Clean up the udev database\n"
498                "  -w --wait-for-initialization[=SECONDS]\n"
499                "                              Wait for device to be initialized\n",
500                program_invocation_short_name);
501 
502         return 0;
503 }
504 
505 static int draw_tree(
506                 sd_device *parent,
507                 sd_device *const array[], size_t n,
508                 const char *prefix,
509                 unsigned level);
510 
output_tree_device(sd_device * device,const char * str,const char * prefix,bool more,sd_device * const array[],size_t n,unsigned level)511 static int output_tree_device(
512                 sd_device *device,
513                 const char *str,
514                 const char *prefix,
515                 bool more,
516                 sd_device *const array[], size_t n,
517                 unsigned level) {
518 
519         _cleanup_free_ char *subprefix = NULL, *subsubprefix = NULL;
520 
521         assert(device);
522         assert(str);
523 
524         prefix = strempty(prefix);
525 
526         printf("%s%s%s\n", prefix, special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT), str);
527 
528         subprefix = strjoin(prefix, special_glyph(more ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
529         if (!subprefix)
530                 return log_oom();
531 
532         subsubprefix = strjoin(subprefix, special_glyph(SPECIAL_GLYPH_VERTICAL_DOTTED), " ");
533         if (!subsubprefix)
534                 return log_oom();
535 
536         (void) print_record(device, subsubprefix);
537 
538         return draw_tree(device, array, n, subprefix, level + 1);
539 }
540 
draw_tree(sd_device * parent,sd_device * const array[],size_t n,const char * prefix,unsigned level)541 static int draw_tree(
542                 sd_device *parent,
543                 sd_device *const array[], size_t n,
544                 const char *prefix,
545                 unsigned level) {
546 
547         const char *parent_path;
548         size_t i = 0;
549         int r;
550 
551         if (n == 0)
552                 return 0;
553 
554         assert(array);
555 
556         if (parent) {
557                 r = sd_device_get_devpath(parent, &parent_path);
558                 if (r < 0)
559                         return log_error_errno(r, "Failed to get sysfs path of parent device: %m");
560         } else
561                 parent_path = NULL;
562 
563         if (level > TREE_DEPTH_MAX) {
564                 log_warning("Eliding tree below '%s', too deep.", strna(parent_path));
565                 return 0;
566         }
567 
568         while (i < n) {
569                 sd_device *device = array[i];
570                 const char *device_path, *str;
571                 bool more = false;
572                 size_t j;
573 
574                 r = sd_device_get_devpath(device, &device_path);
575                 if (r < 0)
576                         return log_error_errno(r, "Failed to get sysfs path of enumerated device: %m");
577 
578                 /* Scan through the subsequent devices looking children of the device we are looking at. */
579                 for (j = i + 1; j < n; j++) {
580                         sd_device *next = array[j];
581                         const char *next_path;
582 
583                         r = sd_device_get_devpath(next, &next_path);
584                         if (r < 0)
585                                 return log_error_errno(r, "Failed to get sysfs of child device: %m");
586 
587                         if (!path_startswith(next_path, device_path)) {
588                                 more = !parent_path || path_startswith(next_path, parent_path);
589                                 break;
590                         }
591                 }
592 
593                 /* Determine the string to display for this node. If we are at the top of the tree, the full
594                  * device path so far, otherwise just the part suffixing the parent's device path. */
595                 str = parent ? ASSERT_PTR(path_startswith(device_path, parent_path)) : device_path;
596 
597                 r = output_tree_device(device, str, prefix, more, array + i + 1, j - i - 1, level);
598                 if (r < 0)
599                         return r;
600 
601                 i = j;
602         }
603 
604         return 0;
605 }
606 
print_tree(sd_device * below)607 static int print_tree(sd_device* below) {
608         _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
609         const char *below_path;
610         sd_device **array;
611         size_t n = 0;
612         int r;
613 
614         if (below) {
615                 r = sd_device_get_devpath(below, &below_path);
616                 if (r < 0)
617                         return log_error_errno(r, "Failed to get sysfs path of device: %m");
618 
619         } else
620                 below_path = NULL;
621 
622         r = sd_device_enumerator_new(&e);
623         if (r < 0)
624                 return log_error_errno(r, "Failed to allocate device enumerator: %m");
625 
626         if (below) {
627                 r = sd_device_enumerator_add_match_parent(e, below);
628                 if (r < 0)
629                         return log_error_errno(r, "Failed to install parent enumerator match: %m");
630         }
631 
632         r = sd_device_enumerator_allow_uninitialized(e);
633         if (r < 0)
634                 return log_error_errno(r, "Failed to enable enumeration of uninitialized devices: %m");
635 
636         r = device_enumerator_scan_devices_and_subsystems(e);
637         if (r < 0)
638                 return log_error_errno(r, "Failed to scan for devices and subsystems: %m");
639 
640         if (below) {
641                 /* This must be called after device_enumerator_scan_devices_and_subsystems(). */
642                 r = device_enumerator_add_parent_devices(e, below);
643                 if (r < 0)
644                         return log_error_errno(r, "Failed to add parent devices: %m");
645         }
646 
647         assert_se(array = device_enumerator_get_devices(e, &n));
648 
649         if (n == 0) {
650                 log_info("No items.");
651                 return 0;
652         }
653 
654         r = draw_tree(NULL, array, n, NULL, 0);
655         if (r < 0)
656                 return r;
657 
658         printf("\n%zu items shown.\n", n);
659         return 0;
660 }
661 
info_main(int argc,char * argv[],void * userdata)662 int info_main(int argc, char *argv[], void *userdata) {
663         _cleanup_strv_free_ char **devices = NULL;
664         _cleanup_free_ char *name = NULL;
665         int c, r, ret;
666 
667         enum {
668                 ARG_PROPERTY = 0x100,
669                 ARG_VALUE,
670         };
671 
672         static const struct option options[] = {
673                 { "attribute-walk",          no_argument,       NULL, 'a'          },
674                 { "tree",                    no_argument,       NULL, 't'          },
675                 { "cleanup-db",              no_argument,       NULL, 'c'          },
676                 { "device-id-of-file",       required_argument, NULL, 'd'          },
677                 { "export",                  no_argument,       NULL, 'x'          },
678                 { "export-db",               no_argument,       NULL, 'e'          },
679                 { "export-prefix",           required_argument, NULL, 'P'          },
680                 { "help",                    no_argument,       NULL, 'h'          },
681                 { "name",                    required_argument, NULL, 'n'          },
682                 { "path",                    required_argument, NULL, 'p'          },
683                 { "property",                required_argument, NULL, ARG_PROPERTY },
684                 { "query",                   required_argument, NULL, 'q'          },
685                 { "root",                    no_argument,       NULL, 'r'          },
686                 { "value",                   no_argument,       NULL, ARG_VALUE    },
687                 { "version",                 no_argument,       NULL, 'V'          },
688                 { "wait-for-initialization", optional_argument, NULL, 'w'          },
689                 {}
690         };
691 
692         ActionType action = ACTION_QUERY;
693         QueryType query = QUERY_ALL;
694 
695         while ((c = getopt_long(argc, argv, "atced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
696                 switch (c) {
697                 case ARG_PROPERTY:
698                         /* Make sure that if the empty property list was specified, we won't show any
699                            properties. */
700                         if (isempty(optarg) && !arg_properties) {
701                                 arg_properties = new0(char*, 1);
702                                 if (!arg_properties)
703                                         return log_oom();
704                         } else {
705                                 r = strv_split_and_extend(&arg_properties, optarg, ",", true);
706                                 if (r < 0)
707                                         return log_oom();
708                         }
709                         break;
710                 case ARG_VALUE:
711                         arg_value = true;
712                         break;
713                 case 'n':
714                 case 'p': {
715                         const char *prefix = c == 'n' ? "/dev/" : "/sys/";
716                         char *path;
717 
718                         path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
719                         if (!path)
720                                 return log_oom();
721 
722                         r = strv_consume(&devices, path);
723                         if (r < 0)
724                                 return log_oom();
725                         break;
726                 }
727 
728                 case 'q':
729                         action = ACTION_QUERY;
730                         if (streq(optarg, "property") || streq(optarg, "env"))
731                                 query = QUERY_PROPERTY;
732                         else if (streq(optarg, "name"))
733                                 query = QUERY_NAME;
734                         else if (streq(optarg, "symlink"))
735                                 query = QUERY_SYMLINK;
736                         else if (streq(optarg, "path"))
737                                 query = QUERY_PATH;
738                         else if (streq(optarg, "all"))
739                                 query = QUERY_ALL;
740                         else
741                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
742                         break;
743                 case 'r':
744                         arg_root = true;
745                         break;
746                 case 'd':
747                         action = ACTION_DEVICE_ID_FILE;
748                         r = free_and_strdup(&name, optarg);
749                         if (r < 0)
750                                 return log_oom();
751                         break;
752                 case 'a':
753                         action = ACTION_ATTRIBUTE_WALK;
754                         break;
755                 case 't':
756                         action = ACTION_TREE;
757                         break;
758                 case 'e':
759                         return export_devices();
760                 case 'c':
761                         cleanup_db();
762                         return 0;
763                 case 'x':
764                         arg_export = true;
765                         break;
766                 case 'P':
767                         arg_export = true;
768                         arg_export_prefix = optarg;
769                         break;
770                 case 'w':
771                         if (optarg) {
772                                 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
773                                 if (r < 0)
774                                         return log_error_errno(r, "Failed to parse timeout value: %m");
775                         } else
776                                 arg_wait_for_initialization_timeout = USEC_INFINITY;
777                         break;
778                 case 'V':
779                         return print_version();
780                 case 'h':
781                         return help();
782                 case '?':
783                         return -EINVAL;
784                 default:
785                         assert_not_reached();
786                 }
787 
788         if (action == ACTION_DEVICE_ID_FILE) {
789                 if (argv[optind])
790                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
791                                                "Positional arguments are not allowed with -d/--device-id-of-file.");
792                 assert(name);
793                 return stat_device(name, arg_export, arg_export_prefix);
794         }
795 
796         r = strv_extend_strv(&devices, argv + optind, false);
797         if (r < 0)
798                 return log_error_errno(r, "Failed to build argument list: %m");
799 
800         if (action != ACTION_TREE && strv_isempty(devices))
801                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
802                                        "A device name or path is required");
803         if (IN_SET(action, ACTION_ATTRIBUTE_WALK, ACTION_TREE) && strv_length(devices) > 1)
804                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
805                                        "Only one device may be specified with -a/--attribute-walk and -t/--tree");
806 
807         if (arg_export && arg_value)
808                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
809                                        "-x/--export or -P/--export-prefix cannot be used with --value");
810 
811         if (strv_isempty(devices)) {
812                 assert(action == ACTION_TREE);
813                 pager_open(0);
814                 return print_tree(NULL);
815         }
816 
817         ret = 0;
818         STRV_FOREACH(p, devices) {
819                 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
820 
821                 r = find_device(*p, NULL, &device);
822                 if (r < 0) {
823                         if (r == -EINVAL)
824                                 log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys/ or a unit name: %m", *p);
825                         else
826                                 log_error_errno(r, "Unknown device \"%s\": %m",  *p);
827 
828                         if (ret == 0)
829                                 ret = r;
830                         continue;
831                 }
832 
833                 if (arg_wait_for_initialization_timeout > 0) {
834                         sd_device *d;
835 
836                         r = device_wait_for_initialization(
837                                         device,
838                                         NULL,
839                                         usec_add(now(CLOCK_MONOTONIC), arg_wait_for_initialization_timeout),
840                                         &d);
841                         if (r < 0)
842                                 return r;
843 
844                         sd_device_unref(device);
845                         device = d;
846                 }
847 
848                 if (action == ACTION_QUERY)
849                         r = query_device(query, device);
850                 else if (action == ACTION_ATTRIBUTE_WALK)
851                         r = print_device_chain(device);
852                 else if (action == ACTION_TREE)
853                         r = print_tree(device);
854                 else
855                         assert_not_reached();
856                 if (r < 0)
857                         return r;
858         }
859 
860         return ret;
861 }
862