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 "string-table.h"
22 #include "string-util.h"
23 #include "udev-util.h"
24 #include "udevadm-util.h"
27 typedef enum ActionType
{
29 ACTION_ATTRIBUTE_WALK
,
30 ACTION_DEVICE_ID_FILE
,
33 typedef enum QueryType
{
41 static bool arg_root
= false;
42 static bool arg_export
= false;
43 static const char *arg_export_prefix
= NULL
;
44 static usec_t arg_wait_for_initialization_timeout
= 0;
46 static bool skip_attribute(const char *name
) {
47 /* Those are either displayed separately or should not be shown at all. */
48 return STR_IN_SET(name
,
58 typedef struct SysAttr
{
63 static int sysattr_compare(const SysAttr
*a
, const SysAttr
*b
) {
64 return strcmp(a
->name
, b
->name
);
67 static int print_all_attributes(sd_device
*device
, bool is_parent
) {
68 _cleanup_free_ SysAttr
*sysattrs
= NULL
;
69 const char *name
, *value
;
73 (void) sd_device_get_devpath(device
, &value
);
74 printf(" looking at %sdevice '%s':\n", is_parent
? "parent " : "", strempty(value
));
77 (void) sd_device_get_sysname(device
, &value
);
78 printf(" %s==\"%s\"\n", is_parent
? "KERNELS" : "KERNEL", strempty(value
));
81 (void) sd_device_get_subsystem(device
, &value
);
82 printf(" %s==\"%s\"\n", is_parent
? "SUBSYSTEMS" : "SUBSYSTEM", strempty(value
));
85 (void) sd_device_get_driver(device
, &value
);
86 printf(" %s==\"%s\"\n", is_parent
? "DRIVERS" : "DRIVER", strempty(value
));
88 FOREACH_DEVICE_SYSATTR(device
, name
) {
91 if (skip_attribute(name
))
94 if (sd_device_get_sysattr_value(device
, name
, &value
) < 0)
97 /* skip any values that look like a path */
101 /* skip nonprintable attributes */
103 while (len
> 0 && isprint((unsigned char) value
[len
-1]))
108 if (!GREEDY_REALLOC(sysattrs
, n_items
+ 1))
111 sysattrs
[n_items
] = (SysAttr
) {
118 typesafe_qsort(sysattrs
, n_items
, sysattr_compare
);
120 for (size_t i
= 0; i
< n_items
; i
++)
121 printf(" %s{%s}==\"%s\"\n", is_parent
? "ATTRS" : "ATTR", sysattrs
[i
].name
, sysattrs
[i
].value
);
128 static int print_device_chain(sd_device
*device
) {
129 sd_device
*child
, *parent
;
133 "Udevadm info starts with the device specified by the devpath and then\n"
134 "walks up the chain of parent devices. It prints for every device\n"
135 "found, all possible attributes in the udev rules key format.\n"
136 "A rule to match, can be composed by the attributes of the device\n"
137 "and the attributes from one single parent device.\n"
140 r
= print_all_attributes(device
, false);
144 for (child
= device
; sd_device_get_parent(child
, &parent
) >= 0; child
= parent
) {
145 r
= print_all_attributes(parent
, true);
153 static int print_record(sd_device
*device
) {
154 const char *str
, *val
;
157 (void) sd_device_get_devpath(device
, &str
);
158 printf("P: %s\n", str
);
160 if (sd_device_get_devname(device
, &str
) >= 0) {
161 assert_se(val
= path_startswith(str
, "/dev/"));
162 printf("N: %s\n", val
);
165 if (device_get_devlink_priority(device
, &i
) >= 0)
166 printf("L: %i\n", i
);
168 FOREACH_DEVICE_DEVLINK(device
, str
) {
169 assert_se(val
= path_startswith(str
, "/dev/"));
170 printf("S: %s\n", val
);
173 FOREACH_DEVICE_PROPERTY(device
, str
, val
)
174 printf("E: %s=%s\n", str
, val
);
180 static int stat_device(const char *name
, bool export
, const char *prefix
) {
183 if (stat(name
, &statbuf
) != 0)
189 printf("%sMAJOR=%u\n"
191 prefix
, major(statbuf
.st_dev
),
192 prefix
, minor(statbuf
.st_dev
));
194 printf("%u:%u\n", major(statbuf
.st_dev
), minor(statbuf
.st_dev
));
198 static int export_devices(void) {
199 _cleanup_(sd_device_enumerator_unrefp
) sd_device_enumerator
*e
= NULL
;
203 r
= sd_device_enumerator_new(&e
);
207 r
= sd_device_enumerator_allow_uninitialized(e
);
209 return log_error_errno(r
, "Failed to set allowing uninitialized flag: %m");
211 r
= device_enumerator_scan_devices(e
);
213 return log_error_errno(r
, "Failed to scan devices: %m");
215 FOREACH_DEVICE_AND_SUBSYSTEM(e
, d
)
216 (void) print_record(d
);
221 static void cleanup_dir(DIR *dir
, mode_t mask
, int depth
) {
227 FOREACH_DIRENT_ALL(dent
, dir
, break) {
230 if (dent
->d_name
[0] == '.')
232 if (fstatat(dirfd(dir
), dent
->d_name
, &stats
, AT_SYMLINK_NOFOLLOW
) != 0)
234 if ((stats
.st_mode
& mask
) != 0)
236 if (S_ISDIR(stats
.st_mode
)) {
237 _cleanup_closedir_
DIR *dir2
= NULL
;
239 dir2
= fdopendir(openat(dirfd(dir
), dent
->d_name
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
));
241 cleanup_dir(dir2
, mask
, depth
-1);
243 (void) unlinkat(dirfd(dir
), dent
->d_name
, AT_REMOVEDIR
);
245 (void) unlinkat(dirfd(dir
), dent
->d_name
, 0);
249 static void cleanup_db(void) {
250 _cleanup_closedir_
DIR *dir1
= NULL
, *dir2
= NULL
, *dir3
= NULL
, *dir4
= NULL
, *dir5
= NULL
;
252 dir1
= opendir("/run/udev/data");
254 cleanup_dir(dir1
, S_ISVTX
, 1);
256 dir2
= opendir("/run/udev/links");
258 cleanup_dir(dir2
, 0, 2);
260 dir3
= opendir("/run/udev/tags");
262 cleanup_dir(dir3
, 0, 2);
264 dir4
= opendir("/run/udev/static_node-tags");
266 cleanup_dir(dir4
, 0, 2);
268 dir5
= opendir("/run/udev/watch");
270 cleanup_dir(dir5
, 0, 1);
273 static int query_device(QueryType query
, sd_device
* device
) {
282 r
= sd_device_get_devname(device
, &node
);
284 return log_error_errno(r
, "No device node found: %m");
287 assert_se(node
= path_startswith(node
, "/dev/"));
288 printf("%s\n", node
);
292 case QUERY_SYMLINK
: {
293 const char *devlink
, *prefix
= "";
295 FOREACH_DEVICE_DEVLINK(device
, devlink
) {
297 assert_se(devlink
= path_startswith(devlink
, "/dev/"));
298 printf("%s%s", prefix
, devlink
);
308 r
= sd_device_get_devpath(device
, &devpath
);
310 return log_error_errno(r
, "Failed to get device path: %m");
312 printf("%s\n", devpath
);
316 case QUERY_PROPERTY
: {
317 const char *key
, *value
;
319 FOREACH_DEVICE_PROPERTY(device
, key
, value
)
321 printf("%s%s='%s'\n", strempty(arg_export_prefix
), key
, value
);
323 printf("%s=%s\n", key
, value
);
328 return print_record(device
);
331 assert_not_reached("unknown query type");
335 static int help(void) {
336 printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n"
337 "Query sysfs or the udev database.\n\n"
338 " -h --help Print this message\n"
339 " -V --version Print version of the program\n"
340 " -q --query=TYPE Query device information:\n"
341 " name Name of device node\n"
342 " symlink Pointing to node\n"
343 " path sysfs device path\n"
344 " property The device properties\n"
346 " -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
347 " -n --name=NAME Node or symlink name used for query or attribute walk\n"
348 " -r --root Prepend dev directory to path names\n"
349 " -a --attribute-walk Print all key matches walking along the chain\n"
350 " of parent devices\n"
351 " -d --device-id-of-file=FILE Print major:minor of device containing this file\n"
352 " -x --export Export key/value pairs\n"
353 " -P --export-prefix Export the key name with a prefix\n"
354 " -e --export-db Export the content of the udev database\n"
355 " -c --cleanup-db Clean up the udev database\n"
356 " -w --wait-for-initialization[=SECONDS]\n"
357 " Wait for device to be initialized\n",
358 program_invocation_short_name
);
363 int info_main(int argc
, char *argv
[], void *userdata
) {
364 _cleanup_strv_free_
char **devices
= NULL
;
365 _cleanup_free_
char *name
= NULL
;
368 static const struct option options
[] = {
369 { "name", required_argument
, NULL
, 'n' },
370 { "path", required_argument
, NULL
, 'p' },
371 { "query", required_argument
, NULL
, 'q' },
372 { "attribute-walk", no_argument
, NULL
, 'a' },
373 { "cleanup-db", no_argument
, NULL
, 'c' },
374 { "export-db", no_argument
, NULL
, 'e' },
375 { "root", no_argument
, NULL
, 'r' },
376 { "device-id-of-file", required_argument
, NULL
, 'd' },
377 { "export", no_argument
, NULL
, 'x' },
378 { "export-prefix", required_argument
, NULL
, 'P' },
379 { "wait-for-initialization", optional_argument
, NULL
, 'w' },
380 { "version", no_argument
, NULL
, 'V' },
381 { "help", no_argument
, NULL
, 'h' },
385 ActionType action
= ACTION_QUERY
;
386 QueryType query
= QUERY_ALL
;
388 while ((c
= getopt_long(argc
, argv
, "aced:n:p:q:rxP:w::Vh", options
, NULL
)) >= 0)
392 const char *prefix
= c
== 'n' ? "/dev/" : "/sys/";
395 path
= path_join(path_startswith(optarg
, prefix
) ? NULL
: prefix
, optarg
);
399 r
= strv_consume(&devices
, path
);
406 action
= ACTION_QUERY
;
407 if (streq(optarg
, "property") || streq(optarg
, "env"))
408 query
= QUERY_PROPERTY
;
409 else if (streq(optarg
, "name"))
411 else if (streq(optarg
, "symlink"))
412 query
= QUERY_SYMLINK
;
413 else if (streq(optarg
, "path"))
415 else if (streq(optarg
, "all"))
418 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "unknown query type");
424 action
= ACTION_DEVICE_ID_FILE
;
425 r
= free_and_strdup(&name
, optarg
);
430 action
= ACTION_ATTRIBUTE_WALK
;
433 return export_devices();
442 arg_export_prefix
= optarg
;
446 r
= parse_sec(optarg
, &arg_wait_for_initialization_timeout
);
448 return log_error_errno(r
, "Failed to parse timeout value: %m");
450 arg_wait_for_initialization_timeout
= USEC_INFINITY
;
453 return print_version();
459 assert_not_reached("Unknown option");
462 if (action
== ACTION_DEVICE_ID_FILE
) {
464 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
465 "Positional arguments are not allowed with -d/--device-id-of-file.");
467 return stat_device(name
, arg_export
, arg_export_prefix
);
470 r
= strv_extend_strv(&devices
, argv
+ optind
, false);
472 return log_error_errno(r
, "Failed to build argument list: %m");
474 if (strv_isempty(devices
))
475 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
476 "A device name or path is required");
477 if (action
== ACTION_ATTRIBUTE_WALK
&& strv_length(devices
) > 1)
478 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
479 "Only one device may be specified with -a/--attribute-walk");
482 STRV_FOREACH(p
, devices
) {
483 _cleanup_(sd_device_unrefp
) sd_device
*device
= NULL
;
485 r
= find_device(*p
, NULL
, &device
);
487 return log_error_errno(r
, "Bad argument \"%s\", expected an absolute path in /dev/ or /sys or a unit name: %m", *p
);
489 return log_error_errno(r
, "Unknown device \"%s\": %m", *p
);
491 if (arg_wait_for_initialization_timeout
> 0) {
494 r
= device_wait_for_initialization(
497 usec_add(now(CLOCK_MONOTONIC
), arg_wait_for_initialization_timeout
),
502 sd_device_unref(device
);
506 if (action
== ACTION_QUERY
)
507 r
= query_device(query
, device
);
508 else if (action
== ACTION_ATTRIBUTE_WALK
)
509 r
= print_device_chain(device
);
511 assert_not_reached("Unknown action");