]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-info.c
992764b80ce64d56c0116cac5777f93f75553ce0
[thirdparty/systemd.git] / src / udev / udevadm-info.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <getopt.h>
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11
12 #include "sd-device.h"
13
14 #include "alloc-util.h"
15 #include "device-enumerator-private.h"
16 #include "device-private.h"
17 #include "device-util.h"
18 #include "dirent-util.h"
19 #include "errno-util.h"
20 #include "fd-util.h"
21 #include "sort-util.h"
22 #include "static-destruct.h"
23 #include "string-table.h"
24 #include "string-util.h"
25 #include "udev-util.h"
26 #include "udevadm-util.h"
27 #include "udevadm.h"
28
29 typedef enum ActionType {
30 ACTION_QUERY,
31 ACTION_ATTRIBUTE_WALK,
32 ACTION_DEVICE_ID_FILE,
33 } ActionType;
34
35 typedef enum QueryType {
36 QUERY_NAME,
37 QUERY_PATH,
38 QUERY_SYMLINK,
39 QUERY_PROPERTY,
40 QUERY_ALL,
41 } QueryType;
42
43 static char **arg_properties = NULL;
44 static bool arg_root = false;
45 static bool arg_export = false;
46 static bool arg_value = false;
47 static const char *arg_export_prefix = NULL;
48 static usec_t arg_wait_for_initialization_timeout = 0;
49
50 static bool skip_attribute(const char *name) {
51 assert(name);
52
53 /* Those are either displayed separately or should not be shown at all. */
54 return STR_IN_SET(name,
55 "uevent",
56 "dev",
57 "modalias",
58 "resource",
59 "driver",
60 "subsystem",
61 "module");
62 }
63
64 typedef struct SysAttr {
65 const char *name;
66 const char *value;
67 } SysAttr;
68
69 STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
70
71 static int sysattr_compare(const SysAttr *a, const SysAttr *b) {
72 assert(a);
73 assert(b);
74
75 return strcmp(a->name, b->name);
76 }
77
78 static int print_all_attributes(sd_device *device, bool is_parent) {
79 _cleanup_free_ SysAttr *sysattrs = NULL;
80 const char *name, *value;
81 size_t n_items = 0;
82 int r;
83
84 assert(device);
85
86 value = NULL;
87 (void) sd_device_get_devpath(device, &value);
88 printf(" looking at %sdevice '%s':\n", is_parent ? "parent " : "", strempty(value));
89
90 value = NULL;
91 (void) sd_device_get_sysname(device, &value);
92 printf(" %s==\"%s\"\n", is_parent ? "KERNELS" : "KERNEL", strempty(value));
93
94 value = NULL;
95 (void) sd_device_get_subsystem(device, &value);
96 printf(" %s==\"%s\"\n", is_parent ? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value));
97
98 value = NULL;
99 (void) sd_device_get_driver(device, &value);
100 printf(" %s==\"%s\"\n", is_parent ? "DRIVERS" : "DRIVER", strempty(value));
101
102 FOREACH_DEVICE_SYSATTR(device, name) {
103 size_t len;
104
105 if (skip_attribute(name))
106 continue;
107
108 r = sd_device_get_sysattr_value(device, name, &value);
109 if (r >= 0) {
110 /* skip any values that look like a path */
111 if (value[0] == '/')
112 continue;
113
114 /* skip nonprintable attributes */
115 len = strlen(value);
116 while (len > 0 && isprint((unsigned char) value[len-1]))
117 len--;
118 if (len > 0)
119 continue;
120
121 } else if (ERRNO_IS_PRIVILEGE(r))
122 value = "(not readable)";
123 else
124 continue;
125
126 if (!GREEDY_REALLOC(sysattrs, n_items + 1))
127 return log_oom();
128
129 sysattrs[n_items] = (SysAttr) {
130 .name = name,
131 .value = value,
132 };
133 n_items++;
134 }
135
136 typesafe_qsort(sysattrs, n_items, sysattr_compare);
137
138 for (size_t i = 0; i < n_items; i++)
139 printf(" %s{%s}==\"%s\"\n", is_parent ? "ATTRS" : "ATTR", sysattrs[i].name, sysattrs[i].value);
140
141 puts("");
142
143 return 0;
144 }
145
146 static int print_device_chain(sd_device *device) {
147 sd_device *child, *parent;
148 int r;
149
150 assert(device);
151
152 printf("\n"
153 "Udevadm info starts with the device specified by the devpath and then\n"
154 "walks up the chain of parent devices. It prints for every device\n"
155 "found, all possible attributes in the udev rules key format.\n"
156 "A rule to match, can be composed by the attributes of the device\n"
157 "and the attributes from one single parent device.\n"
158 "\n");
159
160 r = print_all_attributes(device, false);
161 if (r < 0)
162 return r;
163
164 for (child = device; sd_device_get_parent(child, &parent) >= 0; child = parent) {
165 r = print_all_attributes(parent, true);
166 if (r < 0)
167 return r;
168 }
169
170 return 0;
171 }
172
173 static int print_record(sd_device *device) {
174 const char *str, *val;
175 int i;
176
177 assert(device);
178
179 (void) sd_device_get_devpath(device, &str);
180 printf("P: %s\n", str);
181
182 if (sd_device_get_devname(device, &str) >= 0) {
183 assert_se(val = path_startswith(str, "/dev/"));
184 printf("N: %s\n", val);
185 }
186
187 if (device_get_devlink_priority(device, &i) >= 0)
188 printf("L: %i\n", i);
189
190 FOREACH_DEVICE_DEVLINK(device, str) {
191 assert_se(val = path_startswith(str, "/dev/"));
192 printf("S: %s\n", val);
193 }
194
195 FOREACH_DEVICE_PROPERTY(device, str, val)
196 printf("E: %s=%s\n", str, val);
197
198 puts("");
199 return 0;
200 }
201
202 static int stat_device(const char *name, bool export, const char *prefix) {
203 struct stat statbuf;
204
205 assert(name);
206
207 if (stat(name, &statbuf) != 0)
208 return -errno;
209
210 if (export) {
211 if (!prefix)
212 prefix = "INFO_";
213 printf("%sMAJOR=%u\n"
214 "%sMINOR=%u\n",
215 prefix, major(statbuf.st_dev),
216 prefix, minor(statbuf.st_dev));
217 } else
218 printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
219 return 0;
220 }
221
222 static int export_devices(void) {
223 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
224 sd_device *d;
225 int r;
226
227 r = sd_device_enumerator_new(&e);
228 if (r < 0)
229 return log_oom();
230
231 r = sd_device_enumerator_allow_uninitialized(e);
232 if (r < 0)
233 return log_error_errno(r, "Failed to set allowing uninitialized flag: %m");
234
235 r = device_enumerator_scan_devices(e);
236 if (r < 0)
237 return log_error_errno(r, "Failed to scan devices: %m");
238
239 FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
240 (void) print_record(d);
241
242 return 0;
243 }
244
245 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
246 assert(dir);
247
248 if (depth <= 0)
249 return;
250
251 FOREACH_DIRENT_ALL(dent, dir, break) {
252 struct stat stats;
253
254 if (dot_or_dot_dot(dent->d_name))
255 continue;
256 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
257 continue;
258 if ((stats.st_mode & mask) != 0)
259 continue;
260 if (S_ISDIR(stats.st_mode)) {
261 _cleanup_closedir_ DIR *dir2 = NULL;
262
263 dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
264 if (dir2)
265 cleanup_dir(dir2, mask, depth-1);
266
267 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
268 } else
269 (void) unlinkat(dirfd(dir), dent->d_name, 0);
270 }
271 }
272
273 /*
274 * Assume that dir is a directory with file names matching udev data base
275 * entries for devices in /run/udev/data (such as "b8:16"), and removes
276 * all files except those that haven't been deleted in /run/udev/data
277 * (i.e. they were skipped during db cleanup because of the db_persist flag).
278 */
279 static void cleanup_dir_after_db_cleanup(DIR *dir, DIR *datadir) {
280 assert(dir);
281 assert(datadir);
282
283 FOREACH_DIRENT_ALL(dent, dir, break) {
284 if (dot_or_dot_dot(dent->d_name))
285 continue;
286
287 if (faccessat(dirfd(datadir), dent->d_name, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
288 /* The corresponding udev database file still exists.
289 * Assuming the parsistent flag is set for the database. */
290 continue;
291
292 (void) unlinkat(dirfd(dir), dent->d_name, 0);
293 }
294 }
295
296 static void cleanup_dirs_after_db_cleanup(DIR *dir, DIR *datadir) {
297 assert(dir);
298 assert(datadir);
299
300 FOREACH_DIRENT_ALL(dent, dir, break) {
301 struct stat stats;
302
303 if (dot_or_dot_dot(dent->d_name))
304 continue;
305 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
306 continue;
307 if (S_ISDIR(stats.st_mode)) {
308 _cleanup_closedir_ DIR *dir2 = NULL;
309
310 dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
311 if (dir2)
312 cleanup_dir_after_db_cleanup(dir2, datadir);
313
314 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
315 } else
316 (void) unlinkat(dirfd(dir), dent->d_name, 0);
317 }
318 }
319
320 static void cleanup_db(void) {
321 _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL;
322
323 dir1 = opendir("/run/udev/data");
324 if (dir1)
325 cleanup_dir(dir1, S_ISVTX, 1);
326
327 dir2 = opendir("/run/udev/links");
328 if (dir2)
329 cleanup_dirs_after_db_cleanup(dir2, dir1);
330
331 dir3 = opendir("/run/udev/tags");
332 if (dir3)
333 cleanup_dirs_after_db_cleanup(dir3, dir1);
334
335 dir4 = opendir("/run/udev/static_node-tags");
336 if (dir4)
337 cleanup_dir(dir4, 0, 2);
338
339 /* Do not remove /run/udev/watch. It will be handled by udevd well on restart.
340 * And should not be removed by external program when udevd is running. */
341 }
342
343 static int query_device(QueryType query, sd_device* device) {
344 int r;
345
346 assert(device);
347
348 switch(query) {
349 case QUERY_NAME: {
350 const char *node;
351
352 r = sd_device_get_devname(device, &node);
353 if (r < 0)
354 return log_error_errno(r, "No device node found: %m");
355
356 if (!arg_root)
357 assert_se(node = path_startswith(node, "/dev/"));
358 printf("%s\n", node);
359 return 0;
360 }
361
362 case QUERY_SYMLINK: {
363 const char *devlink, *prefix = "";
364
365 FOREACH_DEVICE_DEVLINK(device, devlink) {
366 if (!arg_root)
367 assert_se(devlink = path_startswith(devlink, "/dev/"));
368 printf("%s%s", prefix, devlink);
369 prefix = " ";
370 }
371 puts("");
372 return 0;
373 }
374
375 case QUERY_PATH: {
376 const char *devpath;
377
378 r = sd_device_get_devpath(device, &devpath);
379 if (r < 0)
380 return log_error_errno(r, "Failed to get device path: %m");
381
382 printf("%s\n", devpath);
383 return 0;
384 }
385
386 case QUERY_PROPERTY: {
387 const char *key, *value;
388
389 FOREACH_DEVICE_PROPERTY(device, key, value) {
390 if (arg_properties && !strv_contains(arg_properties, key))
391 continue;
392
393 if (arg_export)
394 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
395 else if (arg_value)
396 printf("%s\n", value);
397 else
398 printf("%s=%s\n", key, value);
399 }
400
401 return 0;
402 }
403
404 case QUERY_ALL:
405 return print_record(device);
406
407 default:
408 assert_not_reached();
409 }
410 }
411
412 static int help(void) {
413 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
414 "Query sysfs or the udev database.\n\n"
415 " -h --help Print this message\n"
416 " -V --version Print version of the program\n"
417 " -q --query=TYPE Query device information:\n"
418 " name Name of device node\n"
419 " symlink Pointing to node\n"
420 " path sysfs device path\n"
421 " property The device properties\n"
422 " all All values\n"
423 " --property=NAME Show only properties by this name\n"
424 " --value When showing properties, print only their values\n"
425 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
426 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
427 " -r --root Prepend dev directory to path names\n"
428 " -a --attribute-walk Print all key matches walking along the chain\n"
429 " of parent devices\n"
430 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
431 " -x --export Export key/value pairs\n"
432 " -P --export-prefix Export the key name with a prefix\n"
433 " -e --export-db Export the content of the udev database\n"
434 " -c --cleanup-db Clean up the udev database\n"
435 " -w --wait-for-initialization[=SECONDS]\n"
436 " Wait for device to be initialized\n",
437 program_invocation_short_name);
438
439 return 0;
440 }
441
442 int info_main(int argc, char *argv[], void *userdata) {
443 _cleanup_strv_free_ char **devices = NULL;
444 _cleanup_free_ char *name = NULL;
445 int c, r;
446
447 enum {
448 ARG_PROPERTY = 0x100,
449 ARG_VALUE,
450 };
451
452 static const struct option options[] = {
453 { "attribute-walk", no_argument, NULL, 'a' },
454 { "cleanup-db", no_argument, NULL, 'c' },
455 { "device-id-of-file", required_argument, NULL, 'd' },
456 { "export", no_argument, NULL, 'x' },
457 { "export-db", no_argument, NULL, 'e' },
458 { "export-prefix", required_argument, NULL, 'P' },
459 { "help", no_argument, NULL, 'h' },
460 { "name", required_argument, NULL, 'n' },
461 { "path", required_argument, NULL, 'p' },
462 { "property", required_argument, NULL, ARG_PROPERTY },
463 { "query", required_argument, NULL, 'q' },
464 { "root", no_argument, NULL, 'r' },
465 { "value", no_argument, NULL, ARG_VALUE },
466 { "version", no_argument, NULL, 'V' },
467 { "wait-for-initialization", optional_argument, NULL, 'w' },
468 {}
469 };
470
471 ActionType action = ACTION_QUERY;
472 QueryType query = QUERY_ALL;
473
474 while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
475 switch (c) {
476 case ARG_PROPERTY:
477 /* Make sure that if the empty property list was specified, we won't show any
478 properties. */
479 if (isempty(optarg) && !arg_properties) {
480 arg_properties = new0(char*, 1);
481 if (!arg_properties)
482 return log_oom();
483 } else {
484 r = strv_split_and_extend(&arg_properties, optarg, ",", true);
485 if (r < 0)
486 return log_oom();
487 }
488 break;
489 case ARG_VALUE:
490 arg_value = true;
491 break;
492 case 'n':
493 case 'p': {
494 const char *prefix = c == 'n' ? "/dev/" : "/sys/";
495 char *path;
496
497 path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
498 if (!path)
499 return log_oom();
500
501 r = strv_consume(&devices, path);
502 if (r < 0)
503 return log_oom();
504 break;
505 }
506
507 case 'q':
508 action = ACTION_QUERY;
509 if (streq(optarg, "property") || streq(optarg, "env"))
510 query = QUERY_PROPERTY;
511 else if (streq(optarg, "name"))
512 query = QUERY_NAME;
513 else if (streq(optarg, "symlink"))
514 query = QUERY_SYMLINK;
515 else if (streq(optarg, "path"))
516 query = QUERY_PATH;
517 else if (streq(optarg, "all"))
518 query = QUERY_ALL;
519 else
520 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
521 break;
522 case 'r':
523 arg_root = true;
524 break;
525 case 'd':
526 action = ACTION_DEVICE_ID_FILE;
527 r = free_and_strdup(&name, optarg);
528 if (r < 0)
529 return log_oom();
530 break;
531 case 'a':
532 action = ACTION_ATTRIBUTE_WALK;
533 break;
534 case 'e':
535 return export_devices();
536 case 'c':
537 cleanup_db();
538 return 0;
539 case 'x':
540 arg_export = true;
541 break;
542 case 'P':
543 arg_export = true;
544 arg_export_prefix = optarg;
545 break;
546 case 'w':
547 if (optarg) {
548 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
549 if (r < 0)
550 return log_error_errno(r, "Failed to parse timeout value: %m");
551 } else
552 arg_wait_for_initialization_timeout = USEC_INFINITY;
553 break;
554 case 'V':
555 return print_version();
556 case 'h':
557 return help();
558 case '?':
559 return -EINVAL;
560 default:
561 assert_not_reached();
562 }
563
564 if (action == ACTION_DEVICE_ID_FILE) {
565 if (argv[optind])
566 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
567 "Positional arguments are not allowed with -d/--device-id-of-file.");
568 assert(name);
569 return stat_device(name, arg_export, arg_export_prefix);
570 }
571
572 r = strv_extend_strv(&devices, argv + optind, false);
573 if (r < 0)
574 return log_error_errno(r, "Failed to build argument list: %m");
575
576 if (strv_isempty(devices))
577 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
578 "A device name or path is required");
579 if (action == ACTION_ATTRIBUTE_WALK && strv_length(devices) > 1)
580 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
581 "Only one device may be specified with -a/--attribute-walk");
582
583 if (arg_export && arg_value)
584 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
585 "-x/--export or -P/--export-prefix cannot be used with --value");
586
587 char **p;
588 STRV_FOREACH(p, devices) {
589 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
590
591 r = find_device(*p, NULL, &device);
592 if (r == -EINVAL)
593 return log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p);
594 if (r < 0)
595 return log_error_errno(r, "Unknown device \"%s\": %m", *p);
596
597 if (arg_wait_for_initialization_timeout > 0) {
598 sd_device *d;
599
600 r = device_wait_for_initialization(
601 device,
602 NULL,
603 usec_add(now(CLOCK_MONOTONIC), arg_wait_for_initialization_timeout),
604 &d);
605 if (r < 0)
606 return r;
607
608 sd_device_unref(device);
609 device = d;
610 }
611
612 if (action == ACTION_QUERY)
613 r = query_device(query, device);
614 else if (action == ACTION_ATTRIBUTE_WALK)
615 r = print_device_chain(device);
616 else
617 assert_not_reached();
618 if (r < 0)
619 return r;
620 }
621
622 return 0;
623 }