]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-device: introduce sd_device_get_child_first() and _next()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 20 Sep 2022 01:50:09 +0000 (10:50 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 22 Sep 2022 22:03:15 +0000 (07:03 +0900)
These functions provide a high-level interface for enumerating
child devices.

Suggested at https://github.com/systemd/systemd/pull/24731#discussion_r973987065.

src/libsystemd/libsystemd.sym
src/libsystemd/sd-device/device-internal.h
src/libsystemd/sd-device/device-util.h
src/libsystemd/sd-device/sd-device.c
src/systemd/sd-device.h

index 18717cbb7b71455a1816a8b6de96f8bc096bd12e..992a79fcc48c78d4071586f371d334992f4fc548 100644 (file)
@@ -785,6 +785,8 @@ global:
         sd_bus_error_setfv;
 
         sd_device_new_child;
+        sd_device_get_child_first;
+        sd_device_get_child_next;
         sd_device_monitor_set_description;
         sd_device_monitor_get_description;
 
index 9a4533a8f61713f991d5f932287d96014a9828d3..a465eb25fda5417a0682743707f35631f983b4ef 100644 (file)
@@ -47,6 +47,10 @@ struct sd_device {
         uint64_t devlinks_iterator_generation; /* generation when iteration was started */
         int devlink_priority;
 
+        Hashmap *children;
+        Iterator children_iterator;
+        bool children_enumerated;
+
         int ifindex;
         char *devtype;
         char *devname;
index 0561a172ae42c7b64166cc100a03a2708dfcc511..f139e114a8fa78896318dcbcc91bb6ce2773d803 100644 (file)
              devlink;                                   \
              devlink = sd_device_get_devlink_next(device))
 
+#define _FOREACH_DEVICE_CHILD(device, child, suffix_ptr)                \
+        for (child = sd_device_get_child_first(device, suffix_ptr);     \
+             child;                                                     \
+             child = sd_device_get_child_next(device, suffix_ptr))
+
+#define FOREACH_DEVICE_CHILD(device, child)                             \
+        _FOREACH_DEVICE_CHILD(device, child, NULL)
+
+#define FOREACH_DEVICE_CHILD_WITH_SUFFIX(device, child, suffix)         \
+        _FOREACH_DEVICE_CHILD(device, child, &suffix)
+
 #define FOREACH_DEVICE(enumerator, device)                               \
         for (device = sd_device_enumerator_get_device_first(enumerator); \
              device;                                                     \
index ab96f889cb1450c763c16bb056e5ab2a5cd4828f..579cbe39e508b0858ab3e11ae5fad161d023d413 100644 (file)
@@ -78,6 +78,7 @@ static sd_device *device_free(sd_device *device) {
         set_free(device->all_tags);
         set_free(device->current_tags);
         set_free(device->devlinks);
+        hashmap_free(device->children);
 
         return mfree(device);
 }
@@ -870,8 +871,129 @@ _public_ int sd_device_get_syspath(sd_device *device, const char **ret) {
         return 0;
 }
 
+DEFINE_PRIVATE_HASH_OPS_FULL(
+        device_by_path_hash_ops,
+        char, path_hash_func, path_compare, free,
+        sd_device, sd_device_unref);
+
+static int device_enumerate_children_internal(sd_device *device, const char *subdir, Set **stack, Hashmap **children) {
+        _cleanup_closedir_ DIR *dir = NULL;
+        int r;
+
+        assert(device);
+        assert(stack);
+        assert(children);
+
+        r = device_opendir(device, subdir, &dir);
+        if (r < 0)
+                return r;
+
+        FOREACH_DIRENT_ALL(de, dir, return -errno) {
+                _cleanup_(sd_device_unrefp) sd_device *child = NULL;
+                _cleanup_free_ char *p = NULL;
+
+                if (dot_or_dot_dot(de->d_name))
+                        continue;
+
+                if (!IN_SET(de->d_type, DT_LNK, DT_DIR))
+                        continue;
+
+                if (subdir)
+                        p = path_join(subdir, de->d_name);
+                else
+                        p = strdup(de->d_name);
+                if (!p)
+                        return -ENOMEM;
+
+                /* Try to create child device. */
+                r = sd_device_new_child(&child, device, p);
+                if (r >= 0) {
+                        /* OK, this is a child device, saving it. */
+                        r = hashmap_ensure_put(children, &device_by_path_hash_ops, p, child);
+                        if (r < 0)
+                                return r;
+
+                        TAKE_PTR(p);
+                        TAKE_PTR(child);
+                } else if (r == -ENODEV) {
+                        /* This is not a child device. Push the sub-directory into stack, and read it later. */
+
+                        if (de->d_type == DT_LNK)
+                                /* Do not follow symlinks, otherwise, we will enter an infinite loop, e.g.,
+                                 * /sys/class/block/nvme0n1/subsystem/nvme0n1/subsystem/nvme0n1/subsystem/… */
+                                continue;
+
+                        r = set_ensure_consume(stack, &path_hash_ops_free, TAKE_PTR(p));
+                        if (r < 0)
+                                return r;
+                } else
+                        return r;
+        }
+
+        return 0;
+}
+
+static int device_enumerate_children(sd_device *device) {
+        _cleanup_hashmap_free_ Hashmap *children = NULL;
+        _cleanup_set_free_ Set *stack = NULL;
+        int r;
+
+        assert(device);
+
+        if (device->children_enumerated)
+                return 0; /* Already enumerated. */
+
+        r = device_enumerate_children_internal(device, NULL, &stack, &children);
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *subdir = NULL;
+
+                subdir = set_steal_first(stack);
+                if (!subdir)
+                        break;
+
+                r = device_enumerate_children_internal(device, subdir, &stack, &children);
+                if (r < 0)
+                        return r;
+        }
+
+        device->children_enumerated = true;
+        device->children = TAKE_PTR(children);
+        return 1; /* Enumerated. */
+}
+
+_public_ sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix) {
+        int r;
+
+        assert(device);
+
+        r = device_enumerate_children(device);
+        if (r < 0) {
+                log_device_debug_errno(device, r, "sd-device: failed to enumerate child devices: %m");
+                if (ret_suffix)
+                        *ret_suffix = NULL;
+                return NULL;
+        }
+
+        device->children_iterator = ITERATOR_FIRST;
+
+        return sd_device_get_child_next(device, ret_suffix);
+}
+
+_public_ sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix) {
+        sd_device *child;
+
+        assert(device);
+
+        hashmap_iterate(device->children, &device->children_iterator, (void**) &child, (const void**) ret_suffix);
+        return child;
+}
+
 _public_ int sd_device_new_child(sd_device **ret, sd_device *device, const char *suffix) {
         _cleanup_free_ char *path = NULL;
+        sd_device *child;
         const char *s;
         int r;
 
@@ -882,6 +1004,13 @@ _public_ int sd_device_new_child(sd_device **ret, sd_device *device, const char
         if (!path_is_safe(suffix))
                 return -EINVAL;
 
+        /* If we have already enumerated children, try to find the child from the cache. */
+        child = hashmap_get(device->children, suffix);
+        if (child) {
+                *ret = sd_device_ref(child);
+                return 0;
+        }
+
         r = sd_device_get_syspath(device, &s);
         if (r < 0)
                 return r;
index 41d3f832913c0118a57c0144ce6ecb5a1d1177e5..e3d647f75d1eb272632ca4d5530ed8714287f7d4 100644 (file)
@@ -100,6 +100,8 @@ const char *sd_device_get_property_first(sd_device *device, const char **value);
 const char *sd_device_get_property_next(sd_device *device, const char **value);
 const char *sd_device_get_sysattr_first(sd_device *device);
 const char *sd_device_get_sysattr_next(sd_device *device);
+sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix);
+sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix);
 
 int sd_device_has_tag(sd_device *device, const char *tag);
 int sd_device_has_current_tag(sd_device *device, const char *tag);