|
16 | 16 | extern "C" |
17 | 17 | { |
18 | 18 | #endif |
| 19 | +#include <stdbool.h> |
19 | 20 | #include <stdio.h> |
| 21 | +#include <limits.h> |
20 | 22 | #include <stdlib.h> |
| 23 | +#include <string.h> |
21 | 24 |
|
22 | 25 | #ifndef _WIN32 |
23 | 26 | #if defined(__APPLE__) |
@@ -60,6 +63,57 @@ rcutils_get_zero_initialized_shared_library(void) |
60 | 63 | return zero_initialized_shared_library; |
61 | 64 | } |
62 | 65 |
|
| 66 | +#if defined(__APPLE__) |
| 67 | +/// Whether a loaded image's name could be the library that dlopen loaded for `library_path`. |
| 68 | +/** |
| 69 | + * `resolved_path` is the realpath() of `library_path`, or NULL if it has none. |
| 70 | + */ |
| 71 | +static bool |
| 72 | +image_name_matches(const char * image_name, const char * library_path, const char * resolved_path) |
| 73 | +{ |
| 74 | + if (NULL == strchr(library_path, '/')) { |
| 75 | + // dlopen searched for a bare file name, so the image can be in any directory. |
| 76 | + const char * slash = strrchr(image_name, '/'); |
| 77 | + return 0 == strcmp(NULL == slash ? image_name : slash + 1, library_path); |
| 78 | + } |
| 79 | + return 0 == strcmp(image_name, library_path) || |
| 80 | + (NULL != resolved_path && 0 == strcmp(image_name, resolved_path)); |
| 81 | +} |
| 82 | + |
| 83 | +/// Find the name of the loaded image whose handle is `lib_pointer`; NULL if none. |
| 84 | +/** |
| 85 | + * With `only_matching_names`, images rejected by image_name_matches() are skipped. |
| 86 | + */ |
| 87 | +static rcutils_ret_t |
| 88 | +find_image_name( |
| 89 | + void * lib_pointer, const char * library_path, const char * resolved_path, |
| 90 | + bool only_matching_names, const char ** image_name) |
| 91 | +{ |
| 92 | + *image_name = NULL; |
| 93 | + uint32_t image_count = _dyld_image_count(); |
| 94 | + for (uint32_t i = 0; NULL == *image_name && i < image_count; ++i) { |
| 95 | + // Iterate in reverse as the library is likely near the end of the list. |
| 96 | + const char * candidate_name = _dyld_get_image_name(image_count - i - 1); |
| 97 | + if (NULL == candidate_name) { |
| 98 | + RCUTILS_SET_ERROR_MSG("dyld image index out of range"); |
| 99 | + return RCUTILS_RET_ERROR; |
| 100 | + } |
| 101 | + if (only_matching_names && !image_name_matches(candidate_name, library_path, resolved_path)) { |
| 102 | + continue; |
| 103 | + } |
| 104 | + void * handle = dlopen(candidate_name, RTLD_LAZY | RTLD_NOLOAD); |
| 105 | + if (handle == lib_pointer) { |
| 106 | + *image_name = candidate_name; |
| 107 | + } |
| 108 | + if (dlclose(handle) != 0) { |
| 109 | + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlclose error: %s", dlerror()); |
| 110 | + return RCUTILS_RET_ERROR; |
| 111 | + } |
| 112 | + } |
| 113 | + return RCUTILS_RET_OK; |
| 114 | +} |
| 115 | +#endif // defined(__APPLE__) |
| 116 | + |
63 | 117 | rcutils_ret_t |
64 | 118 | rcutils_load_shared_library( |
65 | 119 | rcutils_shared_library_t * lib, |
@@ -98,25 +152,20 @@ rcutils_load_shared_library( |
98 | 152 | } |
99 | 153 |
|
100 | 154 | #if defined(__APPLE__) |
| 155 | + // Find the loaded image's name to get its full path. Each candidate costs a dlopen/dlclose, |
| 156 | + // and every dlclose makes dyld walk all loaded images, so checking every image is quadratic. |
| 157 | + // Check images whose name matches the request first, then fall back to all images in case |
| 158 | + // dyld knows the library under another name (e.g. a versioned symlink). |
| 159 | + char resolved_path[PATH_MAX]; |
| 160 | + const char * resolved = |
| 161 | + NULL == strchr(library_path, '/') ? NULL : realpath(library_path, resolved_path); |
101 | 162 | const char * image_name = NULL; |
102 | | - uint32_t image_count = _dyld_image_count(); |
103 | | - for (uint32_t i = 0; NULL == image_name && i < image_count; ++i) { |
104 | | - // Iterate in reverse as the library is likely near the end of the list. |
105 | | - const char * candidate_name = _dyld_get_image_name(image_count - i - 1); |
106 | | - if (NULL == candidate_name) { |
107 | | - RCUTILS_SET_ERROR_MSG("dyld image index out of range"); |
108 | | - ret = RCUTILS_RET_ERROR; |
109 | | - goto fail; |
110 | | - } |
111 | | - void * handle = dlopen(candidate_name, RTLD_LAZY | RTLD_NOLOAD); |
112 | | - if (handle == lib->lib_pointer) { |
113 | | - image_name = candidate_name; |
114 | | - } |
115 | | - if (dlclose(handle) != 0) { |
116 | | - RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlclose error: %s", dlerror()); |
117 | | - ret = RCUTILS_RET_ERROR; |
118 | | - goto fail; |
119 | | - } |
| 163 | + ret = find_image_name(lib->lib_pointer, library_path, resolved, true, &image_name); |
| 164 | + if (RCUTILS_RET_OK == ret && NULL == image_name) { |
| 165 | + ret = find_image_name(lib->lib_pointer, library_path, resolved, false, &image_name); |
| 166 | + } |
| 167 | + if (RCUTILS_RET_OK != ret) { |
| 168 | + goto fail; |
120 | 169 | } |
121 | 170 | if (NULL == image_name) { |
122 | 171 | RCUTILS_SET_ERROR_MSG("dyld image name could not be found"); |
|
0 commit comments