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