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