1 /* SPDX-License-Identifier: GPL-2.0-or-later */
12 #include "sd-device.h"
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"
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"
28 typedef enum ActionType
{
30 ACTION_ATTRIBUTE_WALK
,
31 ACTION_DEVICE_ID_FILE
,
34 typedef enum QueryType
{
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;
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
,
61 typedef struct SysAttr
{
66 STATIC_DESTRUCTOR_REGISTER(arg_properties
, strv_freep
);
68 static int sysattr_compare(const SysAttr
*a
, const SysAttr
*b
) {
69 return strcmp(a
->name
, b
->name
);
72 static int print_all_attributes(sd_device
*device
, bool is_parent
) {
73 _cleanup_free_ SysAttr
*sysattrs
= NULL
;
74 const char *name
, *value
;
79 (void) sd_device_get_devpath(device
, &value
);
80 printf(" looking at %sdevice '%s':\n", is_parent
? "parent " : "", strempty(value
));
83 (void) sd_device_get_sysname(device
, &value
);
84 printf(" %s==\"%s\"\n", is_parent
? "KERNELS" : "KERNEL", strempty(value
));
87 (void) sd_device_get_subsystem(device
, &value
);
88 printf(" %s==\"%s\"\n", is_parent
? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value
));
91 (void) sd_device_get_driver(device
, &value
);
92 printf(" %s==\"%s\"\n", is_parent
? "DRIVERS" : "DRIVER", strempty(value
));
94 FOREACH_DEVICE_SYSATTR(device
, name
) {
97 if (skip_attribute(name
))
100 r
= sd_device_get_sysattr_value(device
, name
, &value
);
102 /* skip any values that look like a path */
106 /* skip nonprintable attributes */
108 while (len
> 0 && isprint((unsigned char) value
[len
-1]))
113 } else if (r
== -EPERM
)
114 value
= "(write-only)";
118 if (!GREEDY_REALLOC(sysattrs
, n_items
+ 1))
121 sysattrs
[n_items
] = (SysAttr
) {
128 typesafe_qsort(sysattrs
, n_items
, sysattr_compare
);
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
);
138 static int print_device_chain(sd_device
*device
) {
139 sd_device
*child
, *parent
;
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"
150 r
= print_all_attributes(device
, false);
154 for (child
= device
; sd_device_get_parent(child
, &parent
) >= 0; child
= parent
) {
155 r
= print_all_attributes(parent
, true);
163 static int print_record(sd_device
*device
) {
164 const char *str
, *val
;
167 (void) sd_device_get_devpath(device
, &str
);
168 printf("P: %s\n", str
);
170 if (sd_device_get_devname(device
, &str
) >= 0) {
171 assert_se(val
= path_startswith(str
, "/dev/"));
172 printf("N: %s\n", val
);
175 if (device_get_devlink_priority(device
, &i
) >= 0)
176 printf("L: %i\n", i
);
178 FOREACH_DEVICE_DEVLINK(device
, str
) {
179 assert_se(val
= path_startswith(str
, "/dev/"));
180 printf("S: %s\n", val
);
183 FOREACH_DEVICE_PROPERTY(device
, str
, val
)
184 printf("E: %s=%s\n", str
, val
);
190 static int stat_device(const char *name
, bool export
, const char *prefix
) {
193 if (stat(name
, &statbuf
) != 0)
199 printf("%sMAJOR=%u\n"
201 prefix
, major(statbuf
.st_dev
),
202 prefix
, minor(statbuf
.st_dev
));
204 printf("%u:%u\n", major(statbuf
.st_dev
), minor(statbuf
.st_dev
));
208 static int export_devices(void) {
209 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*e
= NULL
;
213 r
= sd_device_enumerator_new(&e
);
217 r
= sd_device_enumerator_allow_uninitialized(e
);
219 return log_error_errno(r
, "Failed to set allowing uninitialized flag: %m");
221 r
= device_enumerator_scan_devices(e
);
223 return log_error_errno(r
, "Failed to scan devices: %m");
225 FOREACH_DEVICE_AND_SUBSYSTEM(e
, d
)
226 (void) print_record(d
);
231 static void cleanup_dir(DIR *dir
, mode_t mask
, int depth
) {
237 FOREACH_DIRENT_ALL(dent
, dir
, break) {
240 if (dot_or_dot_dot(dent
->d_name
))
242 if (fstatat(dirfd(dir
), dent
->d_name
, &stats
, AT_SYMLINK_NOFOLLOW
) < 0)
244 if ((stats
.st_mode
& mask
) != 0)
246 if (S_ISDIR(stats
.st_mode
)) {
247 _cleanup_closedir_
DIR *dir2
= NULL
;
249 dir2
= fdopendir(openat(dirfd(dir
), dent
->d_name
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
));
251 cleanup_dir(dir2
, mask
, depth
-1);
253 (void) unlinkat(dirfd(dir
), dent
->d_name
, AT_REMOVEDIR
);
255 (void) unlinkat(dirfd(dir
), dent
->d_name
, 0);
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.
266 static bool cleanup_dir_after_db_cleanup(DIR *dir
, DIR *datadir
) {
267 unsigned int kept
= 0;
269 assert(dir
&& datadir
);
271 FOREACH_DIRENT_ALL(dent
, dir
, break) {
272 struct stat data_stats
, link_stats
;
274 if (dot_or_dot_dot(dent
->d_name
))
276 if (fstatat(dirfd(dir
), dent
->d_name
, &link_stats
, AT_SYMLINK_NOFOLLOW
) < 0) {
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);
286 /* The entry still exists under /run/udev/data */
293 static void cleanup_dirs_after_db_cleanup(DIR *dir
, DIR *datadir
) {
295 assert(dir
&& datadir
);
297 FOREACH_DIRENT_ALL(dent
, dir
, break) {
300 if (dot_or_dot_dot(dent
->d_name
))
302 if (fstatat(dirfd(dir
), dent
->d_name
, &stats
, AT_SYMLINK_NOFOLLOW
) < 0)
304 if (S_ISDIR(stats
.st_mode
)) {
305 _cleanup_closedir_
DIR *dir2
= NULL
;
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
);
311 (void) unlinkat(dirfd(dir
), dent
->d_name
, 0);
315 static void cleanup_db(void) {
316 _cleanup_closedir_
DIR *dir1
= NULL
, *dir2
= NULL
, *dir3
= NULL
, *dir4
= NULL
;
318 dir1
= opendir("/run/udev/data");
320 cleanup_dir(dir1
, S_ISVTX
, 1);
322 dir2
= opendir("/run/udev/links");
324 cleanup_dirs_after_db_cleanup(dir2
, dir1
);
326 dir3
= opendir("/run/udev/tags");
328 cleanup_dirs_after_db_cleanup(dir3
, dir1
);
330 dir4
= opendir("/run/udev/static_node-tags");
332 cleanup_dir(dir4
, 0, 2);
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. */
338 static int query_device(QueryType query
, sd_device
* device
) {
347 r
= sd_device_get_devname(device
, &node
);
349 return log_error_errno(r
, "No device node found: %m");
352 assert_se(node
= path_startswith(node
, "/dev/"));
353 printf("%s\n", node
);
357 case QUERY_SYMLINK
: {
358 const char *devlink
, *prefix
= "";
360 FOREACH_DEVICE_DEVLINK(device
, devlink
) {
362 assert_se(devlink
= path_startswith(devlink
, "/dev/"));
363 printf("%s%s", prefix
, devlink
);
373 r
= sd_device_get_devpath(device
, &devpath
);
375 return log_error_errno(r
, "Failed to get device path: %m");
377 printf("%s\n", devpath
);
381 case QUERY_PROPERTY
: {
382 const char *key
, *value
;
384 FOREACH_DEVICE_PROPERTY(device
, key
, value
) {
385 if (arg_properties
&& !strv_contains(arg_properties
, key
))
389 printf("%s%s='%s'\n", strempty(arg_export_prefix
), key
, value
);
391 printf("%s\n", value
);
393 printf("%s=%s\n", key
, value
);
400 return print_record(device
);
403 assert_not_reached();
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"
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
);
437 int info_main(int argc
, char *argv
[], void *userdata
) {
438 _cleanup_strv_free_
char **devices
= NULL
;
439 _cleanup_free_
char *name
= NULL
;
443 ARG_PROPERTY
= 0x100,
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' },
466 ActionType action
= ACTION_QUERY
;
467 QueryType query
= QUERY_ALL
;
469 while ((c
= getopt_long(argc
, argv
, "aced:n:p:q:rxP:w::Vh", options
, NULL
)) >= 0)
472 /* Make sure that if the empty property list was specified, we won't show any
474 if (isempty(optarg
) && !arg_properties
) {
475 arg_properties
= new0(char*, 1);
479 r
= strv_split_and_extend(&arg_properties
, optarg
, ",", true);
489 const char *prefix
= c
== 'n' ? "/dev/" : "/sys/";
492 path
= path_join(path_startswith(optarg
, prefix
) ? NULL
: prefix
, optarg
);
496 r
= strv_consume(&devices
, path
);
503 action
= ACTION_QUERY
;
504 if (streq(optarg
, "property") || streq(optarg
, "env"))
505 query
= QUERY_PROPERTY
;
506 else if (streq(optarg
, "name"))
508 else if (streq(optarg
, "symlink"))
509 query
= QUERY_SYMLINK
;
510 else if (streq(optarg
, "path"))
512 else if (streq(optarg
, "all"))
515 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "unknown query type");
521 action
= ACTION_DEVICE_ID_FILE
;
522 r
= free_and_strdup(&name
, optarg
);
527 action
= ACTION_ATTRIBUTE_WALK
;
530 return export_devices();
539 arg_export_prefix
= optarg
;
543 r
= parse_sec(optarg
, &arg_wait_for_initialization_timeout
);
545 return log_error_errno(r
, "Failed to parse timeout value: %m");
547 arg_wait_for_initialization_timeout
= USEC_INFINITY
;
550 return print_version();
556 assert_not_reached();
559 if (action
== ACTION_DEVICE_ID_FILE
) {
561 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
562 "Positional arguments are not allowed with -d/--device-id-of-file.");
564 return stat_device(name
, arg_export
, arg_export_prefix
);
567 r
= strv_extend_strv(&devices
, argv
+ optind
, false);
569 return log_error_errno(r
, "Failed to build argument list: %m");
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");
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");
583 STRV_FOREACH(p
, devices
) {
584 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
586 r
= find_device(*p
, NULL
, &device
);
588 return log_error_errno(r
, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p
);
590 return log_error_errno(r
, "Unknown device \"%s\": %m", *p
);
592 if (arg_wait_for_initialization_timeout
> 0) {
595 r
= device_wait_for_initialization(
598 usec_add(now(CLOCK_MONOTONIC
), arg_wait_for_initialization_timeout
),
603 sd_device_unref(device
);
607 if (action
== ACTION_QUERY
)
608 r
= query_device(query
, device
);
609 else if (action
== ACTION_ATTRIBUTE_WALK
)
610 r
= print_device_chain(device
);
612 assert_not_reached();