// SPDX-License-Identifier: MIT
#include "DSOLoader.hh"
#include "DSOExceptions.hh"
#if defined(DSOLD_WITH_SIGN)
# include <sign/Verifier.hh>
#endif
#include <dlfcn.h>
#include <cstdlib>
#include <filesystem>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#ifdef __CYGWIN__
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif
#if defined( hpux ) || defined( __hpux ) || defined( __hpux__ )
#define LIBPATH "SHLIB"
#elif defined( _AIX )
#define LIBPATH "LIBPATH"
#elif defined( __CYGWIN__ )
#define LIBPATH "PATH"
#else
#define LIBPATH "LD_LIBRARY_PATH"
#endif
// ---------------------------------------------------------------------------
// DSO handle RAII guard (anonymous namespace, file-local)
// ---------------------------------------------------------------------------
namespace
{
class HandleGuard final
{
public:
explicit HandleGuard( void* h ) noexcept : handle( h ) {}
HandleGuard( HandleGuard&& o ) noexcept : handle( o.handle ) { o.handle = nullptr; }
~HandleGuard( ) noexcept { if( handle ) dlclose( handle ); }
HandleGuard( HandleGuard const& ) = delete;
HandleGuard& operator=( HandleGuard const& ) = delete;
HandleGuard& operator=( HandleGuard&& ) = delete;
bool operator<( HandleGuard const& o ) const noexcept { return handle < o.handle; }
void* handle;
};
std::set< HandleGuard > dsoHandles;
} // namespace
// ---------------------------------------------------------------------------
namespace fedem::dso
{
std::string DSOLoader::filenamePrefix;
std::mutex DSOLoader::mutex_;
// =========================================================================
// Private helpers — caller must hold mutex_
// =========================================================================
// Returns the resolved path that was successfully loaded.
std::string DSOLoader::loadSingle( std::string const& soName )
{
void* dlib = dlopen( soName.c_str( ), RTLD_GLOBAL | RTLD_LAZY );
if( dlib == nullptr )
{
char const* err = dlerror( );
throw exception::FileNotFound( soName, err ? err : "" );
}
dsoHandles.emplace( dlib );
return soName;
}
void DSOLoader::scanDirectory( std::string const& ext,
std::string const& folder,
std::string& missingFiles )
{
namespace fs = std::filesystem;
fs::path dirPath = folder.empty( ) ? fs::current_path( ) : fs::path( folder );
std::error_code ec;
if( ! fs::is_directory( dirPath, ec ) ) return;
for( auto const& entry : fs::directory_iterator( dirPath, ec ) )
{
if( ec ) break;
if( ! entry.is_regular_file( ec ) && ! entry.is_symlink( ec ) ) continue;
std::string const fname = entry.path( ).filename( ).string( );
bool match = false;
if( filenamePrefix.empty( ) )
match = fname.size() >= ext.size()
&& fname.compare( fname.size()-ext.size(), ext.size(), ext ) == 0;
else
match = fname.size() >= (filenamePrefix.size()+ext.size())
&& fname.compare(0, filenamePrefix.size(), filenamePrefix) == 0
&& fname.compare(fname.size()-ext.size(), ext.size(), ext) == 0;
if( match )
{
try { loadSingle( entry.path().string() ); }
catch( exception::FileNotFound& ex )
{
if( !ex.missingFilename().empty() )
{
if( !missingFiles.empty() ) missingFiles += ", ";
missingFiles += ex.missingFilename() + "\n -- " + ex.whatStr();
}
}
}
}
}
// =========================================================================
// Public API — original (no signature check)
// =========================================================================
void DSOLoader::load( std::string const& soName )
{
std::lock_guard<std::mutex> lk( mutex_ );
loadSingle( soName );
}
void DSOLoader::load( std::string const& soName,
std::vector<std::string> const& list )
{
std::lock_guard<std::mutex> lk( mutex_ );
std::ostringstream errLog( std::ios_base::ate );
bool found = false;
for( auto const& dir : list )
{
namespace fs = std::filesystem;
fs::path soPath = dir.empty() ? fs::path(soName) : fs::path(dir) / soName;
try { loadSingle( soPath.string() ); found = true; break; }
catch( exception::FileNotFound& ex ) { errLog << ex.whatStr(); }
}
if( !found ) throw exception::FileNotFound( soName, errLog.str() );
}
void DSOLoader::loadAll( std::string const& ext, std::string const& folder )
{
std::lock_guard<std::mutex> lk( mutex_ );
std::string miss;
scanDirectory( ext, folder, miss );
if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
}
void DSOLoader::loadAll( std::string const& ext )
{
std::lock_guard<std::mutex> lk( mutex_ );
std::string miss;
scanDirectory( ext, "", miss );
if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
}
void DSOLoader::loadAll( std::string const& ext,
std::vector<std::string> const& list )
{
std::lock_guard<std::mutex> lk( mutex_ );
std::string miss;
for( auto const& dir : list ) scanDirectory( ext, dir, miss );
if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
}
void DSOLoader::loadAllByEnvironment( std::string const& ext,
std::string const& env )
{
std::lock_guard<std::mutex> lk( mutex_ );
char const* envVal = getenv( env.c_str() );
std::string miss;
if( envVal )
{
std::istringstream stream( envVal );
std::string token;
while( std::getline( stream, token, PATH_SEPARATOR ) )
scanDirectory( ext, token, miss );
}
else scanDirectory( ext, "", miss );
if( !miss.empty() ) throw exception::FileNotFound( miss, "DSO load error" );
}
void DSOLoader::loadAllByEnvironment( std::string const& ext )
{
loadAllByEnvironment( ext, LIBPATH );
}
void DSOLoader::prefix( std::string const& pre )
{
std::lock_guard<std::mutex> lk( mutex_ );
filenamePrefix = pre;
}
std::string DSOLoader::prefix( )
{
std::lock_guard<std::mutex> lk( mutex_ );
return filenamePrefix;
}
#if defined(DSOLD_WITH_SIGN)
// =========================================================================
// Signed loading API — compiled only when plugin-sign + OpenSSL available
// =========================================================================
SignedLoadResult DSOLoader::loadSigned(
std::string const& soName,
std::vector<std::string> const& searchDirs,
std::string const& trustedKeysDir )
{
SignedLoadResult result;
namespace fs = std::filesystem;
// Resolve path and load
std::string resolvedPath;
{
std::lock_guard<std::mutex> lk( mutex_ );
std::ostringstream errLog;
for( auto const& dir : searchDirs )
{
fs::path soPath = dir.empty() ? fs::path(soName) : fs::path(dir) / soName;
if( !fs::exists( soPath ) ) continue;
try
{
resolvedPath = loadSingle( soPath.string() );
result.loaded = true;
result.soPath = resolvedPath;
break;
}
catch( exception::FileNotFound& ex ) { errLog << ex.whatStr(); }
}
if( !result.loaded )
{
result.detail = errLog.str();
result.trust = sign::TrustLevel::UNSIGNED;
return result;
}
}
// Verify (outside mutex — may take time, uses its own cache)
sign::VerifyResult vr = sign::Verifier::verify( resolvedPath, trustedKeysDir );
result.trust = vr.trust;
result.detail = vr.detail;
return result;
}
void DSOLoader::loadVerified(
std::string const& soName,
std::vector<std::string> const& searchDirs,
std::string const& trustedKeysDir,
sign::TrustLevel minTrust )
{
SignedLoadResult r = loadSigned( soName, searchDirs, trustedKeysDir );
if( !r.loaded )
throw exception::FileNotFound( soName, r.detail );
// Check trust level meets minimum
// Ordering: TRUSTED > UNKNOWN > UNSIGNED > REJECTED
auto rank = []( sign::TrustLevel t ) -> int {
switch( t ) {
case sign::TrustLevel::TRUSTED: return 3;
case sign::TrustLevel::UNKNOWN: return 2;
case sign::TrustLevel::UNSIGNED: return 1;
case sign::TrustLevel::REJECTED: return 0;
}
return 0;
};
if( rank( r.trust ) < rank( minTrust ) )
throw exception::SignatureRejected( r.soPath, r.detail );
}
#endif // DSOLD_WITH_SIGN
} // namespace fedem::dso