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