-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <ctype.h>
#include <errno.h>
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "sort-util.h"
+#include "static-destruct.h"
#include "string-table.h"
#include "string-util.h"
#include "udev-util.h"
QUERY_ALL,
} QueryType;
+static char **arg_properties = NULL;
static bool arg_root = false;
static bool arg_export = false;
+static bool arg_value = false;
static const char *arg_export_prefix = NULL;
static usec_t arg_wait_for_initialization_timeout = 0;
static bool skip_attribute(const char *name) {
+ assert(name);
+
/* Those are either displayed separately or should not be shown at all. */
return STR_IN_SET(name,
"uevent",
const char *value;
} SysAttr;
+STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
+
static int sysattr_compare(const SysAttr *a, const SysAttr *b) {
+ assert(a);
+ assert(b);
+
return strcmp(a->name, b->name);
}
static int print_all_attributes(sd_device *device, bool is_parent) {
_cleanup_free_ SysAttr *sysattrs = NULL;
- size_t n_items = 0, n_allocated = 0;
const char *name, *value;
+ size_t n_items = 0;
+ int r;
+
+ assert(device);
value = NULL;
(void) sd_device_get_devpath(device, &value);
if (skip_attribute(name))
continue;
- if (sd_device_get_sysattr_value(device, name, &value) < 0)
- continue;
-
- /* skip any values that look like a path */
- if (value[0] == '/')
- continue;
-
- /* skip nonprintable attributes */
- len = strlen(value);
- while (len > 0 && isprint(value[len-1]))
- len--;
- if (len > 0)
+ r = sd_device_get_sysattr_value(device, name, &value);
+ if (r >= 0) {
+ /* skip any values that look like a path */
+ if (value[0] == '/')
+ continue;
+
+ /* skip nonprintable attributes */
+ len = strlen(value);
+ while (len > 0 && isprint((unsigned char) value[len-1]))
+ len--;
+ if (len > 0)
+ continue;
+
+ } else if (ERRNO_IS_PRIVILEGE(r))
+ value = "(not readable)";
+ else
continue;
- if (!GREEDY_REALLOC(sysattrs, n_allocated, n_items + 1))
+ if (!GREEDY_REALLOC(sysattrs, n_items + 1))
return log_oom();
sysattrs[n_items] = (SysAttr) {
sd_device *child, *parent;
int r;
+ assert(device);
+
printf("\n"
"Udevadm info starts with the device specified by the devpath and then\n"
"walks up the chain of parent devices. It prints for every device\n"
const char *str, *val;
int i;
+ assert(device);
+
(void) sd_device_get_devpath(device, &str);
printf("P: %s\n", str);
static int stat_device(const char *name, bool export, const char *prefix) {
struct stat statbuf;
+ assert(name);
+
if (stat(name, &statbuf) != 0)
return -errno;
}
static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
- struct dirent *dent;
+ assert(dir);
if (depth <= 0)
return;
FOREACH_DIRENT_ALL(dent, dir, break) {
struct stat stats;
- if (dent->d_name[0] == '.')
+ if (dot_or_dot_dot(dent->d_name))
continue;
- if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
+ if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
continue;
if ((stats.st_mode & mask) != 0)
continue;
}
}
-static void cleanup_db(void) {
- _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL, *dir5 = NULL;
+/*
+ * Assume that dir is a directory with file names matching udev data base
+ * entries for devices in /run/udev/data (such as "b8:16"), and removes
+ * all files except those that haven't been deleted in /run/udev/data
+ * (i.e. they were skipped during db cleanup because of the db_persist flag).
+ */
+static void cleanup_dir_after_db_cleanup(DIR *dir, DIR *datadir) {
+ assert(dir);
+ assert(datadir);
+
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ if (dot_or_dot_dot(dent->d_name))
+ continue;
+
+ if (faccessat(dirfd(datadir), dent->d_name, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+ /* The corresponding udev database file still exists.
+ * Assuming the parsistent flag is set for the database. */
+ continue;
- (void) unlink("/run/udev/queue.bin");
+ (void) unlinkat(dirfd(dir), dent->d_name, 0);
+ }
+}
+
+static void cleanup_dirs_after_db_cleanup(DIR *dir, DIR *datadir) {
+ assert(dir);
+ assert(datadir);
+
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ struct stat stats;
+
+ if (dot_or_dot_dot(dent->d_name))
+ continue;
+ if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) < 0)
+ continue;
+ if (S_ISDIR(stats.st_mode)) {
+ _cleanup_closedir_ DIR *dir2 = NULL;
+
+ dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC));
+ if (dir2)
+ cleanup_dir_after_db_cleanup(dir2, datadir);
+
+ (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR);
+ } else
+ (void) unlinkat(dirfd(dir), dent->d_name, 0);
+ }
+}
+
+static void cleanup_db(void) {
+ _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL;
dir1 = opendir("/run/udev/data");
if (dir1)
dir2 = opendir("/run/udev/links");
if (dir2)
- cleanup_dir(dir2, 0, 2);
+ cleanup_dirs_after_db_cleanup(dir2, dir1);
dir3 = opendir("/run/udev/tags");
if (dir3)
- cleanup_dir(dir3, 0, 2);
+ cleanup_dirs_after_db_cleanup(dir3, dir1);
dir4 = opendir("/run/udev/static_node-tags");
if (dir4)
cleanup_dir(dir4, 0, 2);
- dir5 = opendir("/run/udev/watch");
- if (dir5)
- cleanup_dir(dir5, 0, 1);
+ /* Do not remove /run/udev/watch. It will be handled by udevd well on restart.
+ * And should not be removed by external program when udevd is running. */
}
static int query_device(QueryType query, sd_device* device) {
case QUERY_PROPERTY: {
const char *key, *value;
- FOREACH_DEVICE_PROPERTY(device, key, value)
+ FOREACH_DEVICE_PROPERTY(device, key, value) {
+ if (arg_properties && !strv_contains(arg_properties, key))
+ continue;
+
if (arg_export)
printf("%s%s='%s'\n", strempty(arg_export_prefix), key, value);
+ else if (arg_value)
+ printf("%s\n", value);
else
printf("%s=%s\n", key, value);
+ }
+
return 0;
}
case QUERY_ALL:
return print_record(device);
- }
- assert_not_reached("unknown query type");
- return 0;
+ default:
+ assert_not_reached();
+ }
}
static int help(void) {
" path sysfs device path\n"
" property The device properties\n"
" all All values\n"
+ " --property=NAME Show only properties by this name\n"
+ " --value When showing properties, print only their values\n"
" -p --path=SYSPATH sysfs device path used for query or attribute walk\n"
" -n --name=NAME Node or symlink name used for query or attribute walk\n"
" -r --root Prepend dev directory to path names\n"
" -e --export-db Export the content of the udev database\n"
" -c --cleanup-db Clean up the udev database\n"
" -w --wait-for-initialization[=SECONDS]\n"
- " Wait for device to be initialized\n"
- , program_invocation_short_name);
+ " Wait for device to be initialized\n",
+ program_invocation_short_name);
return 0;
}
_cleanup_free_ char *name = NULL;
int c, r;
+ enum {
+ ARG_PROPERTY = 0x100,
+ ARG_VALUE,
+ };
+
static const struct option options[] = {
- { "name", required_argument, NULL, 'n' },
- { "path", required_argument, NULL, 'p' },
- { "query", required_argument, NULL, 'q' },
- { "attribute-walk", no_argument, NULL, 'a' },
- { "cleanup-db", no_argument, NULL, 'c' },
- { "export-db", no_argument, NULL, 'e' },
- { "root", no_argument, NULL, 'r' },
- { "device-id-of-file", required_argument, NULL, 'd' },
- { "export", no_argument, NULL, 'x' },
- { "export-prefix", required_argument, NULL, 'P' },
- { "wait-for-initialization", optional_argument, NULL, 'w' },
- { "version", no_argument, NULL, 'V' },
- { "help", no_argument, NULL, 'h' },
+ { "attribute-walk", no_argument, NULL, 'a' },
+ { "cleanup-db", no_argument, NULL, 'c' },
+ { "device-id-of-file", required_argument, NULL, 'd' },
+ { "export", no_argument, NULL, 'x' },
+ { "export-db", no_argument, NULL, 'e' },
+ { "export-prefix", required_argument, NULL, 'P' },
+ { "help", no_argument, NULL, 'h' },
+ { "name", required_argument, NULL, 'n' },
+ { "path", required_argument, NULL, 'p' },
+ { "property", required_argument, NULL, ARG_PROPERTY },
+ { "query", required_argument, NULL, 'q' },
+ { "root", no_argument, NULL, 'r' },
+ { "value", no_argument, NULL, ARG_VALUE },
+ { "version", no_argument, NULL, 'V' },
+ { "wait-for-initialization", optional_argument, NULL, 'w' },
{}
};
while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:w::Vh", options, NULL)) >= 0)
switch (c) {
+ case ARG_PROPERTY:
+ /* Make sure that if the empty property list was specified, we won't show any
+ properties. */
+ if (isempty(optarg) && !arg_properties) {
+ arg_properties = new0(char*, 1);
+ if (!arg_properties)
+ return log_oom();
+ } else {
+ r = strv_split_and_extend(&arg_properties, optarg, ",", true);
+ if (r < 0)
+ return log_oom();
+ }
+ break;
+ case ARG_VALUE:
+ arg_value = true;
+ break;
case 'n':
case 'p': {
const char *prefix = c == 'n' ? "/dev/" : "/sys/";
case '?':
return -EINVAL;
default:
- assert_not_reached("Unknown option");
+ assert_not_reached();
}
if (action == ACTION_DEVICE_ID_FILE) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Only one device may be specified with -a/--attribute-walk");
- char **p;
+ if (arg_export && arg_value)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "-x/--export or -P/--export-prefix cannot be used with --value");
+
STRV_FOREACH(p, devices) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
if (arg_wait_for_initialization_timeout > 0) {
sd_device *d;
- r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, &d);
+ r = device_wait_for_initialization(
+ device,
+ NULL,
+ usec_add(now(CLOCK_MONOTONIC), arg_wait_for_initialization_timeout),
+ &d);
if (r < 0)
return r;
else if (action == ACTION_ATTRIBUTE_WALK)
r = print_device_chain(device);
else
- assert_not_reached("Unknown action");
+ assert_not_reached();
if (r < 0)
return r;
}