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