]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-info.c
udevadm: allow a .device unit to be specified for query and trigger
[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_(sd_device_unrefp) sd_device *device = 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 if (device) {
365 log_error("device already specified");
366 return -EINVAL;
367 }
368
369 r = find_device(optarg, "/dev/", &device);
370 if (r < 0)
371 return log_error_errno(r, "device node not found: %m");
372 break;
373 case 'p':
374 if (device) {
375 log_error("device already specified");
376 return -EINVAL;
377 }
378
379 r = find_device(optarg, "/sys", &device);
380 if (r < 0)
381 return log_error_errno(r, "syspath not found: %m");
382 break;
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 log_error("unknown query type");
397 return -EINVAL;
398 }
399 break;
400 case 'r':
401 arg_root = true;
402 break;
403 case 'd':
404 action = ACTION_DEVICE_ID_FILE;
405 r = free_and_strdup(&name, optarg);
406 if (r < 0)
407 return log_oom();
408 break;
409 case 'a':
410 action = ACTION_ATTRIBUTE_WALK;
411 break;
412 case 'e':
413 return export_devices();
414 case 'c':
415 cleanup_db();
416 return 0;
417 case 'x':
418 arg_export = true;
419 break;
420 case 'P':
421 arg_export_prefix = optarg;
422 break;
423 case 'V':
424 return print_version();
425 case 'h':
426 return help();
427 case '?':
428 return -EINVAL;
429 default:
430 assert_not_reached("Unknown option");
431 }
432
433 if (IN_SET(action, ACTION_QUERY, ACTION_ATTRIBUTE_WALK) &&
434 !device) {
435 /* A device argument is required. It may be an option or a positional arg. */
436
437 if (!argv[optind])
438 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
439 "A device name or path is required");
440
441 r = find_device(argv[optind], NULL, &device);
442 if (r == -EINVAL)
443 return log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m",
444 argv[optind]);
445 if (r < 0)
446 return log_error_errno(r, "Unknown device \"%s\": %m", argv[optind]);
447 }
448
449 switch (action) {
450 case ACTION_QUERY:
451 assert(device);
452 return query_device(query, device);
453
454 case ACTION_ATTRIBUTE_WALK:
455 assert(device);
456 return print_device_chain(device);
457
458 case ACTION_DEVICE_ID_FILE:
459 assert(name);
460 return stat_device(name, arg_export, arg_export_prefix);
461 }
462
463 assert_not_reached("Unknown action");
464 }