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