jaulib v1.3.6
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
test_os_dynlinker.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 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#include <cassert>
25#include <cstring>
26#include <pthread.h>
27
28#include <jau/test/catch2_ext.hpp>
29
30#include <jau/file_util.hpp>
31#include <jau/environment.hpp>
32#include <jau/os/dyn_linker.hpp>
33#include <jau/os/native_lib.hpp>
34
35using namespace jau::enums;
36
37static bool existsPath(const std::string& libPath) noexcept {
38 jau::fs::file_stats path_stats(libPath);
39 return path_stats.exists();
40}
41static bool existsLibBasename(const std::string& libBasename, const std::string& relDir, std::string& libPath) noexcept {
42 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
43 libPath = jau::fs::dirname(executable_path) + "/" + relDir + "/"+libName;
44 return existsPath(libPath);
45}
46
47// Test 00 Move testlib.* -> orig/, copy orig/testlib.so.1.2.3 to copy/testlib2.so
48TEST_CASE( "Test00", "[dll][os]" ) {
49 // Paranoia constraints!
50 //
51 // First move testlib.* into a new sub-folder to NOT have system linker:
52 // - find it at cwd
53 //
54 // Second copy testlib.so into a new file to NOT have system linker:
55 // - reuse the already linked native library
56 // - the path location of the already linked native library
57 //
58 // Result:
59 // - test_dir/orig/libtest.so* (all files w/ symlinks)
60 // - test_dir/copy/libtest2.so (single file)
61 //
62 const std::string libBasename = "testlib";
63 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
64 const std::string libPathBuild = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/"+libName;
65 const std::string libPathOrig = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/orig/"+libName;
66 if( !existsPath(libPathBuild) ) {
67 if( !existsPath(libPathOrig) ) {
68 std::cout << "Warning: library '" << libBasename << "' doesn't exist at: build '" << libPathBuild << "', nor at orig '" << libPathOrig << "'" << std::endl;
69 return;
70 }
71 // OK, already moved
72 } else {
73 // move test_dir/testlib.* to test_dir/orig/testlib.* (overwrite)
74 jau::fs::file_stats path_stats(libPathBuild);
75 const std::string libPath = path_stats.final_target()->path();
76 const std::string libDir = jau::fs::dirname(libPath);
77 const std::string libDirOrig = libDir + "/orig";
78
79 // recreate test_dir/orig
80 std::cout << "remove: " << libDirOrig << std::endl;
82 REQUIRE( true == jau::fs::mkdir(libDirOrig, jau::fs::fmode_t::def_dir_prot, true) );
83
84 // move all libName* from test_dir to test_dir/orig at depth 1 (flat)
85 {
89 std::cout << "move: libs in '" << libDir << "' to '" << libDirOrig << "'" << std::endl;
90 const jau::fs::path_visitor pv = [&](jau::fs::traverse_event tevt, const jau::fs::file_stats& element_stats, size_t depth) -> bool {
92 std::cout << "- move: ignore entry depth[" << depth << "]" << element_stats.item().to_string() << std::endl;
93 return false;
94 }
95 if( jau::fs::traverse_event::none != ( tevt & ( jau::fs::traverse_event::file | jau::fs::traverse_event::symlink ) ) ) { // at least one of: file + link
96 std::string bname = element_stats.item().basename();
97 if( bname.starts_with(libName) ) {
98 const std::string p = libDirOrig + "/" + bname;
99 std::cout << "- move: depth[" << depth << "]: '" << element_stats.path() << "' to '" << p << "'" << std::endl;
100 jau::fs::rename(element_stats.path(), p);
101 }
102 }
103 return true;
104 };
105 REQUIRE( true == jau::fs::visit(libDir, topts, pv) );
106 jau::fs::file_stats path_stats2(libPathOrig);
107 std::cout << "post move: " << path_stats2.to_string() << std::endl;
108 REQUIRE( true == existsPath(libPathOrig) );
109 }
110 }
111 // copy test_dir/orig/testlib.so.1.2.3 to test_dir/copy/testlib2.so
112 {
116 jau::fs::file_stats path_stats(libPathOrig);
117 const std::string libPathOrigFile = path_stats.final_target()->path();
118
119 const std::string libBasenameCopy = "testlib2";
120 const std::string libNameCopy = jau::os::DynamicLinker::getCanonicalName(libBasenameCopy);
121 const std::string libDirCopy = jau::fs::absolute( jau::fs::dirname(executable_path) ) + "/copy";
122
123 // recreate test_dir/copy
124 if( existsPath( libDirCopy ) ) {
125 std::cout << "remove: " << libDirCopy << std::endl;
127 }
128 REQUIRE( true == jau::fs::mkdir(libDirCopy, jau::fs::fmode_t::def_dir_prot, true) );
129
130 const std::string libPathCopy = libDirCopy + "/" + libNameCopy;
131 REQUIRE( true == jau::fs::copy(libPathOrigFile, libPathCopy, copts) );
132 REQUIRE( true == existsPath(libPathCopy) );
133 std::string libPath2;
134 REQUIRE( true == existsLibBasename(libBasenameCopy, "copy", libPath2) );
135 }
136}
137
138void test01DynamikLinkerAbs(const std::string& libBasename, const std::string& relDir) {
139 const std::string symbolName = "jaulib_id_entryfunc";
140 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
141 std::cout << "- libBasename: " << libBasename << std::endl;
142 std::cout << "- libName: " << libName << std::endl;
143
144 std::string libPath;
145 {
146 std::cout << "- cwd: " << jau::fs::get_cwd() << std::endl;
147 std::cout << "- exe " << executable_path << std::endl << std::endl;
148 if( !existsLibBasename(libBasename, relDir, libPath) ) {
149 std::cout << "Warning: library '" << libName << "' doesn't exist at: '" << libPath << "'" << std::endl;
150 return;
151 }
152 }
155 std::cout << "- Path: " << libPath << std::endl;
156 std::cout << "- LibHandle: " << jau::to_hexstring(libHandle) << std::endl;
157 REQUIRE( nullptr != libHandle );
158
159 jau::os::DynamicLinker::symhandle_t symHandle = dl.lookupSymbol(libHandle, symbolName);
160 std::cout << "- Symbol '" << symbolName << "': Handle = " << jau::to_hexstring(symHandle) << std::endl;
161 REQUIRE( nullptr != symHandle );
162
163 const char* nativePath = dl.lookupLibraryPathname(libHandle, symbolName);
164 if( nullptr == nativePath ) {
165 std::cout << "- Native Path: null" << std::endl;
166 } else {
167 std::cout << "- Native Path: '" << std::string(nativePath) << "'" << std::endl;
168 }
169 REQUIRE( nullptr != nativePath );
170
171 {
172 std::string bname1 = jau::os::DynamicLinker::getBaseName(libPath);
173 std::cout << "- Basename (path): " << bname1 << std::endl;
174 REQUIRE( libBasename == bname1 );
175 if( nullptr != nativePath ) {
176 std::string bname2 = jau::os::DynamicLinker::getBaseName(std::string(nativePath));
177 std::cout << "- Basename (native-path): " << bname2 << std::endl;
178 REQUIRE( libBasename == bname2 );
179 }
180 }
181}
182
183// Test 01 Local Open dlsym etc
184TEST_CASE( "Test01", "[dll][os]" ) {
185 {
186 std::string lib_path_var_name = jau::os::DynamicLinker::getEnvLibPathVarName();
187 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
188 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
189 std::cout << "- lib_path_var_name: " << lib_path_var_name << std::endl;
190 std::cout << "- lib_path_var : " << lib_path_var << std::endl;
191 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: ";
192 std::cout << jau::to_string(lib_paths, "\n - path: ") << std::endl;
193 }
194 test01DynamikLinkerAbs("testlib", "orig");
195 test01DynamikLinkerAbs("testlib2", "copy");
196}
197
198void test10NativeLibrary(const std::string& libBasename, const std::string& libDirRel) {
199 const std::string symbolName = "jaulib_id_entryfunc";
200 const std::string libName = jau::os::DynamicLinker::getCanonicalName(libBasename);
201 std::cout << "- libBasename: " << libBasename << std::endl;
202 std::cout << "- libName: " << libName << std::endl;
203 const std::string exe_path_abs = jau::fs::absolute(executable_path);
204 const std::string exe_dir = jau::fs::dirname(exe_path_abs);
205 const std::string cwd = jau::fs::get_cwd();
206 const std::string libPathRel = libDirRel + "/" + libName;
207 std::string libDirAbs, libPathAbs;
208 {
209 std::cout << "- cwd: " << cwd << std::endl;
210 std::cout << "- exe-rel " << executable_path << std::endl;
211 std::cout << "- exe-abs " << exe_path_abs << std::endl << std::endl;
212 {
213 jau::fs::file_stats fs(exe_dir + "/" + libDirRel);
214 REQUIRE( true == fs.exists() );
215 libDirAbs = fs.final_target()->path();
216 }
217 libPathAbs = libDirAbs + "/" + libName;
218
219 if( !existsPath(libPathAbs) ) {
220 std::cout << "Warning: library '" << libName << "' doesn't exist at: '" << libPathAbs << "'" << std::endl;
221 return;
222 }
223 }
224 std::string lib_path_var_name = jau::os::DynamicLinker::getEnvLibPathVarName();
225 std::string lib_path_var0 = jau::environment::getProperty( lib_path_var_name );
226 {
227 std::cout << "Sys-Path: '" << lib_path_var_name << "': Original" << std::endl;
228 std::vector<std::string> lib_paths1 = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
229 std::cout << "- lib_path_var (1) : " << lib_path_var0 << std::endl;
230 std::cout << "- lib_paths: count : " << lib_paths1.size() << std::endl << " - path: '";
231 std::cout << jau::to_string(lib_paths1, "'\n - path: '") << "'" << std::endl;
232 }
233
234 // 1: Test with libPathAbs, no search
235 {
236 const std::string cwd2 = jau::fs::get_cwd();
237 const bool searchSystemPath = false;
238 const bool searchSystemPathFirst = false;
239 const bool global = false;
240 std::cout << "Check-1: Absolute Path: '" << libPathAbs << "', cwd2 '" << cwd2 << "'" << std::endl;
241 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathAbs, searchSystemPath, searchSystemPathFirst, global, symbolName);
242 std::cout << "Check-1: " << nl.toString() << std::endl;
243 REQUIRE( true == nl.isValid() );
244 REQUIRE( true == nl.isOpen() );
245 nl.close();
246 REQUIRE( false == nl.isOpen() );
247 REQUIRE( true == nl.isValid() );
248 }
249
250 // 2: Test with libPathRel, cd into test_exe path and search from cwd (no sys)
251 {
252 REQUIRE( true == jau::fs::chdir(exe_dir) );
253 {
254 const std::string cwd2 = jau::fs::get_cwd();
255 const bool searchSystemPath = false;
256 const bool searchSystemPathFirst = false;
257 const bool global = false;
258 std::cout << "Check-2: Relative Path to cwd: libPathRel '" << libPathRel << "', cwd2 '" << cwd2 << "'" << std::endl;
259 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathRel, searchSystemPath, searchSystemPathFirst, global, symbolName);
260 std::cout << "Check-2: " << nl.toString() << std::endl;
261 REQUIRE( true == nl.isValid() );
262 REQUIRE( true == nl.isOpen() );
263 nl.close();
264 REQUIRE( false == nl.isOpen() );
265 REQUIRE( true == nl.isValid() );
266 }
267 REQUIRE( true == jau::fs::chdir(cwd) );
268 }
269
270 // Add libDirAbs to orig sys-path
271 {
272 std::cout << "Sys-Path: '" << lib_path_var_name << "': Variant 1: With libDirAbs" << std::endl;
273 std::string lib_path_var2;
274 if( lib_path_var0.size() > 0 ) {
275 lib_path_var2 = lib_path_var0+jau::os::path_separator()+libDirAbs;
276 } else {
277 lib_path_var2 = libDirAbs;
278 }
279 ::setenv(lib_path_var_name.c_str(), lib_path_var2.c_str(), 1 /* overwrite */);
280 std::cout << "- lib_path_var set 2: " << lib_path_var2 << std::endl;
281
282 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
283 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
284 std::cout << "- lib_path_var get 2: " << lib_path_var << std::endl;
285 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: '";
286 std::cout << jau::to_string(lib_paths, "'\n - path: '") << "'" << std::endl;
287 }
288
289 // 10+11: Test with libBasename + libName, search from sys-path (incl. libDirAbs)
290 {
291 const bool searchSystemPath = true;
292 const bool searchSystemPathFirst = true;
293 const bool global = false;
294 {
295 const std::string cwd2 = jau::fs::get_cwd();
296 std::cout << "Check-10: libBasename in sys: '" << libBasename << "', cwd2 '" << cwd2 << "'" << std::endl;
297 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libBasename, searchSystemPath, searchSystemPathFirst, global, symbolName);
298 std::cout << "Check-10: " << nl.toString() << std::endl;
299 REQUIRE( true == nl.isValid() );
300 REQUIRE( true == nl.isOpen() );
301 nl.close();
302 REQUIRE( false == nl.isOpen() );
303 REQUIRE( true == nl.isValid() );
304 }
305 {
306 const std::string cwd2 = jau::fs::get_cwd();
307 std::cout << "Check-11: libName in sys: '" << libName << "', cwd2 '" << cwd2 << "'" << std::endl;
308 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libName, searchSystemPath, searchSystemPathFirst, global, symbolName);
309 std::cout << "Check-11: " << nl.toString() << std::endl;
310 REQUIRE( true == nl.isValid() );
311 REQUIRE( true == nl.isOpen() );
312 nl.close();
313 REQUIRE( false == nl.isOpen() );
314 REQUIRE( true == nl.isValid() );
315 }
316 }
317
318 // Add test_exe path to orig sys-path
319 {
320 std::cout << "Sys-Path: '" << lib_path_var_name << "': Variant 2: With test_exe path" << std::endl;
321 std::string lib_path_var2;
322 if( lib_path_var0.size() > 0 ) {
323 lib_path_var2 = lib_path_var0+jau::os::path_separator()+exe_dir;
324 } else {
325 lib_path_var2 = exe_dir;
326 }
327 ::setenv(lib_path_var_name.c_str(), lib_path_var2.c_str(), 1 /* overwrite */);
328 std::cout << "- lib_path_var set 3: " << lib_path_var2 << std::endl;
329
330 std::string lib_path_var = jau::environment::getProperty( lib_path_var_name );
331 std::vector<std::string> lib_paths = jau::os::DynamicLinker::getSystemEnvLibraryPaths();
332 std::cout << "- lib_path_var get 3: " << lib_path_var << std::endl;
333 std::cout << "- lib_paths: count : " << lib_paths.size() << std::endl << " - path: '";
334 std::cout << jau::to_string(lib_paths, "'\n - path: '") << "'" << std::endl;
335 }
336
337#if 1
338 // 12: Test with libPathRel, search from sys-path (incl. text_exe path)
339 {
340 const bool searchSystemPath = true;
341 const bool searchSystemPathFirst = true;
342 const bool global = true;
343 std::cout << "Check-12: Relative Path to cwd: " << libPathRel << std::endl;
344 jau::os::NativeLibrary nl = jau::os::NativeLibrary::open(libPathRel, searchSystemPath, searchSystemPathFirst, global, symbolName);
345 std::cout << "Check-12: " << nl.toString() << std::endl;
346 REQUIRE( true == nl.isValid() );
347 REQUIRE( true == nl.isOpen() );
348 nl.close();
349 REQUIRE( false == nl.isOpen() );
350 REQUIRE( true == nl.isValid() );
351 }
352#endif
353}
354
355// Test 10 NativeLibrary Find / Open dlsym etc using orig filenames
356TEST_CASE( "Test10", "[orig][NativeLibrary][dll][os]" ) {
357 test10NativeLibrary("testlib", "orig");
358}
359
360// Test 10 NativeLibrary Find / Open dlsym etc using a copy w/ changed filename
361TEST_CASE( "Test11", "[copy][NativeLibrary][dll][os]" ) {
362 test10NativeLibrary("testlib2", "copy");
363}
static std::string getProperty(const std::string &name) noexcept
Returns the value of the environment's variable 'name'.
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
const file_stats * final_target(size_t *link_count=nullptr) const noexcept
Returns the final target element, either a pointer to this instance if not a symbolic-link or the fin...
std::string to_string() const noexcept
Returns a comprehensive string representation of this element.
std::string path() const noexcept
Returns the unix path representation.
Low level secure dynamic linker access.
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.
static DynamicLinker & get() noexcept
Returns static singleton instance of DynamicLinker.
symhandle_t lookupSymbol(libhandle_t handle, const std::string &symbolName) noexcept
void * symhandle_t
symbol handle within a library
static constexpr_cxx20 std::string getEnvLibPathVarName() noexcept
Returns the environment library path variable name, e.g.
void * libhandle_t
library handle
const char * lookupLibraryPathname(libhandle_t handle, const std::string &symbolName) noexcept
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.
libhandle_t openLibraryLocal(const std::string &pathname) noexcept
Opens the named library, restricting access to this process.
static std::vector< std::string > getSystemEnvLibraryPaths()
Returns a list of system paths, from the getSystemEnvLibraryPathVarname() variable.
Runtime libary dynamic library (RTLD) access.
bool isValid() const noexcept
Returns true if this instance is valid, i.e.
void close() noexcept
Closes this native library.
std::string toString() const noexcept
bool isOpen() const noexcept
Returns true if this instance isValid() and not close()'ed, otherwise false.
static NativeLibrary open(const std::string &libName, const bool searchSystemPath, const bool searchSystemPathFirst, const bool global) noexcept
Opens the given native library, assuming it has the same base name on all platforms.
std::string to_string(const endian_t v) noexcept
Return std::string representation of the given endian.
constexpr bool is_set(const E mask, const E bits) noexcept
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
bool copy(const std::string &source_path, const std::string &dest_path, const copy_options copts=copy_options::none) noexcept
Copy the given source_path to dest_path using copy_options.
std::string get_cwd() noexcept
Return the current working directory or empty on failure.
Definition file_util.cpp:84
bool visit(const std::string &path, const traverse_options topts, const path_visitor &visitor, std::vector< int > *dirfds=nullptr) noexcept
Visit element(s) of a given path, see traverse_options for detailed settings.
std::string absolute(const std::string_view &relpath) noexcept
Returns the absolute path of given relpath if existing, otherwise an empty string.
std::string dirname(const std::string_view &path) noexcept
Return stripped last component from given path separated by /, excluding the trailing separator /.
copy_options
Filesystem copy options used to copy() path elements.
traverse_options
Filesystem traverse options used to visit() path elements.
bool rename(const std::string &oldpath, const std::string &newpath) noexcept
Rename oldpath to newpath using POSIX rename(), with the following combinations.
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
bool chdir(const std::string &path) noexcept
Change working directory.
traverse_event
Filesystem traverse event used to call path_visitor for path elements from visit().
jau::function< bool(traverse_event, const file_stats &, size_t)> path_visitor
path_visitor jau::FunctionDef definition
@ def_dir_prot
Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_g...
@ verbose
Enable verbosity mode, show error messages on stderr.
@ overwrite
Overwrite existing destination files.
@ preserve_all
Preserve uid and gid if allowed and access- and modification-timestamps, i.e.
@ verbose
Enable verbosity mode, potentially used by a path_visitor implementation like remove().
@ dir_check_entry
Call path_visitor at directory entry, allowing path_visitor to skip traversal of this directory if re...
@ recursive
Traverse through directories, i.e.
@ none
No value, neither file, symlink nor dir_entry or dir_exit.
@ file
Visiting a file, may be in conjunction with symlink, i.e.
@ dir_check_entry
Visiting a directory on entry, see traverse_options::dir_check_entry.
@ symlink
Visiting a symbolic-link, either to a file or a non-existing entity.
constexpr_cxx20 std::string path_separator() noexcept
Returns the OS's path separator as a string, e.g.
std::string to_hexstring(value_type const &v, const bool skipLeading0x=false) noexcept
Produce a lower-case hexadecimal string representation with leading 0x in MSB of the given pointer.
Author: Sven Gothel sgothel@jausoft.com Copyright Gothel Software e.K.
Definition enum_util.hpp:65
Author: Sven Gothel sgothel@jausoft.com Copyright (c) 2022 Gothel Software e.K.
Definition file_util.hpp:41
static bool existsLibBasename(const std::string &libBasename, const std::string &relDir, std::string &libPath) noexcept
void test10NativeLibrary(const std::string &libBasename, const std::string &libDirRel)
TEST_CASE("Test00", "[dll][os]")
void test01DynamikLinkerAbs(const std::string &libBasename, const std::string &relDir)
static bool existsPath(const std::string &libPath) noexcept