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