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