]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-info.c
udevadm: improve error output when a device is not specified or specified wrong
[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 printf("\n");
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 printf("N: %s\n", str + STRLEN("/dev/"));
136
137 if (device_get_devlink_priority(device, &i) >= 0)
138 printf("L: %i\n", i);
139
140 FOREACH_DEVICE_DEVLINK(device, str)
141 printf("S: %s\n", str + STRLEN("/dev/"));
142
143 FOREACH_DEVICE_PROPERTY(device, str, val)
144 printf("E: %s=%s\n", str, val);
145
146 printf("\n");
147 return 0;
148 }
149
150 static int stat_device(const char *name, bool export, const char *prefix) {
151 struct stat statbuf;
152
153 if (stat(name, &statbuf) != 0)
154 return -errno;
155
156 if (export) {
157 if (!prefix)
158 prefix = "INFO_";
159 printf("%sMAJOR=%u\n"
160 "%sMINOR=%u\n",
161 prefix, major(statbuf.st_dev),
162 prefix, minor(statbuf.st_dev));
163 } else
164 printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
165 return 0;
166 }
167
168 static int export_devices(void) {
169 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
170 sd_device *d;
171 int r;
172
173 r = sd_device_enumerator_new(&e);
174 if (r < 0)
175 return r;
176
177 r = sd_device_enumerator_allow_uninitialized(e);
178 if (r < 0)
179 return r;
180
181 r = device_enumerator_scan_devices(e);
182 if (r < 0)
183 return r;
184
185 FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
186 print_record(d);
187
188 return 0;
189 }
190
191 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
192 struct dirent *dent;
193
194 if (depth <= 0)
195 return;
196
197 FOREACH_DIRENT_ALL(dent, dir, break) {
198 struct stat stats;
199
200 if (dent->d_name[0] == '.')
201 continue;
202 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
203 continue;
204 if ((stats.st_mode & mask) != 0)
205 continue;
206 if (S_ISDIR(stats.st_mode)) {
207 _cleanup_closedir_ DIR *dir2 = NULL;
208
209 dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
210 if (dir2)
211 cleanup_dir(dir2, mask, depth-1);
212
213 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
214 } else
215 (void) unlinkat(dirfd(dir), dent->d_name, 0);
216 }
217 }
218
219 static void cleanup_db(void) {
220 _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL, *dir5 = NULL;
221
222 (void) unlink("/run/udev/queue.bin");
223
224 dir1 = opendir("/run/udev/data");
225 if (dir1)
226 cleanup_dir(dir1, S_ISVTX, 1);
227
228 dir2 = opendir("/run/udev/links");
229 if (dir2)
230 cleanup_dir(dir2, 0, 2);
231
232 dir3 = opendir("/run/udev/tags");
233 if (dir3)
234 cleanup_dir(dir3, 0, 2);
235
236 dir4 = opendir("/run/udev/static_node-tags");
237 if (dir4)
238 cleanup_dir(dir4, 0, 2);
239
240 dir5 = opendir("/run/udev/watch");
241 if (dir5)
242 cleanup_dir(dir5, 0, 1);
243 }
244
245 static int query_device(QueryType query, sd_device* device) {
246 int r;
247
248 assert(device);
249
250 switch(query) {
251 case QUERY_NAME: {
252 const char *node;
253
254 r = sd_device_get_devname(device, &node);
255 if (r < 0)
256 return log_error_errno(r, "No device node found: %m");
257
258 if (arg_root)
259 printf("%s\n", node);
260 else
261 printf("%s\n", node + STRLEN("/dev/"));
262 return 0;
263 }
264
265 case QUERY_SYMLINK: {
266 const char *devlink;
267 bool first = true;
268
269 FOREACH_DEVICE_DEVLINK(device, devlink) {
270 if (!first)
271 printf(" ");
272 if (arg_root)
273 printf("%s", devlink);
274 else
275 printf("%s", devlink + STRLEN("/dev/"));
276
277 first = false;
278 }
279 printf("\n");
280 return 0;
281 }
282
283 case QUERY_PATH: {
284 const char *devpath;
285
286 r = sd_device_get_devpath(device, &devpath);
287 if (r < 0)
288 return log_error_errno(r, "Failed to get device path: %m");
289
290 printf("%s\n", devpath);
291 return 0;
292 }
293
294 case QUERY_PROPERTY: {
295 const char *key, *value;
296
297 FOREACH_DEVICE_PROPERTY(device, key, value)
298 if (arg_export)
299 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
300 else
301 printf("%s=%s\n", key, value);
302 return 0;
303 }
304
305 case QUERY_ALL:
306 return print_record(device);
307 }
308
309 assert_not_reached("unknown query type");
310 return 0;
311 }
312
313 static int help(void) {
314 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
315 "Query sysfs or the udev database.\n\n"
316 " -h --help Print this message\n"
317 " -V --version Print version of the program\n"
318 " -q --query=TYPE Query device information:\n"
319 " name Name of device node\n"
320 " symlink Pointing to node\n"
321 " path sysfs device path\n"
322 " property The device properties\n"
323 " all All values\n"
324 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
325 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
326 " -r --root Prepend dev directory to path names\n"
327 " -a --attribute-walk Print all key matches walking along the chain\n"
328 " of parent devices\n"
329 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
330 " -x --export Export key/value pairs\n"
331 " -P --export-prefix Export the key name with a prefix\n"
332 " -e --export-db Export the content of the udev database\n"
333 " -c --cleanup-db Clean up the udev database\n"
334 , program_invocation_short_name);
335
336 return 0;
337 }
338
339 int info_main(int argc, char *argv[], void *userdata) {
340 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
341 _cleanup_free_ char *name = NULL;
342 int c, r;
343
344 static const struct option options[] = {
345 { "name", required_argument, NULL, 'n' },
346 { "path", required_argument, NULL, 'p' },
347 { "query", required_argument, NULL, 'q' },
348 { "attribute-walk", no_argument, NULL, 'a' },
349 { "cleanup-db", no_argument, NULL, 'c' },
350 { "export-db", no_argument, NULL, 'e' },
351 { "root", no_argument, NULL, 'r' },
352 { "device-id-of-file", required_argument, NULL, 'd' },
353 { "export", no_argument, NULL, 'x' },
354 { "export-prefix", required_argument, NULL, 'P' },
355 { "version", no_argument, NULL, 'V' },
356 { "help", no_argument, NULL, 'h' },
357 {}
358 };
359
360 ActionType action = ACTION_QUERY;
361 QueryType query = QUERY_ALL;
362
363 while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL)) >= 0)
364 switch (c) {
365 case 'n':
366 if (device) {
367 log_error("device already specified");
368 return -EINVAL;
369 }
370
371 r = find_device(optarg, "/dev/", &device);
372 if (r < 0)
373 return log_error_errno(r, "device node not found: %m");
374 break;
375 case 'p':
376 if (device) {
377 log_error("device already specified");
378 return -EINVAL;
379 }
380
381 r = find_device(optarg, "/sys", &device);
382 if (r < 0)
383 return log_error_errno(r, "syspath not found: %m");
384 break;
385 case 'q':
386 action = ACTION_QUERY;
387 if (streq(optarg, "property") || streq(optarg, "env"))
388 query = QUERY_PROPERTY;
389 else if (streq(optarg, "name"))
390 query = QUERY_NAME;
391 else if (streq(optarg, "symlink"))
392 query = QUERY_SYMLINK;
393 else if (streq(optarg, "path"))
394 query = QUERY_PATH;
395 else if (streq(optarg, "all"))
396 query = QUERY_ALL;
397 else {
398 log_error("unknown query type");
399 return -EINVAL;
400 }
401 break;
402 case 'r':
403 arg_root = true;
404 break;
405 case 'd':
406 action = ACTION_DEVICE_ID_FILE;
407 r = free_and_strdup(&name, optarg);
408 if (r < 0)
409 return log_oom();
410 break;
411 case 'a':
412 action = ACTION_ATTRIBUTE_WALK;
413 break;
414 case 'e':
415 return export_devices();
416 case 'c':
417 cleanup_db();
418 return 0;
419 case 'x':
420 arg_export = true;
421 break;
422 case 'P':
423 arg_export_prefix = optarg;
424 break;
425 case 'V':
426 return print_version();
427 case 'h':
428 return help();
429 case '?':
430 return -EINVAL;
431 default:
432 assert_not_reached("Unknown option");
433 }
434
435 if (IN_SET(action, ACTION_QUERY, ACTION_ATTRIBUTE_WALK) &&
436 !device) {
437 /* A device argument is required. It may be an option or a positional arg. */
438
439 if (!argv[optind])
440 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
441 "A device name or path is required");
442
443 r = find_device(argv[optind], NULL, &device);
444 if (r == -EINVAL)
445 return log_error_errno(r, "Bad argument \"%s\", an absolute path in /dev/ or /sys expected: %m",
446 argv[optind]);
447 if (r < 0)
448 return log_error_errno(r, "Unknown device \"%s\": %m", argv[optind]);
449 }
450
451 switch (action) {
452 case ACTION_QUERY:
453 assert(device);
454 return query_device(query, device);
455
456 case ACTION_ATTRIBUTE_WALK:
457 assert(device);
458 return print_device_chain(device);
459
460 case ACTION_DEVICE_ID_FILE:
461 assert(name);
462 return stat_device(name, arg_export, arg_export_prefix);
463 }
464
465 assert_not_reached("Unknown action");
466 }