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