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