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