/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* Global Trust Authority is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
*/
use std::env;
use std::path::{Path, PathBuf};
/// Standard Linux system library directories
/// These paths are searched in order when a plugin is not found at the configured path
const SYSTEM_LIBRARY_PATHS: &[&str] = &["/usr/lib64", "/usr/lib", "/lib64", "/lib"];
/// Finds a plugin library file using a simplified library search order.
///
/// This function implements a simplified version of Linux library search order:
/// 1. Configured path (if it exists and is a file)
/// 2. `LD_LIBRARY_PATH` environment variable paths
/// 3. Current directory (for convenience, not part of standard dlopen)
/// 4. System library directories (`/usr/lib64`, `/usr/lib`, `/lib64`, `/lib`)
///
/// # Note
///
/// This is a simplified implementation. Standard Linux `dlopen()` also searches:
/// - RPATH/RUNPATH from the calling executable/library
/// - `/etc/ld.so.cache` (system library cache)
/// - Standard paths in order: `/lib`, `/usr/lib`
///
/// For plugin loading, this simplified order is usually sufficient.
///
/// # Arguments
///
/// * `configured_path` - The plugin path specified in configuration
///
/// # Returns
///
/// * `Ok(String)` - The absolute path to the found plugin library
/// * `Err(String)` - Error message if the plugin is not found in any search path
///
/// # Examples
///
/// ```rust,no_run
/// use plugin_manager::find_plugin_path;
///
/// let path = find_plugin_path("/usr/lib64/libtpm_boot_attester.so")?;
/// ```
pub fn find_plugin_path(configured_path: &str) -> Result<String, String> {
let path = Path::new(configured_path);
// First, check if the configured path exists and is a file
if path.exists() {
// Verify it's a file, not a directory
if path.is_file() {
return Ok(configured_path.to_string());
} else {
return Err(format!("Configured plugin path is a directory, not a file: {}", configured_path));
}
}
// Extract filename from configured path
let filename = path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| format!("Invalid plugin path (no filename): {}", path.display()))?;
// Build search paths following Linux standard library search order
let search_paths = build_search_paths()?;
// Try to find plugin in search paths
for search_path in &search_paths {
let candidate_path = search_path.join(filename);
// Verify it exists and is a file (not a directory)
if candidate_path.exists() && candidate_path.is_file() {
return Ok(candidate_path.to_string_lossy().to_string());
}
}
// Plugin not found in any path
let checked_paths: Vec<String> = search_paths.iter().map(|p| p.join(filename).display().to_string()).collect();
Err(format!("Plugin file not found: {} (checked paths: {:?})", path.display(), checked_paths))
}
/// Builds the list of search paths for plugin discovery.
///
/// The order is:
/// 1. LD_LIBRARY_PATH environment variable paths (highest priority)
/// 2. Current directory (for convenience)
/// 3. System library directories (`/usr/lib64`, `/usr/lib`, `/lib64`, `/lib`)
///
/// # Note
///
/// This is a simplified search order. Standard Linux `dlopen()` also uses:
/// - RPATH/RUNPATH from the calling executable
/// - `/etc/ld.so.cache` (system library cache)
/// - Standard order: `/lib`, `/usr/lib`
///
/// # Returns
///
/// * `Ok(Vec<PathBuf>)` - List of search paths in priority order
/// * `Err(String)` - Error message if current directory cannot be determined
pub(crate) fn build_search_paths() -> Result<Vec<PathBuf>, String> {
let mut search_paths = Vec::new();
// Add paths from LD_LIBRARY_PATH environment variable (highest priority)
if let Ok(ld_library_path) = env::var("LD_LIBRARY_PATH") {
for path in ld_library_path.split(':') {
if !path.is_empty() {
search_paths.push(PathBuf::from(path));
}
}
}
// Add current directory
search_paths.push(env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?);
// Add system library directories (standard Linux paths)
for path in SYSTEM_LIBRARY_PATHS {
search_paths.push(PathBuf::from(*path));
}
Ok(search_paths)
}