From ec9b4f2b924a05395b8e9aca15190f69392037f6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 20 Sep 2022 10:50:09 +0900 Subject: [PATCH] sd-device: introduce sd_device_get_child_first() and _next() 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 | 2 + src/libsystemd/sd-device/device-internal.h | 4 + src/libsystemd/sd-device/device-util.h | 11 ++ src/libsystemd/sd-device/sd-device.c | 129 +++++++++++++++++++++ src/systemd/sd-device.h | 2 + 5 files changed, 148 insertions(+) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 18717cbb7b..992a79fcc4 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -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; diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h index 9a4533a8f6..a465eb25fd 100644 --- a/src/libsystemd/sd-device/device-internal.h +++ b/src/libsystemd/sd-device/device-internal.h @@ -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; diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h index 0561a172ae..f139e114a8 100644 --- a/src/libsystemd/sd-device/device-util.h +++ b/src/libsystemd/sd-device/device-util.h @@ -38,6 +38,17 @@ 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; \ diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index ab96f889cb..579cbe39e5 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -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; diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h index 41d3f83291..e3d647f75d 100644 --- a/src/systemd/sd-device.h +++ b/src/systemd/sd-device.h @@ -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); -- 2.25.1