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