]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-info.c
Merge pull request #16491 from keszybz/udev-logging
[thirdparty/systemd.git] / src / udev / udevadm-info.c
1 /* SPDX-License-Identifier: GPL-2.0+ */
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(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 (void) unlink("/run/udev/queue.bin");
253
254 dir1 = opendir("/run/udev/data");
255 if (dir1)
256 cleanup_dir(dir1, S_ISVTX, 1);
257
258 dir2 = opendir("/run/udev/links");
259 if (dir2)
260 cleanup_dir(dir2, 0, 2);
261
262 dir3 = opendir("/run/udev/tags");
263 if (dir3)
264 cleanup_dir(dir3, 0, 2);
265
266 dir4 = opendir("/run/udev/static_node-tags");
267 if (dir4)
268 cleanup_dir(dir4, 0, 2);
269
270 dir5 = opendir("/run/udev/watch");
271 if (dir5)
272 cleanup_dir(dir5, 0, 1);
273 }
274
275 static int query_device(QueryType query, sd_device* device) {
276 int r;
277
278 assert(device);
279
280 switch(query) {
281 case QUERY_NAME: {
282 const char *node;
283
284 r = sd_device_get_devname(device, &node);
285 if (r < 0)
286 return log_error_errno(r, "No device node found: %m");
287
288 if (!arg_root)
289 assert_se(node = path_startswith(node, "/dev/"));
290 printf("%s\n", node);
291 return 0;
292 }
293
294 case QUERY_SYMLINK: {
295 const char *devlink, *prefix = "";
296
297 FOREACH_DEVICE_DEVLINK(device, devlink) {
298 if (!arg_root)
299 assert_se(devlink = path_startswith(devlink, "/dev/"));
300 printf("%s%s", prefix, devlink);
301 prefix = " ";
302 }
303 puts("");
304 return 0;
305 }
306
307 case QUERY_PATH: {
308 const char *devpath;
309
310 r = sd_device_get_devpath(device, &devpath);
311 if (r < 0)
312 return log_error_errno(r, "Failed to get device path: %m");
313
314 printf("%s\n", devpath);
315 return 0;
316 }
317
318 case QUERY_PROPERTY: {
319 const char *key, *value;
320
321 FOREACH_DEVICE_PROPERTY(device, key, value)
322 if (arg_export)
323 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
324 else
325 printf("%s=%s\n", key, value);
326 return 0;
327 }
328
329 case QUERY_ALL:
330 return print_record(device);
331 }
332
333 assert_not_reached("unknown query type");
334 return 0;
335 }
336
337 static int help(void) {
338 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
339 "Query sysfs or the udev database.\n\n"
340 " -h --help Print this message\n"
341 " -V --version Print version of the program\n"
342 " -q --query=TYPE Query device information:\n"
343 " name Name of device node\n"
344 " symlink Pointing to node\n"
345 " path sysfs device path\n"
346 " property The device properties\n"
347 " all All values\n"
348 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
349 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
350 " -r --root Prepend dev directory to path names\n"
351 " -a --attribute-walk Print all key matches walking along the chain\n"
352 " of parent devices\n"
353 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
354 " -x --export Export key/value pairs\n"
355 " -P --export-prefix Export the key name with a prefix\n"
356 " -e --export-db Export the content of the udev database\n"
357 " -c --cleanup-db Clean up the udev database\n"
358 " -w --wait-for-initialization[=SECONDS]\n"
359 " Wait for device to be initialized\n"
360 , program_invocation_short_name);
361
362 return 0;
363 }
364
365 int info_main(int argc, char *argv[], void *userdata) {
366 _cleanup_strv_free_ char **devices = NULL;
367 _cleanup_free_ char *name = NULL;
368 int c, r;
369
370 static const struct option options[] = {
371 { "name", required_argument, NULL, 'n' },
372 { "path", required_argument, NULL, 'p' },
373 { "query", required_argument, NULL, 'q' },
374 { "attribute-walk", no_argument, NULL, 'a' },
375 { "cleanup-db", no_argument, NULL, 'c' },
376 { "export-db", no_argument, NULL, 'e' },
377 { "root", no_argument, NULL, 'r' },
378 { "device-id-of-file", required_argument, NULL, 'd' },
379 { "export", no_argument, NULL, 'x' },
380 { "export-prefix", required_argument, NULL, 'P' },
381 { "wait-for-initialization", optional_argument, NULL, 'w' },
382 { "version", no_argument, NULL, 'V' },
383 { "help", no_argument, NULL, 'h' },
384 {}
385 };
386
387 ActionType action = ACTION_QUERY;
388 QueryType query = QUERY_ALL;
389
390 while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
391 switch (c) {
392 case 'n':
393 case 'p': {
394 const char *prefix = c == 'n' ? "/dev/" : "/sys/";
395 char *path;
396
397 path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
398 if (!path)
399 return log_oom();
400
401 r = strv_consume(&devices, path);
402 if (r < 0)
403 return log_oom();
404 break;
405 }
406
407 case 'q':
408 action = ACTION_QUERY;
409 if (streq(optarg, "property") || streq(optarg, "env"))
410 query = QUERY_PROPERTY;
411 else if (streq(optarg, "name"))
412 query = QUERY_NAME;
413 else if (streq(optarg, "symlink"))
414 query = QUERY_SYMLINK;
415 else if (streq(optarg, "path"))
416 query = QUERY_PATH;
417 else if (streq(optarg, "all"))
418 query = QUERY_ALL;
419 else
420 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
421 break;
422 case 'r':
423 arg_root = true;
424 break;
425 case 'd':
426 action = ACTION_DEVICE_ID_FILE;
427 r = free_and_strdup(&name, optarg);
428 if (r < 0)
429 return log_oom();
430 break;
431 case 'a':
432 action = ACTION_ATTRIBUTE_WALK;
433 break;
434 case 'e':
435 return export_devices();
436 case 'c':
437 cleanup_db();
438 return 0;
439 case 'x':
440 arg_export = true;
441 break;
442 case 'P':
443 arg_export = true;
444 arg_export_prefix = optarg;
445 break;
446 case 'w':
447 if (optarg) {
448 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
449 if (r < 0)
450 return log_error_errno(r, "Failed to parse timeout value: %m");
451 } else
452 arg_wait_for_initialization_timeout = USEC_INFINITY;
453 break;
454 case 'V':
455 return print_version();
456 case 'h':
457 return help();
458 case '?':
459 return -EINVAL;
460 default:
461 assert_not_reached("Unknown option");
462 }
463
464 if (action == ACTION_DEVICE_ID_FILE) {
465 if (argv[optind])
466 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
467 "Positional arguments are not allowed with -d/--device-id-of-file.");
468 assert(name);
469 return stat_device(name, arg_export, arg_export_prefix);
470 }
471
472 r = strv_extend_strv(&devices, argv + optind, false);
473 if (r < 0)
474 return log_error_errno(r, "Failed to build argument list: %m");
475
476 if (strv_isempty(devices))
477 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
478 "A device name or path is required");
479 if (action == ACTION_ATTRIBUTE_WALK && strv_length(devices) > 1)
480 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
481 "Only one device may be specified with -a/--attribute-walk");
482
483 char **p;
484 STRV_FOREACH(p, devices) {
485 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
486
487 r = find_device(*p, NULL, &device);
488 if (r == -EINVAL)
489 return log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p);
490 if (r < 0)
491 return log_error_errno(r, "Unknown device \"%s\": %m", *p);
492
493 if (arg_wait_for_initialization_timeout > 0) {
494 sd_device *d;
495
496 r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, &d);
497 if (r < 0)
498 return r;
499
500 sd_device_unref(device);
501 device = d;
502 }
503
504 if (action == ACTION_QUERY)
505 r = query_device(query, device);
506 else if (action == ACTION_ATTRIBUTE_WALK)
507 r = print_device_chain(device);
508 else
509 assert_not_reached("Unknown action");
510 if (r < 0)
511 return r;
512 }
513
514 return 0;
515 }