Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/lsst/cpputils/packaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ namespace cpputils {
*/
std::string getPackageDir(std::string const& packageName);

/*!
* \brief return the root directory of the package whose shared library contains
* the given address
*
* The address is resolved to its containing shared library via `dladdr`, and the
* package root is derived from that library's location (`.../lib/libFoo.so` ->
* `...`). This allows a package to locate its own data files without relying on
* environment variables, provided the address belongs to a symbol compiled into
* that package's own shared library.
*
* \param[in] addressInLibrary address of a symbol residing in the target
* package's shared library
*
* \throw lsst::pex::exceptions::NotFoundError if the library cannot be located
*/
std::string getPackageDirFromAddress(void const* addressInLibrary);

}
} // namespace lsst::cpputils

Expand Down
33 changes: 33 additions & 0 deletions src/packaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

#include "lsst/cpputils/packaging.h"

#include <dlfcn.h>

#include <filesystem>
#include <iostream>
#include <sstream>
Comment on lines +25 to 29
#include <string>
Expand All @@ -44,4 +47,34 @@ std::string getPackageDir(std::string const& packageName) {
return dir;
}

std::string getPackageDirFromAddress(void const* addressInLibrary) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle, one could reinforce the code and check that the pointer is not null:

if (addressInLibrary == nullptr) {
    throw std::invalid_argument("Null pointer passed the parameter");
}

I'm sure the null pointer will be noticed by dladdr. However, the function doesn't seem to have any specific code for this scenario:

// A null pointer is a caller precondition violation, not a lookup failure;
// reject it explicitly rather than relying on dladdr's unspecified behavior.
if (addressInLibrary == nullptr) {
throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,
"Null address passed to getPackageDirFromAddress");
}

// dladdr resolves an address to the shared object whose memory map contains
// it, regardless of which library's code is calling dladdr. Passing an
// address from the target package's own library therefore yields that
// library's path (works identically for .so on Linux and .dylib on macOS).
Dl_info info{};
if (dladdr(addressInLibrary, &info) == 0 || info.dli_fname == nullptr) {
throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError,
"Could not locate the shared library for the given address");
}

std::error_code ec;
std::filesystem::path libraryPath = std::filesystem::canonical(info.dli_fname, ec);
if (ec) {
throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError,
"Could not resolve shared library path '" +
std::string(info.dli_fname) + "': " + ec.message());
}

// .../<packageRoot>/lib/lib<name>.{so,dylib} -> <packageRoot>
return libraryPath.parent_path().parent_path().string();
}

}} // namespace lsst::cpputils
16 changes: 16 additions & 0 deletions tests/test_packaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,20 @@ BOOST_AUTO_TEST_CASE(GetPackage) {
lsst::pex::exceptions::NotFoundError);
}

BOOST_AUTO_TEST_CASE(GetPackageDirFromAddress) {
// The address of a symbol defined in libcpputils resolves, via dladdr, to
// the cpputils package directory -- independent of any environment variable.
auto anchor = reinterpret_cast<void const *>(&getPackageDirFromAddress);
// A valid address must resolve without throwing.
std::filesystem::path cpputilsPath;
BOOST_REQUIRE_NO_THROW(cpputilsPath = getPackageDirFromAddress(anchor));
BOOST_CHECK(std::filesystem::is_regular_file(cpputilsPath / "tests" / "test_packaging.cc"));
// It must agree with the environment-variable-based lookup for the same package.
BOOST_CHECK_EQUAL(std::filesystem::canonical(cpputilsPath),
std::filesystem::canonical(getPackageDir("cpputils")));
// A null address is a precondition violation.
BOOST_CHECK_THROW(getPackageDirFromAddress(nullptr),
lsst::pex::exceptions::InvalidParameterError);
}

BOOST_AUTO_TEST_SUITE_END()
Loading