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
) {
235 FOREACH_DIRENT_ALL(dent
, dir
, break) {
238 if (dent
->d_name
[0] == '.')
240 if (fstatat(dirfd(dir
), dent
->d_name
, &stats
, AT_SYMLINK_NOFOLLOW
) != 0)
242 if ((stats
.st_mode
& mask
) != 0)
244 if (S_ISDIR(stats
.st_mode
)) {
245 _cleanup_closedir_
DIR *dir2
= NULL
;
247 dir2
= fdopendir(openat(dirfd(dir
), dent
->d_name
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
));
249 cleanup_dir(dir2
, mask
, depth
-1);
251 (void) unlinkat(dirfd(dir
), dent
->d_name
, AT_REMOVEDIR
);
253 (void) unlinkat(dirfd(dir
), dent
->d_name
, 0);
257 static void cleanup_db(void) {
258 _cleanup_closedir_
DIR *dir1
= NULL
, *dir2
= NULL
, *dir3
= NULL
, *dir4
= NULL
, *dir5
= NULL
;
260 dir1
= opendir("/run/udev/data");
262 cleanup_dir(dir1
, S_ISVTX
, 1);
264 dir2
= opendir("/run/udev/links");
266 cleanup_dir(dir2
, 0, 2);
268 dir3
= opendir("/run/udev/tags");
270 cleanup_dir(dir3
, 0, 2);
272 dir4
= opendir("/run/udev/static_node-tags");
274 cleanup_dir(dir4
, 0, 2);
276 dir5
= opendir("/run/udev/watch");
278 cleanup_dir(dir5
, 0, 1);
281 static int query_device(QueryType query
, sd_device
* device
) {
290 r
= sd_device_get_devname(device
, &node
);
292 return log_error_errno(r
, "No device node found: %m");
295 assert_se(node
= path_startswith(node
, "/dev/"));
296 printf("%s\n", node
);
300 case QUERY_SYMLINK
: {
301 const char *devlink
, *prefix
= "";
303 FOREACH_DEVICE_DEVLINK(device
, devlink
) {
305 assert_se(devlink
= path_startswith(devlink
, "/dev/"));
306 printf("%s%s", prefix
, devlink
);
316 r
= sd_device_get_devpath(device
, &devpath
);
318 return log_error_errno(r
, "Failed to get device path: %m");
320 printf("%s\n", devpath
);
324 case QUERY_PROPERTY
: {
325 const char *key
, *value
;
327 FOREACH_DEVICE_PROPERTY(device
, key
, value
) {
328 if (arg_properties
&& !strv_contains(arg_properties
, key
))
332 printf("%s%s='%s'\n", strempty(arg_export_prefix
), key
, value
);
334 printf("%s\n", value
);
336 printf("%s=%s\n", key
, value
);
343 return print_record(device
);
346 assert_not_reached();
350 static int help(void) {
351 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
352 "Query sysfs or the udev database.\n\n"
353 " -h --help Print this message\n"
354 " -V --version Print version of the program\n"
355 " -q --query=TYPE Query device information:\n"
356 " name Name of device node\n"
357 " symlink Pointing to node\n"
358 " path sysfs device path\n"
359 " property The device properties\n"
361 " --property=NAME Show only properties by this name\n"
362 " --value When showing properties, print only their values\n"
363 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
364 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
365 " -r --root Prepend dev directory to path names\n"
366 " -a --attribute-walk Print all key matches walking along the chain\n"
367 " of parent devices\n"
368 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
369 " -x --export Export key/value pairs\n"
370 " -P --export-prefix Export the key name with a prefix\n"
371 " -e --export-db Export the content of the udev database\n"
372 " -c --cleanup-db Clean up the udev database\n"
373 " -w --wait-for-initialization[=SECONDS]\n"
374 " Wait for device to be initialized\n",
375 program_invocation_short_name
);
380 int info_main(int argc
, char *argv
[], void *userdata
) {
381 _cleanup_strv_free_
char **devices
= NULL
;
382 _cleanup_free_
char *name
= NULL
;
386 ARG_PROPERTY
= 0x100,
390 static const struct option options
[] = {
391 { "attribute-walk", no_argument
, NULL
, 'a' },
392 { "cleanup-db", no_argument
, NULL
, 'c' },
393 { "device-id-of-file", required_argument
, NULL
, 'd' },
394 { "export", no_argument
, NULL
, 'x' },
395 { "export-db", no_argument
, NULL
, 'e' },
396 { "export-prefix", required_argument
, NULL
, 'P' },
397 { "help", no_argument
, NULL
, 'h' },
398 { "name", required_argument
, NULL
, 'n' },
399 { "path", required_argument
, NULL
, 'p' },
400 { "property", required_argument
, NULL
, ARG_PROPERTY
},
401 { "query", required_argument
, NULL
, 'q' },
402 { "root", no_argument
, NULL
, 'r' },
403 { "value", no_argument
, NULL
, ARG_VALUE
},
404 { "version", no_argument
, NULL
, 'V' },
405 { "wait-for-initialization", optional_argument
, NULL
, 'w' },
409 ActionType action
= ACTION_QUERY
;
410 QueryType query
= QUERY_ALL
;
412 while ((c
= getopt_long(argc
, argv
, "aced:n:p:q:rxP:w::Vh", options
, NULL
)) >= 0)
415 /* Make sure that if the empty property list was specified, we won't show any
417 if (isempty(optarg
) && !arg_properties
) {
418 arg_properties
= new0(char*, 1);
422 r
= strv_split_and_extend(&arg_properties
, optarg
, ",", true);
432 const char *prefix
= c
== 'n' ? "/dev/" : "/sys/";
435 path
= path_join(path_startswith(optarg
, prefix
) ? NULL
: prefix
, optarg
);
439 r
= strv_consume(&devices
, path
);
446 action
= ACTION_QUERY
;
447 if (streq(optarg
, "property") || streq(optarg
, "env"))
448 query
= QUERY_PROPERTY
;
449 else if (streq(optarg
, "name"))
451 else if (streq(optarg
, "symlink"))
452 query
= QUERY_SYMLINK
;
453 else if (streq(optarg
, "path"))
455 else if (streq(optarg
, "all"))
458 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "unknown query type");
464 action
= ACTION_DEVICE_ID_FILE
;
465 r
= free_and_strdup(&name
, optarg
);
470 action
= ACTION_ATTRIBUTE_WALK
;
473 return export_devices();
482 arg_export_prefix
= optarg
;
486 r
= parse_sec(optarg
, &arg_wait_for_initialization_timeout
);
488 return log_error_errno(r
, "Failed to parse timeout value: %m");
490 arg_wait_for_initialization_timeout
= USEC_INFINITY
;
493 return print_version();
499 assert_not_reached();
502 if (action
== ACTION_DEVICE_ID_FILE
) {
504 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
505 "Positional arguments are not allowed with -d/--device-id-of-file.");
507 return stat_device(name
, arg_export
, arg_export_prefix
);
510 r
= strv_extend_strv(&devices
, argv
+ optind
, false);
512 return log_error_errno(r
, "Failed to build argument list: %m");
514 if (strv_isempty(devices
))
515 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
516 "A device name or path is required");
517 if (action
== ACTION_ATTRIBUTE_WALK
&& strv_length(devices
) > 1)
518 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
519 "Only one device may be specified with -a/--attribute-walk");
521 if (arg_export
&& arg_value
)
522 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
523 "-x/--export or -P/--export-prefix cannot be used with --value");
526 STRV_FOREACH(p
, devices
) {
527 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
529 r
= find_device(*p
, NULL
, &device
);
531 return log_error_errno(r
, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p
);
533 return log_error_errno(r
, "Unknown device \"%s\": %m", *p
);
535 if (arg_wait_for_initialization_timeout
> 0) {
538 r
= device_wait_for_initialization(
541 usec_add(now(CLOCK_MONOTONIC
), arg_wait_for_initialization_timeout
),
546 sd_device_unref(device
);
550 if (action
== ACTION_QUERY
)
551 r
= query_device(query
, device
);
552 else if (action
== ACTION_ATTRIBUTE_WALK
)
553 r
= print_device_chain(device
);
555 assert_not_reached();