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