]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/udev/udevadm-info.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / udev / udevadm-info.c
index ae6d8caf54e02c39c4b5f4245b5523cc9713eb97..66ddf3a779db57a0e4aeea1f8dd021a71434c77d 100644 (file)
@@ -1,4 +1,4 @@
-/* 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"
@@ -38,12 +40,16 @@ typedef enum QueryType {
         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",
@@ -60,14 +66,22 @@ typedef struct SysAttr {
         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);
@@ -91,21 +105,25 @@ static int print_all_attributes(sd_device *device, bool is_parent) {
                 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((unsigned char) 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) {
@@ -129,6 +147,8 @@ static int print_device_chain(sd_device *device) {
         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"
@@ -154,6 +174,8 @@ static int print_record(sd_device *device) {
         const char *str, *val;
         int i;
 
+        assert(device);
+
         (void) sd_device_get_devpath(device, &str);
         printf("P: %s\n", str);
 
@@ -180,6 +202,8 @@ static int print_record(sd_device *device) {
 static int stat_device(const char *name, bool export, const char *prefix) {
         struct stat statbuf;
 
+        assert(name);
+
         if (stat(name, &statbuf) != 0)
                 return -errno;
 
@@ -219,7 +243,7 @@ static int export_devices(void) {
 }
 
 static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
-        struct dirent *dent;
+        assert(dir);
 
         if (depth <= 0)
                 return;
@@ -227,9 +251,9 @@ static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
         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;
@@ -246,10 +270,55 @@ static void cleanup_dir(DIR *dir, mode_t mask, int depth) {
         }
 }
 
-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)
@@ -257,19 +326,18 @@ static void cleanup_db(void) {
 
         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) {
@@ -318,20 +386,27 @@ 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) {
@@ -345,6 +420,8 @@ 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"
@@ -356,8 +433,8 @@ static int help(void) {
                "  -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;
 }
@@ -367,20 +444,27 @@ int info_main(int argc, char *argv[], void *userdata) {
         _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'          },
                 {}
         };
 
@@ -389,6 +473,22 @@ int info_main(int argc, char *argv[], void *userdata) {
 
         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/";
@@ -458,7 +558,7 @@ int info_main(int argc, char *argv[], void *userdata) {
                 case '?':
                         return -EINVAL;
                 default:
-                        assert_not_reached("Unknown option");
+                        assert_not_reached();
                 }
 
         if (action == ACTION_DEVICE_ID_FILE) {
@@ -480,7 +580,10 @@ int info_main(int argc, char *argv[], void *userdata) {
                 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;
 
@@ -493,7 +596,11 @@ int info_main(int argc, char *argv[], void *userdata) {
                 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;
 
@@ -506,7 +613,7 @@ int info_main(int argc, char *argv[], void *userdata) {
                 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;
         }