master ohmu/fastfetch-openbsd / src / common / impl / library.c
ohmu  ·  2026-10-15
  1#include "fastfetch.h"
  2#include "common/library.h"
  3#include "common/debug.h"
  4
  5#if _WIN32
  6    #include "common/windows/nt.h"
  7    #include <errno.h>
  8    #include <ntstatus.h>
  9#endif
 10
 11#ifndef FF_DISABLE_DLOPEN
 12
 13    #include <stdarg.h>
 14
 15    // Clang doesn't define __SANITIZE_ADDRESS__ but defines __has_feature(address_sanitizer)
 16    #if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature)
 17        #if __has_feature(address_sanitizer)
 18            #define __SANITIZE_ADDRESS__
 19        #endif
 20    #endif
 21
 22    #ifndef FF_DLOPEN_FLAGS
 23        #ifdef __SANITIZE_ADDRESS__
 24            #define FF_DLOPEN_FLAGS RTLD_LAZY | RTLD_NODELETE
 25        #else
 26            #define FF_DLOPEN_FLAGS RTLD_LAZY
 27        #endif
 28    #endif
 29
 30void* ffLibraryLoadSingle(const char* path, int maxVersion) {
 31    void* result = dlopen(path, FF_DLOPEN_FLAGS);
 32
 33    #if _WIN32
 34
 35    // libX.dll.1 never exists on Windows, while libX-1.dll may exist
 36    FF_UNUSED(maxVersion)
 37
 38    if (result != nullptr) {
 39        return result;
 40    }
 41
 42    uint32_t pathLen = ffStrbufLastIndexC(&instance.state.platform.exePath, '/');
 43    if (pathLen == instance.state.platform.exePath.length) {
 44        return result;
 45    }
 46
 47    char absPath[MAX_PATH * 2];
 48    strcpy(mempcpy(absPath, instance.state.platform.exePath.chars, pathLen + 1), path);
 49    return dlopen(absPath, FF_DLOPEN_FLAGS);
 50
 51    #else
 52
 53    if (result == nullptr) {
 54        FF_DEBUG("dlopen(\"%s\"): %s", path, dlerror());
 55    }
 56
 57    if (result != nullptr || maxVersion < 0) {
 58        return result;
 59    }
 60
 61    FF_STRBUF_AUTO_DESTROY pathbuf = ffStrbufCreateA(64);
 62    ffStrbufAppendS(&pathbuf, path);
 63    ffStrbufAppendC(&pathbuf, '.');
 64
 65    for (int i = maxVersion; i >= 0; --i) {
 66        uint32_t originalLength = pathbuf.length;
 67        ffStrbufAppendSInt(&pathbuf, i);
 68
 69        result = dlopen(pathbuf.chars, FF_DLOPEN_FLAGS);
 70        if (result != nullptr) {
 71            break;
 72        } else {
 73            FF_DEBUG("dlopen(\"%s\"): %s", pathbuf.chars, dlerror());
 74        }
 75
 76        ffStrbufSubstrBefore(&pathbuf, originalLength);
 77    }
 78
 79    #endif
 80
 81    return result;
 82}
 83
 84void* ffLibraryLoadMulti(const char* path, int maxVersion, ...) {
 85    void* result = ffLibraryLoadSingle(path, maxVersion);
 86
 87    if (!result) {
 88        va_list defaultNames;
 89        va_start(defaultNames, maxVersion);
 90
 91        do {
 92            const char* pathRest = va_arg(defaultNames, const char*);
 93            if (pathRest == nullptr) {
 94                break;
 95            }
 96
 97            int maxVersionRest = va_arg(defaultNames, int);
 98            result = ffLibraryLoadSingle(pathRest, maxVersionRest);
 99        } while (!result);
100
101        va_end(defaultNames);
102    }
103
104    return result;
105}
106
107#endif
108
109#if _WIN32
110
111void* dlopen(const char* path, [[maybe_unused]] int mode) {
112    wchar_t pathW[MAX_PATH + 1];
113    ULONG pathWBytes = 0;
114
115    NTSTATUS status = RtlUTF8ToUnicodeN(pathW, sizeof(pathW), &pathWBytes, path, (uint32_t) strlen(path) + 1);
116    if (!NT_SUCCESS(status)) {
117        FF_DEBUG("RtlUTF8ToUnicodeN failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status));
118        return nullptr;
119    }
120
121    PVOID module = nullptr;
122    status = LdrLoadDll(nullptr, nullptr, &(UNICODE_STRING) {
123                                        .Length = (USHORT) (pathWBytes - sizeof(wchar_t)), // Exclude null terminator
124                                        .MaximumLength = (USHORT) pathWBytes,
125                                        .Buffer = pathW,
126                                    },
127        &module);
128
129    if (!NT_SUCCESS(status)) {
130        FF_DEBUG("LdrLoadDll failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status));
131        return nullptr;
132    }
133
134    return module;
135}
136
137int dlclose(void* handle) {
138    NTSTATUS status = LdrUnloadDll(handle);
139    if (!NT_SUCCESS(status)) {
140        FF_DEBUG("LdrUnloadDll failed for handle %p with status 0x%08lX: %s", handle, status, ffDebugNtStatus(status));
141        return -1;
142    }
143    return 0;
144}
145
146void* dlsym(void* handle, const char* symbol) {
147    void* address;
148    USHORT symbolBytes = (USHORT) (strlen(symbol) + 1);
149    NTSTATUS status = LdrGetProcedureAddress(handle, &(ANSI_STRING) {
150                                                         .Length = symbolBytes - sizeof(char),
151                                                         .MaximumLength = symbolBytes,
152                                                         .Buffer = (char*) symbol,
153                                                     },
154        0,
155        &address);
156    if (!NT_SUCCESS(status)) {
157        FF_DEBUG("LdrGetProcedureAddress failed for symbol %s with status 0x%08lX: %s", symbol, status, ffDebugNtStatus(status));
158        return nullptr;
159    }
160    return address;
161}
162
163void* ffLibraryGetModule(const wchar_t* libraryFileName) {
164    // `libraryFileName` is non-null per the `nonnull(1)` contract.
165    // Prefer `ffGetPeb()->ImageBaseAddress` over looking the main module up by name.
166    void* module = nullptr;
167    USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t) + sizeof(wchar_t));
168    NTSTATUS status = LdrGetDllHandle(nullptr, nullptr, &(UNICODE_STRING) {
169                                                      .Length = libraryFileNameBytes - sizeof(wchar_t),
170                                                      .MaximumLength = libraryFileNameBytes,
171                                                      .Buffer = (wchar_t*) libraryFileName,
172                                                  },
173        &module);
174    if (!NT_SUCCESS(status)) {
175        FF_DEBUG("LdrGetDllHandle failed for library %ls with status 0x%08lX: %s", libraryFileName, status, ffDebugNtStatus(status));
176        return nullptr;
177    }
178    return module;
179}
180#endif
181
182struct LibraryIterateDynamicLibsBundle {
183    FFLibraryIterateCallback callback;
184    void* userData;
185};
186
187#if _WIN32
188
189static void ffLibraryIterateDynamicLibsCallback(PLDR_DATA_TABLE_ENTRY DataTableEntry, PVOID Context, BOOLEAN* StopEnumeration) {
190    if (DataTableEntry->FullDllName.Buffer == nullptr || DataTableEntry->FullDllName.Buffer[0] == L'\0') {
191        return;
192    }
193
194    char path[PATH_MAX * 3];
195    ULONG outBytes;
196    if (NT_SUCCESS(RtlUnicodeToUTF8N(path, sizeof(path), &outBytes, DataTableEntry->FullDllName.Buffer, DataTableEntry->FullDllName.Length))) {
197        path[outBytes] = '\0';
198        struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) Context;
199        *StopEnumeration = !bundle->callback(path, bundle->userData);
200    }
201}
202
203bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
204    struct LibraryIterateDynamicLibsBundle bundle = {
205        .callback = callback,
206        .userData = userData,
207    };
208    return NT_SUCCESS(LdrEnumerateLoadedModules(FALSE, ffLibraryIterateDynamicLibsCallback, &bundle));
209}
210
211#elif defined(__APPLE__)
212
213#include <mach-o/dyld.h>
214
215bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
216    uint32_t imageCount = _dyld_image_count();
217    for (uint32_t i = 0; i < imageCount; ++i) {
218        const char* name = _dyld_get_image_name(i);
219        if (name == nullptr || name[0] == '\0') {
220            continue;
221        }
222
223        if (!callback(name, userData)) {
224            break;
225        }
226    }
227
228    return true;
229}
230
231#elif __has_include(<link.h>)
232    #include <link.h>
233
234static int ffLibraryIterateDynamicLibsCallback(struct dl_phdr_info* info, size_t, void* data) {
235    if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
236        return 0;
237    }
238
239    struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) data;
240    return !bundle->callback(info->dlpi_name, bundle->userData);
241}
242
243bool ffLibraryIterateDynamicLibs(bool (*callback)(const char* name, void* userData), void* userData) {
244    dl_iterate_phdr(
245        ffLibraryIterateDynamicLibsCallback,
246        &(struct LibraryIterateDynamicLibsBundle) {
247            .callback = callback,
248            .userData = userData,
249        });
250    return true;
251}
252
253#else
254
255bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback, void*) {
256    // Not implemented for this platform
257    return false;
258}
259
260#endif