diff --git a/CMakeLists.txt b/CMakeLists.txt index b6f5414..ec7ae63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,10 @@ if(WIN32) target_compile_definitions(${PROJECT_NAME} PRIVATE "RCPPUTILS_BUILDING_LIBRARY") endif() +set_target_properties(${PROJECT_NAME} PROPERTIES + BUILD_RPATH ${CMAKE_CURRENT_BINARY_DIR} + INSTALL_RPATH_USE_LINK_PATH TRUE +) ament_target_dependencies(${PROJECT_NAME} rcutils) ament_export_libraries(${PROJECT_NAME}) @@ -71,12 +75,31 @@ if(BUILD_TESTING) ament_add_gtest(test_endian test/test_endian.cpp) + if(WIN32) + set(LIBRARY_PATH_VAR "PATH") + elseif(APPLE) + set(LIBRARY_PATH_VAR "DYLD_LIBRARY_PATH") + else() + set(LIBRARY_PATH_VAR "LD_LIBRARY_PATH") + endif() + add_library(test_library SHARED test/test_library.cpp) + ament_add_gtest(test_find_library test/test_find_library.cpp) target_link_libraries(test_find_library ${PROJECT_NAME} test_library) set_tests_properties(test_find_library PROPERTIES ENVIRONMENT - "_TEST_LIBRARY_DIR=$;_TEST_LIBRARY=$") + "${LIBRARY_PATH_VAR}=$;_TEST_LIBRARY=$") + + # Test with empty LD_LIBRARY_PATH to emulate setcap / setuid executable. + # Using RUNPATH to find library instead. + if(NOT (WIN32 OR APPLE)) + ament_add_gtest(test_find_library_runpath test/test_find_library.cpp) + target_link_libraries(test_find_library_runpath ${PROJECT_NAME} test_library) + set_tests_properties(test_find_library_runpath PROPERTIES + ENVIRONMENT + "${LIBRARY_PATH_VAR}=;_TEST_LIBRARY=$") + endif() endif() ament_package() diff --git a/include/rcpputils/find_library.hpp b/include/rcpputils/find_library.hpp index ef3ef1f..4b41d27 100644 --- a/include/rcpputils/find_library.hpp +++ b/include/rcpputils/find_library.hpp @@ -26,7 +26,8 @@ namespace rcpputils { -/// Find a library located in the OS's specified environment variable for library paths. +/// Find a library located in the OS's specified environment variable for library paths +/// or in the RUNPATH binary header on Linux. /** * * The environment variable and file format per platform: diff --git a/src/find_library.cpp b/src/find_library.cpp index b809d47..d536d75 100644 --- a/src/find_library.cpp +++ b/src/find_library.cpp @@ -14,6 +14,10 @@ #include "rcpputils/find_library.hpp" +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#endif + #include #include @@ -69,12 +73,44 @@ std::list split(const std::string & value, const char delimiter) return list; } +// Retrieves a list of paths from the RUNPATH header on Linux. +// Useful when LD_LIBRARY_PATH is ignored for setcap / setuid executables. +std::list retrieve_runpath_paths() +{ +#if defined(_WIN32) || defined(__APPLE__) + // Return empty list for win / macos + return std::list(); +#else + const ElfW(Dyn) * dyn = _DYNAMIC; + const ElfW(Dyn) * runpath = NULL; + const char * strtab = NULL; + + for (; dyn->d_tag != DT_NULL; ++dyn) { + if (dyn->d_tag == DT_RUNPATH) { + runpath = dyn; + } else if (dyn->d_tag == DT_STRTAB) { + strtab = (const char *)dyn->d_un.d_val; + } + } + + std::string search_path; + if (strtab != NULL && runpath != NULL) { + search_path = std::string(strtab + runpath->d_un.d_val); + } + + return split(search_path, kPathSeparator); +#endif +} + } // namespace std::string find_library_path(const std::string & library_name) { - std::string search_path = get_env_var(kPathVar); - std::list search_paths = split(search_path, kPathSeparator); + const std::string search_path_env = get_env_var(kPathVar); + std::list search_paths = split(search_path_env, kPathSeparator); + + std::list search_paths_runpath = retrieve_runpath_paths(); + search_paths.splice(search_paths.cend(), search_paths_runpath); std::string filename = kSolibPrefix; filename += library_name + kSolibExtension; diff --git a/test/test_find_library.cpp b/test/test_find_library.cpp index d460904..0e22452 100644 --- a/test/test_find_library.cpp +++ b/test/test_find_library.cpp @@ -37,27 +37,6 @@ TEST(test_find_library, find_library) expected_library_path = _expected_library_path; } - const char * test_lib_dir{}; - EXPECT_EQ(rcutils_get_env("_TEST_LIBRARY_DIR", &test_lib_dir), nullptr); - EXPECT_NE(test_lib_dir, nullptr); - - // Set our relevant path variable. - const char * env_var{}; -#ifdef _WIN32 - env_var = "PATH"; -#elif __APPLE__ - env_var = "DYLD_LIBRARY_PATH"; -#else - env_var = "LD_LIBRARY_PATH"; -#endif - -#ifdef _WIN32 - EXPECT_EQ(_putenv_s(env_var, test_lib_dir), 0); -#else - const int override = 1; - EXPECT_EQ(setenv(env_var, test_lib_dir, override), 0); -#endif - // Positive test. const std::string test_lib_actual = find_library_path("test_library"); EXPECT_EQ(test_lib_actual, expected_library_path);