jaulib v1.3.0
Jau Support Library (C++, Java, ..)
dyn_linker.hpp
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2024 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24#pragma once
25
26#include <cstdint>
27#include <string>
28#include <memory>
29#include <mutex>
30#include <unordered_map>
31
32#include "jau/debug.hpp"
33#include "jau/string_util.hpp"
34#include "jau/environment.hpp"
35#include "jau/os/os_support.hpp"
36
37namespace jau::os {
38
39 /** \addtogroup OSSup
40 *
41 * @{
42 */
43
44 /** Low level secure dynamic linker access. */
46 public:
47 /** library handle */
48 typedef void* libhandle_t;
49 /** symbol handle within a library */
50 typedef void* symhandle_t;
51
52 protected:
53 constexpr static const bool DEBUG_LOOKUP = false;
54
55 //
56 // Implemented per platform as hidden detail
57 //
58
59 virtual libhandle_t openLibraryGlobalImpl(const std::string& pathname) noexcept = 0;
60 virtual libhandle_t openLibraryLocalImpl(const std::string& pathname) noexcept = 0;
61 virtual const char* lookupLibraryPathnameImpl(libhandle_t libraryHandle, const std::string& symbolName) noexcept = 0;
62 virtual symhandle_t lookupSymbolGlobalImpl(const std::string& symbolName) noexcept = 0;
63 virtual symhandle_t lookupSymbolLocalImpl(libhandle_t handle, const std::string& symbolName) noexcept = 0;
64 virtual void closeLibraryImpl(libhandle_t handle) noexcept = 0;
65 virtual std::string getLastErrorImpl() noexcept = 0;
66
67 private:
68 class LibRef {
69 private:
70 std::string m_name;
71 ssize_t m_count;
72 public:
73 LibRef(const std::string& name) noexcept
74 : m_name(name), m_count(1) {}
75
76 constexpr_cxx20 LibRef(const LibRef& o) noexcept = default;
77 constexpr_cxx20 LibRef(LibRef&& o) noexcept = default;
78 LibRef& operator=(const LibRef&) noexcept = default;
79 LibRef& operator=(LibRef&&) noexcept = default;
80
81 ssize_t incrRefCount() { return ++m_count; }
82 ssize_t decrRefCount() { return --m_count; }
83 ssize_t count() { return m_count; }
84
85 const std::string& name() { return m_name; }
86
87 std::string toString() { return "LibRef["+m_name+", count "+std::to_string(m_count)+"]"; }
88 };
89 typedef std::shared_ptr<LibRef> LibRef_ref;
90
91 typedef std::unordered_map<libhandle_t, LibRef_ref> LibRefMap_t;
92 typedef typename LibRefMap_t::iterator LibRefIter_t;
93
94 std::mutex m_mtx_libref;
95 LibRefMap_t m_handleToNameMap;
96
97 static DynamicLinker* create();
98
99 protected:
100 DynamicLinker() noexcept = default;
101
102 LibRef_ref incrLibRefCount(const libhandle_t handle, const std::string& libName) noexcept {
103 std::unique_lock<std::mutex> lock(m_mtx_libref);
104 LibRef_ref libRef = nullptr;
105 LibRefIter_t iter = m_handleToNameMap.find(handle);
106 if( m_handleToNameMap.end() == iter ) {
107 std::pair<LibRefIter_t,bool> res = m_handleToNameMap.insert( { handle, std::make_shared<LibRef>(libName) } );
108 libRef = res.first->second;
109 if( !res.second ) {
110 libRef->incrRefCount();
111 }
112 } else {
113 libRef = iter->second;
114 libRef->incrRefCount();
115 }
116 DBG_PRINT("DynamicLinkerImpl.incrLibRefCount %s -> %s, libs loaded %zu",
117 jau::to_hexstring(handle).c_str(), libRef->toString().c_str(), m_handleToNameMap.size());
118 return libRef;
119 }
120
121 LibRef_ref decrLibRefCount(const libhandle_t handle) noexcept {
122 std::unique_lock<std::mutex> lock(m_mtx_libref);
123 LibRef_ref libRef = nullptr;
124 LibRefIter_t iter = m_handleToNameMap.find(handle);
125 if( m_handleToNameMap.end() != iter ) {
126 libRef = iter->second;
127 if( 0 == libRef->decrRefCount() ) {
128 m_handleToNameMap.erase(iter);
129 }
130 DBG_PRINT("DynamicLinkerImpl.decrLibRefCount %s -> %s, libs loaded %zu",
131 jau::to_hexstring(handle).c_str(), libRef->toString().c_str(), m_handleToNameMap.size());
132 } else {
133 DBG_PRINT("DynamicLinkerImpl.decrLibRefCount %s -> null, libs loaded %zu",
134 jau::to_hexstring(handle).c_str(), m_handleToNameMap.size());
135 }
136 return libRef;
137 }
138
139 public:
140 virtual ~DynamicLinker() noexcept = default;
141
142 /** Returns the environment library path variable name, e.g. `LD_LIBRARY_PATH` */
143 constexpr_cxx20 static std::string getEnvLibPathVarName() noexcept {
144 if constexpr ( jau::os::is_darwin() ) {
145 return "DYLD_LIBRARY_PATH";
146 } else if constexpr ( jau::os::is_windows() ) {
147 return "PATH";
148 } else {
149 return "LD_LIBRARY_PATH";
150 }
151 }
152 /**
153 * Returns a list of system paths, from the {@link #getSystemEnvLibraryPathVarname()} variable.
154 */
155 static std::vector<std::string> getSystemEnvLibraryPaths() {
157 }
158
159 /** Returns the native library prefix, e.g. `lib` */
160 constexpr_cxx20 static std::string getDefaultPrefix() noexcept {
161 if constexpr ( jau::os::is_windows() ) {
162 return "";
163 } else {
164 return "lib";
165 }
166 }
167 /** Returns the native library suffix including the dot, e.g. `.so` */
168 constexpr_cxx20 static std::string getDefaultSuffix() noexcept {
169 if constexpr ( jau::os::is_darwin() ) {
170 return ".dylib";
171 } else if constexpr ( jau::os::is_windows() ) {
172 return ".dll";
173 } else {
174 return ".so";
175 }
176 }
177 /**
178 * Returns canonical library name for this system from given library-basename, e.g. `tool' -> `libtool.so`
179 * if it is not yet canonical, see isCanonicalName().
180 * @param basename the library basename
181 * @param checkIsCanonical pass true to first check is basename is already canonical, defaults to true
182 * @see isCanonicalName()
183 * @see getBaseName()
184 * @see jau::fs::basename()
185 */
186 constexpr_cxx20 static std::string getCanonicalName(const std::string& basename, const bool checkIsCanonical=true) noexcept {
187 if( !checkIsCanonical || !isCanonicalName(basename, true) ) {
189 } else {
190 return basename;
191 }
192 }
193
194 /**
195 * Returns true if the given filename contains the canonical prefix and suffix,
196 * otherwise returns false.
197 *
198 * Validation is performed case insensitive if `caseInsensitive == true`
199 *
200 * @param filename the filename to test
201 * @param isBasename pass true if filename is a basename, otherwise false. Defaults to false. See jau::fs::basename().
202 * @param caseInsensitive perform prefix and suffix comparison case-insensitive, defaults to true on Windows otherwise false
203 * @see getCanonicalName()
204 * @see getBaseName()
205 * @see jau::fs::basename()
206 */
207 static bool isCanonicalName(const std::string& filename, const bool isBasename=false, const bool caseInsensitive=jau::os::is_windows()) noexcept;
208
209 /**
210 * Returns the library basename, i.e. the file basename without prefix nor suffix,
211 * performed case insensitive if `caseInsensitive == true`
212 *
213 * @param filename the filename to process
214 * @param isBasename pass true if filename is a basename, otherwise false. Defaults to false. See jau::fs::basename().
215 * @param caseInsensitive perform prefix and suffix comparison case-insensitive, defaults to true on Windows otherwise false
216 * @return basename of filename w/o path nor prefix or suffix, ie. /usr/lib/libDrinkBeer.so -> DrinkBeer on Unix systems
217 * @see getCanonicalName()
218 * @see isCanonicalName()
219 * @see jau::fs::basename()
220 */
221 static std::string getBaseName(const std::string& filename, const bool isBasename=false, const bool caseInsensitive=jau::os::is_windows()) noexcept;
222
223 /**
224 * Returns list of potential absolute library filenames
225 * @param libName library basename (implies file-basename) or canonical-library-name as file-basename or absolute-path
226 * @param searchSystemPath
227 * @param searchSystemPathFirst
228 */
229 static std::vector<std::string> enumerateLibraryPaths(const std::string& libName,
230 bool searchSystemPath=false,
231 bool searchSystemPathFirst=false) noexcept;
232
233
234 /** Returns static singleton instance of DynamicLinker */
235 static DynamicLinker& get() noexcept {
236 /**
237 * Thread safe starting with C++11 6.7:
238 *
239 * If control enters the declaration concurrently while the variable is being initialized,
240 * the concurrent execution shall wait for completion of the initialization.
241 *
242 * (Magic Statics)
243 *
244 * Avoiding non-working double checked locking.
245 */
246 static DynamicLinker* dl = create();
247 return *dl;
248 }
249
250 /**
251 * Opens the named library, allowing system wide access for other <i>users</i>.
252 *
253 * @param pathname the full pathname for the library to open
254 * @return the library handle, maybe 0 if not found.
255 */
256 libhandle_t openLibraryGlobal(const std::string& pathname) noexcept {
257 libhandle_t handle = openLibraryGlobalImpl(pathname);
258 if( nullptr != handle ) {
259 LibRef_ref libRef = incrLibRefCount(handle, pathname);
260 DBG_PRINT("DynamicLinkerImpl.openLibraryGlobal \"%s\": %s -> %s",
261 pathname.c_str(), jau::to_hexstring(handle).c_str(), libRef->toString().c_str());
262 } else {
263 DBG_PRINT("DynamicLinkerImpl.openLibraryGlobal \"%s\" failed, error %s",
264 pathname.c_str(), getLastError().c_str());
265 }
266 return handle;
267 }
268
269 /**
270 * Opens the named library, restricting access to this process.
271 *
272 * @param pathname the full pathname for the library to open
273 * @return the library handle, maybe 0 if not found.
274 */
275 libhandle_t openLibraryLocal(const std::string& pathname) noexcept {
276 libhandle_t handle = openLibraryLocalImpl(pathname);
277 if( nullptr != handle ) {
278 LibRef_ref libRef = incrLibRefCount(handle, pathname);
279 DBG_PRINT("DynamicLinkerImpl.openLibraryLocal \"%s\": %s -> %s",
280 pathname.c_str(), jau::to_hexstring(handle).c_str(), libRef->toString().c_str());
281 } else {
282 DBG_PRINT("DynamicLinkerImpl.openLibraryLocal \"%s\" failed, error %s",
283 pathname.c_str(), getLastError().c_str());
284 }
285 return handle;
286 }
287
288 /**
289 * @param libraryHandle a library handle previously retrieved via {@link #openLibraryLocal(const std::string_view&, boolean)} or {@link #openLibraryGlobal(const std::string_view&, boolean)}.
290 * @param symbolName optional symbol name for an OS which requires the symbol's address to retrieve the path of the containing library
291 * @return the library pathname if found and supported by OS or {@code null}.
292 */
293 const char* lookupLibraryPathname(libhandle_t handle, const std::string& symbolName) noexcept {
294 const char* fname = lookupLibraryPathnameImpl(handle, symbolName);
295 if(DEBUG_LOOKUP) {
296 jau::INFO_PRINT("DynamicLinkerImpl.lookupLibraryPathname(%s, %s) -> '%s'",
297 jau::to_hexstring(handle).c_str(), symbolName.c_str(), nullptr != fname ? fname : "null");
298 }
299 return fname;
300 }
301
302 /**
303 * @param symbolName global symbol name to lookup up system wide.
304 * @return the symbol handle, maybe nullptr if not found.
305 */
306 symhandle_t lookupSymbolGlobal(const std::string& symbolName) noexcept {
307 symhandle_t addr = lookupSymbolGlobalImpl(symbolName);
308 if(DEBUG_LOOKUP) {
309 jau::INFO_PRINT("DynamicLinkerImpl.lookupSymbolGlobal(%s) -> %s",
310 symbolName.c_str(), jau::to_hexstring(addr).c_str());
311 }
312 return addr;
313 }
314
315 /**
316 * @param libraryHandle a library handle previously retrieved via {@link #openLibraryLocal(const std::string_view&, boolean)} or {@link #openLibraryGlobal(const std::string_view&, boolean)}.
317 * @param symbolName global symbol name to lookup up system wide.
318 * @return the symbol handle, maybe nullptr if not found.
319 */
320 symhandle_t lookupSymbol(libhandle_t handle, const std::string& symbolName) noexcept {
321 symhandle_t addr = lookupSymbolLocalImpl(handle, symbolName);
322 if(DEBUG_LOOKUP) {
323 jau::INFO_PRINT("DynamicLinkerImpl.lookupSymbol(%s, %s) -> %s",
324 jau::to_hexstring(handle).c_str(), symbolName.c_str(), jau::to_hexstring(addr).c_str());
325 }
326 return addr;
327 }
328
329 /**
330 * Security checks are implicit by previous call of
331 * {@link #openLibraryLocal(const std::string_view&, boolean)} or {@link #openLibraryGlobal(const std::string_view&, boolean)}
332 * retrieving the <code>librarHandle</code>.
333 *
334 * @param libraryHandle a library handle previously retrieved via {@link #openLibraryLocal(const std::string_view&, boolean)} or {@link #openLibraryGlobal(const std::string_view&, boolean)}.
335 */
336 void closeLibrary(libhandle_t handle) noexcept {
337 LibRef_ref libRef = decrLibRefCount( handle ); // null libRef is OK for global lookup
338 if( nullptr != libRef ) {
339 DBG_PRINT("DynamicLinkerImpl.closeLibrary(%s -> %s)",
340 jau::to_hexstring(handle).c_str(), libRef->toString().c_str());
341 } else {
342 DBG_PRINT("DynamicLinkerImpl.closeLibrary(%s -> null)", jau::to_hexstring(handle).c_str());
343 }
344 if( nullptr != handle ) {
345 closeLibraryImpl(handle);
346 }
347 }
348 /**
349 * Returns a string containing the last error.
350 * Maybe called for debugging purposed if any method fails.
351 * @return error string, maybe null. A null or non-null value has no semantics.
352 */
353 std::string getLastError() noexcept { return getLastErrorImpl(); }
354 };
355
356 /**@}*/
357
358} // namespace jau::os
359
static std::string getProperty(const std::string &name) noexcept
Returns the value of the environment's variable 'name'.
Definition: environment.cpp:60
Low level secure dynamic linker access.
Definition: dyn_linker.hpp:45
static std::string getBaseName(const std::string &filename, const bool isBasename=false, const bool caseInsensitive=jau::os::is_windows()) noexcept
Returns the library basename, i.e.
Definition: os_support.cpp:262
static constexpr const bool DEBUG_LOOKUP
Definition: dyn_linker.hpp:53
virtual const char * lookupLibraryPathnameImpl(libhandle_t libraryHandle, const std::string &symbolName) noexcept=0
virtual libhandle_t openLibraryLocalImpl(const std::string &pathname) noexcept=0
virtual libhandle_t openLibraryGlobalImpl(const std::string &pathname) noexcept=0
static DynamicLinker & get() noexcept
Returns static singleton instance of DynamicLinker.
Definition: dyn_linker.hpp:235
symhandle_t lookupSymbolGlobal(const std::string &symbolName) noexcept
Definition: dyn_linker.hpp:306
virtual symhandle_t lookupSymbolLocalImpl(libhandle_t handle, const std::string &symbolName) noexcept=0
static std::vector< std::string > enumerateLibraryPaths(const std::string &libName, bool searchSystemPath=false, bool searchSystemPathFirst=false) noexcept
Returns list of potential absolute library filenames.
Definition: os_support.cpp:321
LibRef_ref decrLibRefCount(const libhandle_t handle) noexcept
Definition: dyn_linker.hpp:121
symhandle_t lookupSymbol(libhandle_t handle, const std::string &symbolName) noexcept
Definition: dyn_linker.hpp:320
virtual std::string getLastErrorImpl() noexcept=0
void * symhandle_t
symbol handle within a library
Definition: dyn_linker.hpp:50
static constexpr_cxx20 std::string getDefaultPrefix() noexcept
Returns the native library prefix, e.g.
Definition: dyn_linker.hpp:160
static constexpr_cxx20 std::string getDefaultSuffix() noexcept
Returns the native library suffix including the dot, e.g.
Definition: dyn_linker.hpp:168
LibRef_ref incrLibRefCount(const libhandle_t handle, const std::string &libName) noexcept
Definition: dyn_linker.hpp:102
libhandle_t openLibraryGlobal(const std::string &pathname) noexcept
Opens the named library, allowing system wide access for other users.
Definition: dyn_linker.hpp:256
DynamicLinker() noexcept=default
static constexpr_cxx20 std::string getEnvLibPathVarName() noexcept
Returns the environment library path variable name, e.g.
Definition: dyn_linker.hpp:143
void * libhandle_t
library handle
Definition: dyn_linker.hpp:48
const char * lookupLibraryPathname(libhandle_t handle, const std::string &symbolName) noexcept
Definition: dyn_linker.hpp:293
virtual ~DynamicLinker() noexcept=default
std::string getLastError() noexcept
Returns a string containing the last error.
Definition: dyn_linker.hpp:353
static constexpr_cxx20 std::string getCanonicalName(const std::string &basename, const bool checkIsCanonical=true) noexcept
Returns canonical library name for this system from given library-basename, e.g.
Definition: dyn_linker.hpp:186
libhandle_t openLibraryLocal(const std::string &pathname) noexcept
Opens the named library, restricting access to this process.
Definition: dyn_linker.hpp:275
void closeLibrary(libhandle_t handle) noexcept
Security checks are implicit by previous call of openLibraryLocal(const std::string_view&,...
Definition: dyn_linker.hpp:336
static bool isCanonicalName(const std::string &filename, const bool isBasename=false, const bool caseInsensitive=jau::os::is_windows()) noexcept
Returns true if the given filename contains the canonical prefix and suffix, otherwise returns false.
Definition: os_support.cpp:266
static std::vector< std::string > getSystemEnvLibraryPaths()
Returns a list of system paths, from the getSystemEnvLibraryPathVarname() variable.
Definition: dyn_linker.hpp:155
virtual void closeLibraryImpl(libhandle_t handle) noexcept=0
virtual symhandle_t lookupSymbolGlobalImpl(const std::string &symbolName) noexcept=0
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition: debug.hpp:52
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
#define constexpr_cxx20
constexpr qualifier replacement for C++20 constexpr.
std::string basename(const std::string_view &path) noexcept
Return stripped leading directory components from given path separated by /.
Definition: file_util.cpp:151
constexpr bool is_darwin() noexcept
Evaluates true if platform os_type::native contains os_type::Darwin.
Definition: os_support.hpp:212
constexpr_cxx20 std::string path_separator() noexcept
Returns the OS's path separator as a string, e.g.
Definition: os_support.hpp:335
constexpr bool is_windows() noexcept
Evaluates true if platform os_type::native contains os_type::Windows.
Definition: os_support.hpp:200
std::vector< std::string > split_string(const std::string &str, const std::string &separator) noexcept
Split given string str at separator into the resulting std::vector excluding the separator sequence .
std::string to_hexstring(value_type const &v) noexcept
Produce a lower-case hexadecimal string representation of the given pointer.
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2024 Gothel Software e.K.
Definition: dyn_linker.hpp:37
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition: backtrace.hpp:32
void INFO_PRINT(const char *format,...) noexcept
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
Definition: debug.cpp:248
STL namespace.