]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/udevadm-info.c
Merge pull request #32437 from keszybz/notify-fixups-split-out
[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
a6b4b2fa 321static int export_devices(sd_device_enumerator *e) {
13aca847
YW
322 sd_device *d;
323 int r;
912541b0 324
a6b4b2fa 325 assert(e);
912541b0 326
13aca847
YW
327 r = device_enumerator_scan_devices(e);
328 if (r < 0)
df5a4889 329 return log_error_errno(r, "Failed to scan devices: %m");
13aca847 330
cbef829f
ZJS
331 pager_open(arg_pager_flags);
332
13aca847 333 FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
d6e5f170
DDM
334 if (arg_json_format_flags & JSON_FORMAT_OFF)
335 (void) print_record(d, NULL);
336 else {
337 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
338
339 r = record_to_json(d, &v);
340 if (r < 0)
341 return r;
342
343 (void) json_variant_dump(v, arg_json_format_flags, stdout, NULL);
344 }
755700bb 345
912541b0 346 return 0;
bf7ad0ea
KS
347}
348
9ec6e95b 349static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
4881a0d2
YW
350 assert(dir);
351
912541b0
KS
352 if (depth <= 0)
353 return;
354
8fb3f009 355 FOREACH_DIRENT_ALL(dent, dir, break) {
912541b0
KS
356 struct stat stats;
357
28d6e854 358 if (dot_or_dot_dot(dent->d_name))
912541b0 359 continue;
28d6e854 360 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
912541b0
KS
361 continue;
362 if ((stats.st_mode & mask) != 0)
363 continue;
364 if (S_ISDIR(stats.st_mode)) {
7b6d1683 365 _cleanup_closedir_ DIR *subdir = NULL;
912541b0 366
7b6d1683
LP
367 subdir = xopendirat(dirfd(dir), dent->d_name, O_NOFOLLOW);
368 if (!subdir)
369 log_debug_errno(errno, "Failed to open subdirectory '%s', ignoring: %m", dent->d_name);
370 else
371 cleanup_dir(subdir, mask, depth-1);
200c7fa6
LP
372
373 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
374 } else
375 (void) unlinkat(dirfd(dir), dent->d_name, 0);
912541b0 376 }
9ead6627
KS
377}
378
7ec62414
MW
379/*
380 * Assume that dir is a directory with file names matching udev data base
381 * entries for devices in /run/udev/data (such as "b8:16"), and removes
382 * all files except those that haven't been deleted in /run/udev/data
383 * (i.e. they were skipped during db cleanup because of the db_persist flag).
7ec62414 384 */
636ab001 385static void cleanup_dir_after_db_cleanup(DIR *dir, DIR *datadir) {
9e0bd1d6
YW
386 assert(dir);
387 assert(datadir);
7ec62414
MW
388
389 FOREACH_DIRENT_ALL(dent, dir, break) {
7ec62414
MW
390 if (dot_or_dot_dot(dent->d_name))
391 continue;
636ab001
YW
392
393 if (faccessat(dirfd(datadir), dent->d_name, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
394 /* The corresponding udev database file still exists.
2b4cdac9 395 * Assuming the persistent flag is set for the database. */
7ec62414 396 continue;
7ec62414 397
636ab001 398 (void) unlinkat(dirfd(dir), dent->d_name, 0);
7ec62414 399 }
7ec62414
MW
400}
401
402static void cleanup_dirs_after_db_cleanup(DIR *dir, DIR *datadir) {
9e0bd1d6
YW
403 assert(dir);
404 assert(datadir);
7ec62414
MW
405
406 FOREACH_DIRENT_ALL(dent, dir, break) {
407 struct stat stats;
408
409 if (dot_or_dot_dot(dent->d_name))
410 continue;
411 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
412 continue;
413 if (S_ISDIR(stats.st_mode)) {
7b6d1683 414 _cleanup_closedir_ DIR *subdir = NULL;
7ec62414 415
7b6d1683
LP
416 subdir = xopendirat(dirfd(dir), dent->d_name, O_NOFOLLOW);
417 if (!subdir)
418 log_debug_errno(errno, "Failed to open subdirectory '%s', ignoring: %m", dent->d_name);
419 else
420 cleanup_dir_after_db_cleanup(subdir, datadir);
636ab001
YW
421
422 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
7ec62414
MW
423 } else
424 (void) unlinkat(dirfd(dir), dent->d_name, 0);
425 }
426}
427
2024ed61 428static void cleanup_db(void) {
bd979801 429 _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL;
912541b0 430
755700bb 431 dir1 = opendir("/run/udev/data");
13aca847 432 if (dir1)
755700bb 433 cleanup_dir(dir1, S_ISVTX, 1);
912541b0 434
755700bb 435 dir2 = opendir("/run/udev/links");
13aca847 436 if (dir2)
7ec62414 437 cleanup_dirs_after_db_cleanup(dir2, dir1);
912541b0 438
755700bb 439 dir3 = opendir("/run/udev/tags");
13aca847 440 if (dir3)
7ec62414 441 cleanup_dirs_after_db_cleanup(dir3, dir1);
912541b0 442
755700bb 443 dir4 = opendir("/run/udev/static_node-tags");
13aca847 444 if (dir4)
755700bb 445 cleanup_dir(dir4, 0, 2);
84b6ad70 446
bd979801
YW
447 /* Do not remove /run/udev/watch. It will be handled by udevd well on restart.
448 * And should not be removed by external program when udevd is running. */
9ead6627
KS
449}
450
668e7c0c
ZJS
451static int query_device(QueryType query, sd_device* device) {
452 int r;
453
454 assert(device);
455
79893116 456 switch (query) {
668e7c0c
ZJS
457 case QUERY_NAME: {
458 const char *node;
459
460 r = sd_device_get_devname(device, &node);
461 if (r < 0)
462 return log_error_errno(r, "No device node found: %m");
463
d539f791
ZJS
464 if (!arg_root)
465 assert_se(node = path_startswith(node, "/dev/"));
466 printf("%s\n", node);
668e7c0c
ZJS
467 return 0;
468 }
469
470 case QUERY_SYMLINK: {
a1af8372 471 const char *prefix = "";
668e7c0c
ZJS
472
473 FOREACH_DEVICE_DEVLINK(device, devlink) {
d539f791
ZJS
474 if (!arg_root)
475 assert_se(devlink = path_startswith(devlink, "/dev/"));
476 printf("%s%s", prefix, devlink);
477 prefix = " ";
668e7c0c 478 }
d539f791 479 puts("");
668e7c0c
ZJS
480 return 0;
481 }
482
483 case QUERY_PATH: {
484 const char *devpath;
5ac0162c 485
668e7c0c
ZJS
486 r = sd_device_get_devpath(device, &devpath);
487 if (r < 0)
488 return log_error_errno(r, "Failed to get device path: %m");
489
490 printf("%s\n", devpath);
491 return 0;
492 }
493
a1af8372 494 case QUERY_PROPERTY:
6c1482b2
FS
495 FOREACH_DEVICE_PROPERTY(device, key, value) {
496 if (arg_properties && !strv_contains(arg_properties, key))
497 continue;
498
668e7c0c
ZJS
499 if (arg_export)
500 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
6c1482b2
FS
501 else if (arg_value)
502 printf("%s\n", value);
668e7c0c
ZJS
503 else
504 printf("%s=%s\n", key, value);
6c1482b2
FS
505 }
506
668e7c0c 507 return 0;
668e7c0c
ZJS
508
509 case QUERY_ALL:
d6e5f170
DDM
510 if (arg_json_format_flags & JSON_FORMAT_OFF)
511 return print_record(device, NULL);
512 else {
513 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
514
515 r = record_to_json(device, &v);
516 if (r < 0)
517 return r;
518
519 (void) json_variant_dump(v, arg_json_format_flags, stdout, NULL);
520 }
521
522 return 0;
668e7c0c 523
4881a0d2
YW
524 default:
525 assert_not_reached();
526 }
668e7c0c
ZJS
527}
528
529static int help(void) {
5ac0162c
LP
530 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
531 "Query sysfs or the udev database.\n\n"
532 " -h --help Print this message\n"
73527992 533 " -V --version Print version of the program\n"
5ac0162c
LP
534 " -q --query=TYPE Query device information:\n"
535 " name Name of device node\n"
536 " symlink Pointing to node\n"
537 " path sysfs device path\n"
538 " property The device properties\n"
539 " all All values\n"
6c1482b2
FS
540 " --property=NAME Show only properties by this name\n"
541 " --value When showing properties, print only their values\n"
5ac0162c
LP
542 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
543 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
544 " -r --root Prepend dev directory to path names\n"
545 " -a --attribute-walk Print all key matches walking along the chain\n"
546 " of parent devices\n"
9117d94b 547 " -t --tree Show tree of devices\n"
5ac0162c
LP
548 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
549 " -x --export Export key/value pairs\n"
550 " -P --export-prefix Export the key name with a prefix\n"
551 " -e --export-db Export the content of the udev database\n"
552 " -c --cleanup-db Clean up the udev database\n"
ae760f4b 553 " -w --wait-for-initialization[=SECONDS]\n"
b6ec23a0 554 " Wait for device to be initialized\n"
d6e5f170 555 " --no-pager Do not pipe output into a pager\n"
a6b4b2fa
DDM
556 " --json=pretty|short|off Generate JSON output\n"
557 " --subsystem-match=SUBSYSTEM\n"
558 " Query devices matching a subsystem\n"
559 " --subsystem-nomatch=SUBSYSTEM\n"
560 " Query devices not matching a subsystem\n"
561 " --attr-match=FILE[=VALUE]\n"
562 " Query devices that match an attribute\n"
563 " --attr-nomatch=FILE[=VALUE]\n"
564 " Query devices that do not match an attribute\n"
565 " --property-match=KEY=VALUE\n"
566 " Query devices with matching properties\n"
567 " --tag-match=TAG Query devices with a matching tag\n"
568 " --sysname-match=NAME Query devices with this /sys path\n"
569 " --name-match=NAME Query devices with this /dev name\n"
570 " --parent-match=NAME Query devices with this parent device\n"
571 " --initialized-match Query devices that are already initialized\n"
572 " --initialized-nomatch Query devices that are not initialized yet\n",
bc556335 573 program_invocation_short_name);
ee4a776d
YW
574
575 return 0;
5ac0162c
LP
576}
577
9117d94b
LP
578static int draw_tree(
579 sd_device *parent,
580 sd_device *const array[], size_t n,
581 const char *prefix,
582 unsigned level);
583
584static int output_tree_device(
585 sd_device *device,
586 const char *str,
587 const char *prefix,
588 bool more,
589 sd_device *const array[], size_t n,
590 unsigned level) {
591
592 _cleanup_free_ char *subprefix = NULL, *subsubprefix = NULL;
593
594 assert(device);
595 assert(str);
596
597 prefix = strempty(prefix);
598
599 printf("%s%s%s\n", prefix, special_glyph(more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT), str);
600
601 subprefix = strjoin(prefix, special_glyph(more ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
602 if (!subprefix)
603 return log_oom();
604
605 subsubprefix = strjoin(subprefix, special_glyph(SPECIAL_GLYPH_VERTICAL_DOTTED), " ");
606 if (!subsubprefix)
607 return log_oom();
608
609 (void) print_record(device, subsubprefix);
610
611 return draw_tree(device, array, n, subprefix, level + 1);
612}
613
614static int draw_tree(
615 sd_device *parent,
616 sd_device *const array[], size_t n,
617 const char *prefix,
618 unsigned level) {
619
620 const char *parent_path;
621 size_t i = 0;
622 int r;
623
624 if (n == 0)
625 return 0;
626
627 assert(array);
628
629 if (parent) {
630 r = sd_device_get_devpath(parent, &parent_path);
631 if (r < 0)
632 return log_error_errno(r, "Failed to get sysfs path of parent device: %m");
633 } else
634 parent_path = NULL;
635
636 if (level > TREE_DEPTH_MAX) {
637 log_warning("Eliding tree below '%s', too deep.", strna(parent_path));
638 return 0;
639 }
640
641 while (i < n) {
642 sd_device *device = array[i];
643 const char *device_path, *str;
644 bool more = false;
645 size_t j;
646
647 r = sd_device_get_devpath(device, &device_path);
648 if (r < 0)
649 return log_error_errno(r, "Failed to get sysfs path of enumerated device: %m");
650
651 /* Scan through the subsequent devices looking children of the device we are looking at. */
652 for (j = i + 1; j < n; j++) {
653 sd_device *next = array[j];
654 const char *next_path;
655
656 r = sd_device_get_devpath(next, &next_path);
657 if (r < 0)
658 return log_error_errno(r, "Failed to get sysfs of child device: %m");
659
660 if (!path_startswith(next_path, device_path)) {
661 more = !parent_path || path_startswith(next_path, parent_path);
662 break;
663 }
664 }
665
666 /* Determine the string to display for this node. If we are at the top of the tree, the full
667 * device path so far, otherwise just the part suffixing the parent's device path. */
668 str = parent ? ASSERT_PTR(path_startswith(device_path, parent_path)) : device_path;
669
670 r = output_tree_device(device, str, prefix, more, array + i + 1, j - i - 1, level);
671 if (r < 0)
672 return r;
673
674 i = j;
675 }
676
677 return 0;
678}
679
680static int print_tree(sd_device* below) {
681 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
682 const char *below_path;
683 sd_device **array;
684 size_t n = 0;
685 int r;
686
687 if (below) {
688 r = sd_device_get_devpath(below, &below_path);
689 if (r < 0)
690 return log_error_errno(r, "Failed to get sysfs path of device: %m");
691
692 } else
693 below_path = NULL;
694
695 r = sd_device_enumerator_new(&e);
696 if (r < 0)
697 return log_error_errno(r, "Failed to allocate device enumerator: %m");
698
699 if (below) {
700 r = sd_device_enumerator_add_match_parent(e, below);
701 if (r < 0)
702 return log_error_errno(r, "Failed to install parent enumerator match: %m");
703 }
704
705 r = sd_device_enumerator_allow_uninitialized(e);
706 if (r < 0)
707 return log_error_errno(r, "Failed to enable enumeration of uninitialized devices: %m");
708
709 r = device_enumerator_scan_devices_and_subsystems(e);
710 if (r < 0)
711 return log_error_errno(r, "Failed to scan for devices and subsystems: %m");
712
bd4297e7
YW
713 if (below) {
714 /* This must be called after device_enumerator_scan_devices_and_subsystems(). */
715 r = device_enumerator_add_parent_devices(e, below);
716 if (r < 0)
717 return log_error_errno(r, "Failed to add parent devices: %m");
718 }
719
9117d94b
LP
720 assert_se(array = device_enumerator_get_devices(e, &n));
721
722 if (n == 0) {
723 log_info("No items.");
724 return 0;
725 }
726
727 r = draw_tree(NULL, array, n, NULL, 0);
728 if (r < 0)
729 return r;
730
731 printf("\n%zu items shown.\n", n);
732 return 0;
733}
734
a6b4b2fa
DDM
735static int ensure_device_enumerator(sd_device_enumerator **e) {
736 int r;
737
738 assert(e);
739
740 if (*e)
741 return 0;
742
743 r = sd_device_enumerator_new(e);
744 if (r < 0)
745 return log_error_errno(r, "Failed to create device enumerator: %m");
746
747 r = sd_device_enumerator_allow_uninitialized(*e);
748 if (r < 0)
749 return log_error_errno(r, "Failed to allow uninitialized devices: %m");
750
751 return 0;
752}
753
754static int parse_key_value_argument(const char *s, char **key, char **value) {
755 _cleanup_free_ char *k = NULL, *v = NULL;
756 int r;
757
758 assert(s);
759 assert(key);
760 assert(value);
761
4f495126 762 r = extract_many_words(&s, "=", EXTRACT_DONT_COALESCE_SEPARATORS, &k, &v);
a6b4b2fa
DDM
763 if (r < 0)
764 return log_error_errno(r, "Failed to parse key/value pair %s: %m", s);
765 if (r < 2)
766 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in key/value pair %s.", s);
767
768 if (!filename_is_valid(k))
769 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid key name", k);
770
771 free_and_replace(*key, k);
772 free_and_replace(*value, v);
773 return 0;
774}
775
3d05193e 776int info_main(int argc, char *argv[], void *userdata) {
a6b4b2fa 777 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
3c79311a 778 _cleanup_strv_free_ char **devices = NULL;
c4abe719 779 _cleanup_free_ char *name = NULL;
7ba77d8f 780 int c, r, ret;
912541b0 781
6c1482b2
FS
782 enum {
783 ARG_PROPERTY = 0x100,
784 ARG_VALUE,
b6ec23a0 785 ARG_NO_PAGER,
d6e5f170 786 ARG_JSON,
a6b4b2fa
DDM
787 ARG_SUBSYSTEM_MATCH,
788 ARG_SUBSYSTEM_NOMATCH,
789 ARG_ATTR_MATCH,
790 ARG_ATTR_NOMATCH,
791 ARG_PROPERTY_MATCH,
792 ARG_TAG_MATCH,
793 ARG_SYSNAME_MATCH,
794 ARG_NAME_MATCH,
795 ARG_PARENT_MATCH,
796 ARG_INITIALIZED_MATCH,
797 ARG_INITIALIZED_NOMATCH,
6c1482b2
FS
798 };
799
912541b0 800 static const struct option options[] = {
a6b4b2fa
DDM
801 { "attribute-walk", no_argument, NULL, 'a' },
802 { "tree", no_argument, NULL, 't' },
803 { "cleanup-db", no_argument, NULL, 'c' },
804 { "device-id-of-file", required_argument, NULL, 'd' },
805 { "export", no_argument, NULL, 'x' },
806 { "export-db", no_argument, NULL, 'e' },
807 { "export-prefix", required_argument, NULL, 'P' },
808 { "help", no_argument, NULL, 'h' },
809 { "name", required_argument, NULL, 'n' },
810 { "path", required_argument, NULL, 'p' },
811 { "property", required_argument, NULL, ARG_PROPERTY },
812 { "query", required_argument, NULL, 'q' },
813 { "root", no_argument, NULL, 'r' },
814 { "value", no_argument, NULL, ARG_VALUE },
815 { "version", no_argument, NULL, 'V' },
816 { "wait-for-initialization", optional_argument, NULL, 'w' },
817 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
818 { "json", required_argument, NULL, ARG_JSON },
819 { "subsystem-match", required_argument, NULL, ARG_SUBSYSTEM_MATCH },
820 { "subsystem-nomatch", required_argument, NULL, ARG_SUBSYSTEM_NOMATCH },
821 { "attr-match", required_argument, NULL, ARG_ATTR_MATCH },
822 { "attr-nomatch", required_argument, NULL, ARG_ATTR_NOMATCH },
823 { "property-match", required_argument, NULL, ARG_PROPERTY_MATCH },
824 { "tag-match", required_argument, NULL, ARG_TAG_MATCH },
825 { "sysname-match", required_argument, NULL, ARG_SYSNAME_MATCH },
826 { "name-match", required_argument, NULL, ARG_NAME_MATCH },
827 { "parent-match", required_argument, NULL, ARG_PARENT_MATCH },
828 { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH },
829 { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH },
912541b0
KS
830 {}
831 };
832
668e7c0c
ZJS
833 ActionType action = ACTION_QUERY;
834 QueryType query = QUERY_ALL;
912541b0 835
9117d94b 836 while ((c = getopt_long(argc, argv, "atced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
7643ac9a 837 switch (c) {
6c1482b2
FS
838 case ARG_PROPERTY:
839 /* Make sure that if the empty property list was specified, we won't show any
840 properties. */
841 if (isempty(optarg) && !arg_properties) {
842 arg_properties = new0(char*, 1);
843 if (!arg_properties)
844 return log_oom();
845 } else {
846 r = strv_split_and_extend(&arg_properties, optarg, ",", true);
847 if (r < 0)
848 return log_oom();
849 }
850 break;
851 case ARG_VALUE:
852 arg_value = true;
853 break;
ee4a776d 854 case 'n':
3c79311a
ZJS
855 case 'p': {
856 const char *prefix = c == 'n' ? "/dev/" : "/sys/";
857 char *path;
4f5d327a 858
3c79311a
ZJS
859 path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
860 if (!path)
861 return log_oom();
4f5d327a 862
3c79311a 863 r = strv_consume(&devices, path);
13aca847 864 if (r < 0)
3c79311a 865 return log_oom();
912541b0 866 break;
3c79311a
ZJS
867 }
868
912541b0
KS
869 case 'q':
870 action = ACTION_QUERY;
44433ebd 871 if (streq(optarg, "property") || streq(optarg, "env"))
912541b0 872 query = QUERY_PROPERTY;
44433ebd 873 else if (streq(optarg, "name"))
912541b0 874 query = QUERY_NAME;
44433ebd 875 else if (streq(optarg, "symlink"))
912541b0 876 query = QUERY_SYMLINK;
44433ebd 877 else if (streq(optarg, "path"))
912541b0 878 query = QUERY_PATH;
44433ebd 879 else if (streq(optarg, "all"))
912541b0 880 query = QUERY_ALL;
47c8fcbe
YW
881 else
882 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
912541b0
KS
883 break;
884 case 'r':
668e7c0c 885 arg_root = true;
912541b0 886 break;
912541b0
KS
887 case 'd':
888 action = ACTION_DEVICE_ID_FILE;
1cb7d29d
YW
889 r = free_and_strdup(&name, optarg);
890 if (r < 0)
c4abe719 891 return log_oom();
912541b0
KS
892 break;
893 case 'a':
894 action = ACTION_ATTRIBUTE_WALK;
895 break;
9117d94b
LP
896 case 't':
897 action = ACTION_TREE;
898 break;
912541b0 899 case 'e':
d6e5f170
DDM
900 action = ACTION_EXPORT;
901 break;
912541b0 902 case 'c':
2024ed61 903 cleanup_db();
44433ebd 904 return 0;
912541b0 905 case 'x':
668e7c0c 906 arg_export = true;
912541b0
KS
907 break;
908 case 'P':
2277e845 909 arg_export = true;
668e7c0c 910 arg_export_prefix = optarg;
912541b0 911 break;
ae760f4b
YW
912 case 'w':
913 if (optarg) {
914 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
915 if (r < 0)
916 return log_error_errno(r, "Failed to parse timeout value: %m");
917 } else
918 arg_wait_for_initialization_timeout = USEC_INFINITY;
919 break;
912541b0 920 case 'V':
51b006e1 921 return print_version();
912541b0 922 case 'h':
ee4a776d 923 return help();
b6ec23a0
ZJS
924 case ARG_NO_PAGER:
925 arg_pager_flags |= PAGER_DISABLE;
926 break;
d6e5f170
DDM
927
928 case ARG_JSON:
929 r = parse_json_argument(optarg, &arg_json_format_flags);
930 if (r <= 0)
931 return r;
932 break;
933
a6b4b2fa
DDM
934 case ARG_SUBSYSTEM_MATCH:
935 case ARG_SUBSYSTEM_NOMATCH:
936 r = ensure_device_enumerator(&e);
937 if (r < 0)
938 return r;
939
940 r = sd_device_enumerator_add_match_subsystem(e, optarg, c == ARG_SUBSYSTEM_MATCH);
941 if (r < 0)
942 return log_error_errno(r, "Failed to add%s subsystem match '%s': %m",
943 c == ARG_SUBSYSTEM_MATCH ? "" : " negative", optarg);
944
945 break;
946
947 case ARG_ATTR_MATCH:
948 case ARG_ATTR_NOMATCH: {
949 _cleanup_free_ char *k = NULL, *v = NULL;
950
951 r = ensure_device_enumerator(&e);
952 if (r < 0)
953 return r;
954
955 r = parse_key_value_argument(optarg, &k, &v);
956 if (r < 0)
957 return r;
958
959 r = sd_device_enumerator_add_match_sysattr(e, k, v, c == ARG_ATTR_MATCH);
960 if (r < 0)
961 return log_error_errno(r, "Failed to add%s sysattr match '%s=%s': %m",
962 c == ARG_ATTR_MATCH ? "" : " negative", k, v);
963 break;
964 }
965
966 case ARG_PROPERTY_MATCH: {
967 _cleanup_free_ char *k = NULL, *v = NULL;
968
969 r = ensure_device_enumerator(&e);
970 if (r < 0)
971 return r;
972
973 r = parse_key_value_argument(optarg, &k, &v);
974 if (r < 0)
975 return r;
976
977 r = sd_device_enumerator_add_match_property_required(e, k, v);
978 if (r < 0)
979 return log_error_errno(r, "Failed to add property match '%s=%s': %m", k, v);
980 break;
981 }
982
983 case ARG_TAG_MATCH:
984 r = ensure_device_enumerator(&e);
985 if (r < 0)
986 return r;
987
988 r = sd_device_enumerator_add_match_tag(e, optarg);
989 if (r < 0)
990 return log_error_errno(r, "Failed to add tag match '%s': %m", optarg);
991 break;
992
993 case ARG_SYSNAME_MATCH:
994 r = ensure_device_enumerator(&e);
995 if (r < 0)
996 return r;
997
998 r = sd_device_enumerator_add_match_sysname(e, optarg);
999 if (r < 0)
1000 return log_error_errno(r, "Failed to add sysname match '%s': %m", optarg);
1001 break;
1002
1003 case ARG_NAME_MATCH:
1004 case ARG_PARENT_MATCH: {
1005 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
1006
1007 r = find_device(optarg, c == ARG_NAME_MATCH ? "/dev" : "/sys", &dev);
1008 if (r < 0)
1009 return log_error_errno(r, "Failed to open the device '%s': %m", optarg);
1010
1011 r = ensure_device_enumerator(&e);
1012 if (r < 0)
1013 return r;
1014
1015 r = device_enumerator_add_match_parent_incremental(e, dev);
1016 if (r < 0)
1017 return log_error_errno(r, "Failed to add parent match '%s': %m", optarg);
1018 break;
1019 }
1020
1021 case ARG_INITIALIZED_MATCH:
1022 case ARG_INITIALIZED_NOMATCH:
1023 r = ensure_device_enumerator(&e);
1024 if (r < 0)
1025 return r;
1026
1027 r = device_enumerator_add_match_is_initialized(e, c == ARG_INITIALIZED_MATCH ? MATCH_INITIALIZED_YES : MATCH_INITIALIZED_NO);
1028 if (r < 0)
1029 return log_error_errno(r, "Failed to set initialized filter: %m");
1030 break;
1031
ee4a776d
YW
1032 case '?':
1033 return -EINVAL;
912541b0 1034 default:
04499a70 1035 assert_not_reached();
912541b0 1036 }
912541b0 1037
3c79311a
ZJS
1038 if (action == ACTION_DEVICE_ID_FILE) {
1039 if (argv[optind])
668e7c0c 1040 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
3c79311a
ZJS
1041 "Positional arguments are not allowed with -d/--device-id-of-file.");
1042 assert(name);
1043 return stat_device(name, arg_export, arg_export_prefix);
668e7c0c 1044 }
13aca847 1045
a6b4b2fa
DDM
1046 if (action == ACTION_EXPORT) {
1047 r = ensure_device_enumerator(&e);
1048 if (r < 0)
1049 return r;
1050
1051 return export_devices(e);
1052 }
d6e5f170 1053
3c79311a
ZJS
1054 r = strv_extend_strv(&devices, argv + optind, false);
1055 if (r < 0)
1056 return log_error_errno(r, "Failed to build argument list: %m");
19a29798 1057
9117d94b 1058 if (action != ACTION_TREE && strv_isempty(devices))
3c79311a
ZJS
1059 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1060 "A device name or path is required");
9117d94b 1061 if (IN_SET(action, ACTION_ATTRIBUTE_WALK, ACTION_TREE) && strv_length(devices) > 1)
3c79311a 1062 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
9117d94b 1063 "Only one device may be specified with -a/--attribute-walk and -t/--tree");
668e7c0c 1064
6c1482b2
FS
1065 if (arg_export && arg_value)
1066 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1067 "-x/--export or -P/--export-prefix cannot be used with --value");
1068
cbef829f
ZJS
1069 pager_open(arg_pager_flags);
1070
9117d94b
LP
1071 if (strv_isempty(devices)) {
1072 assert(action == ACTION_TREE);
9117d94b
LP
1073 return print_tree(NULL);
1074 }
1075
7ba77d8f 1076 ret = 0;
3c79311a
ZJS
1077 STRV_FOREACH(p, devices) {
1078 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
1079
1080 r = find_device(*p, NULL, &device);
7ba77d8f
LP
1081 if (r < 0) {
1082 if (r == -EINVAL)
1083 log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys/ or a unit name: %m", *p);
1084 else
1085 log_error_errno(r, "Unknown device \"%s\": %m", *p);
1086
1087 if (ret == 0)
1088 ret = r;
1089 continue;
1090 }
3c79311a 1091
ae760f4b 1092 if (arg_wait_for_initialization_timeout > 0) {
7a555216
YW
1093 sd_device *d;
1094
9e3d9067
LP
1095 r = device_wait_for_initialization(
1096 device,
1097 NULL,
4f89ce0c 1098 arg_wait_for_initialization_timeout,
9e3d9067 1099 &d);
ae760f4b
YW
1100 if (r < 0)
1101 return r;
7a555216
YW
1102
1103 sd_device_unref(device);
1104 device = d;
ae760f4b
YW
1105 }
1106
3c79311a
ZJS
1107 if (action == ACTION_QUERY)
1108 r = query_device(query, device);
1109 else if (action == ACTION_ATTRIBUTE_WALK)
1110 r = print_device_chain(device);
9117d94b
LP
1111 else if (action == ACTION_TREE)
1112 r = print_tree(device);
3c79311a 1113 else
04499a70 1114 assert_not_reached();
3c79311a
ZJS
1115 if (r < 0)
1116 return r;
912541b0 1117 }
87171e46 1118
7ba77d8f 1119 return ret;
87171e46 1120}