]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udevadm-info.c
udevadm: do not remove watch directory
[thirdparty/systemd.git] / src / udev / udevadm-info.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
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 <sys/stat.h>
10 #include <unistd.h>
11
12 #include "sd-device.h"
13
14 #include "alloc-util.h"
15 #include "device-enumerator-private.h"
16 #include "device-private.h"
17 #include "device-util.h"
18 #include "dirent-util.h"
19 #include "fd-util.h"
20 #include "sort-util.h"
21 #include "static-destruct.h"
22 #include "string-table.h"
23 #include "string-util.h"
24 #include "udev-util.h"
25 #include "udevadm-util.h"
26 #include "udevadm.h"
27
28 typedef enum ActionType {
29 ACTION_QUERY,
30 ACTION_ATTRIBUTE_WALK,
31 ACTION_DEVICE_ID_FILE,
32 } ActionType;
33
34 typedef enum QueryType {
35 QUERY_NAME,
36 QUERY_PATH,
37 QUERY_SYMLINK,
38 QUERY_PROPERTY,
39 QUERY_ALL,
40 } QueryType;
41
42 static char **arg_properties = NULL;
43 static bool arg_root = false;
44 static bool arg_export = false;
45 static bool arg_value = false;
46 static const char *arg_export_prefix = NULL;
47 static usec_t arg_wait_for_initialization_timeout = 0;
48
49 static bool skip_attribute(const char *name) {
50 /* Those are either displayed separately or should not be shown at all. */
51 return STR_IN_SET(name,
52 "uevent",
53 "dev",
54 "modalias",
55 "resource",
56 "driver",
57 "subsystem",
58 "module");
59 }
60
61 typedef struct SysAttr {
62 const char *name;
63 const char *value;
64 } SysAttr;
65
66 STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
67
68 static int sysattr_compare(const SysAttr *a, const SysAttr *b) {
69 return strcmp(a->name, b->name);
70 }
71
72 static int print_all_attributes(sd_device *device, bool is_parent) {
73 _cleanup_free_ SysAttr *sysattrs = NULL;
74 const char *name, *value;
75 size_t n_items = 0;
76 int r;
77
78 value = NULL;
79 (void) sd_device_get_devpath(device, &value);
80 printf(" looking at %sdevice '%s':\n", is_parent ? "parent " : "", strempty(value));
81
82 value = NULL;
83 (void) sd_device_get_sysname(device, &value);
84 printf(" %s==\"%s\"\n", is_parent ? "KERNELS" : "KERNEL", strempty(value));
85
86 value = NULL;
87 (void) sd_device_get_subsystem(device, &value);
88 printf(" %s==\"%s\"\n", is_parent ? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value));
89
90 value = NULL;
91 (void) sd_device_get_driver(device, &value);
92 printf(" %s==\"%s\"\n", is_parent ? "DRIVERS" : "DRIVER", strempty(value));
93
94 FOREACH_DEVICE_SYSATTR(device, name) {
95 size_t len;
96
97 if (skip_attribute(name))
98 continue;
99
100 r = sd_device_get_sysattr_value(device, name, &value);
101 if (r >= 0) {
102 /* skip any values that look like a path */
103 if (value[0] == '/')
104 continue;
105
106 /* skip nonprintable attributes */
107 len = strlen(value);
108 while (len > 0 && isprint((unsigned char) value[len-1]))
109 len--;
110 if (len > 0)
111 continue;
112
113 } else if (r == -EPERM)
114 value = "(write-only)";
115 else
116 continue;
117
118 if (!GREEDY_REALLOC(sysattrs, n_items + 1))
119 return log_oom();
120
121 sysattrs[n_items] = (SysAttr) {
122 .name = name,
123 .value = value,
124 };
125 n_items++;
126 }
127
128 typesafe_qsort(sysattrs, n_items, sysattr_compare);
129
130 for (size_t i = 0; i < n_items; i++)
131 printf(" %s{%s}==\"%s\"\n", is_parent ? "ATTRS" : "ATTR", sysattrs[i].name, sysattrs[i].value);
132
133 puts("");
134
135 return 0;
136 }
137
138 static int print_device_chain(sd_device *device) {
139 sd_device *child, *parent;
140 int r;
141
142 printf("\n"
143 "Udevadm info starts with the device specified by the devpath and then\n"
144 "walks up the chain of parent devices. It prints for every device\n"
145 "found, all possible attributes in the udev rules key format.\n"
146 "A rule to match, can be composed by the attributes of the device\n"
147 "and the attributes from one single parent device.\n"
148 "\n");
149
150 r = print_all_attributes(device, false);
151 if (r < 0)
152 return r;
153
154 for (child = device; sd_device_get_parent(child, &parent) >= 0; child = parent) {
155 r = print_all_attributes(parent, true);
156 if (r < 0)
157 return r;
158 }
159
160 return 0;
161 }
162
163 static int print_record(sd_device *device) {
164 const char *str, *val;
165 int i;
166
167 (void) sd_device_get_devpath(device, &str);
168 printf("P: %s\n", str);
169
170 if (sd_device_get_devname(device, &str) >= 0) {
171 assert_se(val = path_startswith(str, "/dev/"));
172 printf("N: %s\n", val);
173 }
174
175 if (device_get_devlink_priority(device, &i) >= 0)
176 printf("L: %i\n", i);
177
178 FOREACH_DEVICE_DEVLINK(device, str) {
179 assert_se(val = path_startswith(str, "/dev/"));
180 printf("S: %s\n", val);
181 }
182
183 FOREACH_DEVICE_PROPERTY(device, str, val)
184 printf("E: %s=%s\n", str, val);
185
186 puts("");
187 return 0;
188 }
189
190 static int stat_device(const char *name, bool export, const char *prefix) {
191 struct stat statbuf;
192
193 if (stat(name, &statbuf) != 0)
194 return -errno;
195
196 if (export) {
197 if (!prefix)
198 prefix = "INFO_";
199 printf("%sMAJOR=%u\n"
200 "%sMINOR=%u\n",
201 prefix, major(statbuf.st_dev),
202 prefix, minor(statbuf.st_dev));
203 } else
204 printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev));
205 return 0;
206 }
207
208 static int export_devices(void) {
209 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
210 sd_device *d;
211 int r;
212
213 r = sd_device_enumerator_new(&e);
214 if (r < 0)
215 return log_oom();
216
217 r = sd_device_enumerator_allow_uninitialized(e);
218 if (r < 0)
219 return log_error_errno(r, "Failed to set allowing uninitialized flag: %m");
220
221 r = device_enumerator_scan_devices(e);
222 if (r < 0)
223 return log_error_errno(r, "Failed to scan devices: %m");
224
225 FOREACH_DEVICE_AND_SUBSYSTEM(e, d)
226 (void) print_record(d);
227
228 return 0;
229 }
230
231 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
232 if (depth <= 0)
233 return;
234
235 assert(dir);
236
237 FOREACH_DIRENT_ALL(dent, dir, break) {
238 struct stat stats;
239
240 if (dot_or_dot_dot(dent->d_name))
241 continue;
242 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
243 continue;
244 if ((stats.st_mode & mask) != 0)
245 continue;
246 if (S_ISDIR(stats.st_mode)) {
247 _cleanup_closedir_ DIR *dir2 = NULL;
248
249 dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
250 if (dir2)
251 cleanup_dir(dir2, mask, depth-1);
252
253 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
254 } else
255 (void) unlinkat(dirfd(dir), dent->d_name, 0);
256 }
257 }
258
259 /*
260 * Assume that dir is a directory with file names matching udev data base
261 * entries for devices in /run/udev/data (such as "b8:16"), and removes
262 * all files except those that haven't been deleted in /run/udev/data
263 * (i.e. they were skipped during db cleanup because of the db_persist flag).
264 * Returns true if the directory is empty after cleanup.
265 */
266 static bool cleanup_dir_after_db_cleanup(DIR *dir, DIR *datadir) {
267 unsigned int kept = 0;
268
269 assert(dir && datadir);
270
271 FOREACH_DIRENT_ALL(dent, dir, break) {
272 struct stat data_stats, link_stats;
273
274 if (dot_or_dot_dot(dent->d_name))
275 continue;
276 if (fstatat(dirfd(dir), dent->d_name, &link_stats, AT_SYMLINK_NOFOLLOW) < 0) {
277 if (errno != ENOENT)
278 kept++;
279 continue;
280 }
281
282 if (fstatat(dirfd(datadir), dent->d_name, &data_stats, 0) < 0)
283 (void) unlinkat(dirfd(dir), dent->d_name,
284 S_ISDIR(link_stats.st_mode) ? AT_REMOVEDIR : 0);
285 else
286 /* The entry still exists under /run/udev/data */
287 kept++;
288 }
289
290 return kept == 0;
291 }
292
293 static void cleanup_dirs_after_db_cleanup(DIR *dir, DIR *datadir) {
294
295 assert(dir && datadir);
296
297 FOREACH_DIRENT_ALL(dent, dir, break) {
298 struct stat stats;
299
300 if (dot_or_dot_dot(dent->d_name))
301 continue;
302 if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
303 continue;
304 if (S_ISDIR(stats.st_mode)) {
305 _cleanup_closedir_ DIR *dir2 = NULL;
306
307 dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
308 if (dir2 && cleanup_dir_after_db_cleanup(dir2, datadir))
309 (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
310 } else
311 (void) unlinkat(dirfd(dir), dent->d_name, 0);
312 }
313 }
314
315 static void cleanup_db(void) {
316 _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL;
317
318 dir1 = opendir("/run/udev/data");
319 if (dir1)
320 cleanup_dir(dir1, S_ISVTX, 1);
321
322 dir2 = opendir("/run/udev/links");
323 if (dir2)
324 cleanup_dirs_after_db_cleanup(dir2, dir1);
325
326 dir3 = opendir("/run/udev/tags");
327 if (dir3)
328 cleanup_dirs_after_db_cleanup(dir3, dir1);
329
330 dir4 = opendir("/run/udev/static_node-tags");
331 if (dir4)
332 cleanup_dir(dir4, 0, 2);
333
334 /* Do not remove /run/udev/watch. It will be handled by udevd well on restart.
335 * And should not be removed by external program when udevd is running. */
336 }
337
338 static int query_device(QueryType query, sd_device* device) {
339 int r;
340
341 assert(device);
342
343 switch(query) {
344 case QUERY_NAME: {
345 const char *node;
346
347 r = sd_device_get_devname(device, &node);
348 if (r < 0)
349 return log_error_errno(r, "No device node found: %m");
350
351 if (!arg_root)
352 assert_se(node = path_startswith(node, "/dev/"));
353 printf("%s\n", node);
354 return 0;
355 }
356
357 case QUERY_SYMLINK: {
358 const char *devlink, *prefix = "";
359
360 FOREACH_DEVICE_DEVLINK(device, devlink) {
361 if (!arg_root)
362 assert_se(devlink = path_startswith(devlink, "/dev/"));
363 printf("%s%s", prefix, devlink);
364 prefix = " ";
365 }
366 puts("");
367 return 0;
368 }
369
370 case QUERY_PATH: {
371 const char *devpath;
372
373 r = sd_device_get_devpath(device, &devpath);
374 if (r < 0)
375 return log_error_errno(r, "Failed to get device path: %m");
376
377 printf("%s\n", devpath);
378 return 0;
379 }
380
381 case QUERY_PROPERTY: {
382 const char *key, *value;
383
384 FOREACH_DEVICE_PROPERTY(device, key, value) {
385 if (arg_properties && !strv_contains(arg_properties, key))
386 continue;
387
388 if (arg_export)
389 printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
390 else if (arg_value)
391 printf("%s\n", value);
392 else
393 printf("%s=%s\n", key, value);
394 }
395
396 return 0;
397 }
398
399 case QUERY_ALL:
400 return print_record(device);
401 }
402
403 assert_not_reached();
404 return 0;
405 }
406
407 static int help(void) {
408 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
409 "Query sysfs or the udev database.\n\n"
410 " -h --help Print this message\n"
411 " -V --version Print version of the program\n"
412 " -q --query=TYPE Query device information:\n"
413 " name Name of device node\n"
414 " symlink Pointing to node\n"
415 " path sysfs device path\n"
416 " property The device properties\n"
417 " all All values\n"
418 " --property=NAME Show only properties by this name\n"
419 " --value When showing properties, print only their values\n"
420 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
421 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
422 " -r --root Prepend dev directory to path names\n"
423 " -a --attribute-walk Print all key matches walking along the chain\n"
424 " of parent devices\n"
425 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
426 " -x --export Export key/value pairs\n"
427 " -P --export-prefix Export the key name with a prefix\n"
428 " -e --export-db Export the content of the udev database\n"
429 " -c --cleanup-db Clean up the udev database\n"
430 " -w --wait-for-initialization[=SECONDS]\n"
431 " Wait for device to be initialized\n",
432 program_invocation_short_name);
433
434 return 0;
435 }
436
437 int info_main(int argc, char *argv[], void *userdata) {
438 _cleanup_strv_free_ char **devices = NULL;
439 _cleanup_free_ char *name = NULL;
440 int c, r;
441
442 enum {
443 ARG_PROPERTY = 0x100,
444 ARG_VALUE,
445 };
446
447 static const struct option options[] = {
448 { "attribute-walk", no_argument, NULL, 'a' },
449 { "cleanup-db", no_argument, NULL, 'c' },
450 { "device-id-of-file", required_argument, NULL, 'd' },
451 { "export", no_argument, NULL, 'x' },
452 { "export-db", no_argument, NULL, 'e' },
453 { "export-prefix", required_argument, NULL, 'P' },
454 { "help", no_argument, NULL, 'h' },
455 { "name", required_argument, NULL, 'n' },
456 { "path", required_argument, NULL, 'p' },
457 { "property", required_argument, NULL, ARG_PROPERTY },
458 { "query", required_argument, NULL, 'q' },
459 { "root", no_argument, NULL, 'r' },
460 { "value", no_argument, NULL, ARG_VALUE },
461 { "version", no_argument, NULL, 'V' },
462 { "wait-for-initialization", optional_argument, NULL, 'w' },
463 {}
464 };
465
466 ActionType action = ACTION_QUERY;
467 QueryType query = QUERY_ALL;
468
469 while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
470 switch (c) {
471 case ARG_PROPERTY:
472 /* Make sure that if the empty property list was specified, we won't show any
473 properties. */
474 if (isempty(optarg) && !arg_properties) {
475 arg_properties = new0(char*, 1);
476 if (!arg_properties)
477 return log_oom();
478 } else {
479 r = strv_split_and_extend(&arg_properties, optarg, ",", true);
480 if (r < 0)
481 return log_oom();
482 }
483 break;
484 case ARG_VALUE:
485 arg_value = true;
486 break;
487 case 'n':
488 case 'p': {
489 const char *prefix = c == 'n' ? "/dev/" : "/sys/";
490 char *path;
491
492 path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg);
493 if (!path)
494 return log_oom();
495
496 r = strv_consume(&devices, path);
497 if (r < 0)
498 return log_oom();
499 break;
500 }
501
502 case 'q':
503 action = ACTION_QUERY;
504 if (streq(optarg, "property") || streq(optarg, "env"))
505 query = QUERY_PROPERTY;
506 else if (streq(optarg, "name"))
507 query = QUERY_NAME;
508 else if (streq(optarg, "symlink"))
509 query = QUERY_SYMLINK;
510 else if (streq(optarg, "path"))
511 query = QUERY_PATH;
512 else if (streq(optarg, "all"))
513 query = QUERY_ALL;
514 else
515 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "unknown query type");
516 break;
517 case 'r':
518 arg_root = true;
519 break;
520 case 'd':
521 action = ACTION_DEVICE_ID_FILE;
522 r = free_and_strdup(&name, optarg);
523 if (r < 0)
524 return log_oom();
525 break;
526 case 'a':
527 action = ACTION_ATTRIBUTE_WALK;
528 break;
529 case 'e':
530 return export_devices();
531 case 'c':
532 cleanup_db();
533 return 0;
534 case 'x':
535 arg_export = true;
536 break;
537 case 'P':
538 arg_export = true;
539 arg_export_prefix = optarg;
540 break;
541 case 'w':
542 if (optarg) {
543 r = parse_sec(optarg, &arg_wait_for_initialization_timeout);
544 if (r < 0)
545 return log_error_errno(r, "Failed to parse timeout value: %m");
546 } else
547 arg_wait_for_initialization_timeout = USEC_INFINITY;
548 break;
549 case 'V':
550 return print_version();
551 case 'h':
552 return help();
553 case '?':
554 return -EINVAL;
555 default:
556 assert_not_reached();
557 }
558
559 if (action == ACTION_DEVICE_ID_FILE) {
560 if (argv[optind])
561 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
562 "Positional arguments are not allowed with -d/--device-id-of-file.");
563 assert(name);
564 return stat_device(name, arg_export, arg_export_prefix);
565 }
566
567 r = strv_extend_strv(&devices, argv + optind, false);
568 if (r < 0)
569 return log_error_errno(r, "Failed to build argument list: %m");
570
571 if (strv_isempty(devices))
572 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
573 "A device name or path is required");
574 if (action == ACTION_ATTRIBUTE_WALK && strv_length(devices) > 1)
575 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
576 "Only one device may be specified with -a/--attribute-walk");
577
578 if (arg_export && arg_value)
579 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
580 "-x/--export or -P/--export-prefix cannot be used with --value");
581
582 char **p;
583 STRV_FOREACH(p, devices) {
584 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
585
586 r = find_device(*p, NULL, &device);
587 if (r == -EINVAL)
588 return log_error_errno(r, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p);
589 if (r < 0)
590 return log_error_errno(r, "Unknown device \"%s\": %m", *p);
591
592 if (arg_wait_for_initialization_timeout > 0) {
593 sd_device *d;
594
595 r = device_wait_for_initialization(
596 device,
597 NULL,
598 usec_add(now(CLOCK_MONOTONIC), arg_wait_for_initialization_timeout),
599 &d);
600 if (r < 0)
601 return r;
602
603 sd_device_unref(device);
604 device = d;
605 }
606
607 if (action == ACTION_QUERY)
608 r = query_device(query, device);
609 else if (action == ACTION_ATTRIBUTE_WALK)
610 r = print_device_chain(device);
611 else
612 assert_not_reached();
613 if (r < 0)
614 return r;
615 }
616
617 return 0;
618 }