use anyhow::Result;
use elf::{ElfBytes, endian::AnyEndian};
use std::fs;
use std::path::Path;
pub(crate) fn get_file_dependencies(path: &Path) -> Result<Vec<String>> {
let file_data = match fs::read(path) {
Ok(data) => data,
Err(_) => return Ok(Vec::new()),
};
let file = match ElfBytes::<AnyEndian>::minimal_parse(&file_data) {
Ok(elf) => elf,
Err(_) => return Ok(Vec::new()),
};
let mut dependencies = Vec::new();
if let Ok(Some(dynamic)) = file.dynamic() {
if let Ok(Some(strtab)) = file.section_header_by_name(".dynstr") {
if let Ok(string_table) = file.section_data(&strtab) {
for entry in dynamic.iter() {
if entry.d_tag == elf::abi::DT_NEEDED {
let offset = entry.d_val() as usize;
if let Some(null_pos) =
string_table.0[offset..].iter().position(|&b| b == 0)
{
if let Ok(lib_name) =
std::str::from_utf8(&string_table.0[offset..offset + null_pos])
{
dependencies.push(lib_name.to_string());
}
}
}
}
}
}
}
Ok(dependencies)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
use std::fs;
#[test]
fn test_get_file_dependencies_nonexistent_file() {
let result = get_file_dependencies(Path::new("/nonexistent/file/that/should/not/exist")).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_non_elf_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
fs::write(&file_path, "This is not an ELF file").unwrap();
let result = get_file_dependencies(&file_path).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_empty_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("empty");
fs::write(&file_path, "").unwrap();
let result = get_file_dependencies(&file_path).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_binary_garbage() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("garbage");
fs::write(&file_path, vec![0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xFF]).unwrap();
let result = get_file_dependencies(&file_path).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_invalid_elf_magic() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("fake_elf");
let mut fake_elf = vec![0x7f, 0x45, 0x4c, 0x46]; fake_elf.extend_from_slice(&[0x01, 0x01, 0x01, 0x00]); fake_elf.extend_from_slice(&vec![0x00; 100]); fs::write(&file_path, fake_elf).unwrap();
let result = get_file_dependencies(&file_path).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_permission_denied() {
let temp_dir = TempDir::new().unwrap();
let dir_path = temp_dir.path().join("directory");
fs::create_dir(&dir_path).unwrap();
let result = get_file_dependencies(&dir_path).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_get_file_dependencies_real_elf_binary() {
let hello_binary = Path::new("tests/fixtures/hello");
let result = get_file_dependencies(hello_binary).unwrap();
let expected = vec!["libc.so.6"];
assert_eq!(result, expected,
"Expected exact dependency match for hello binary. Got: {:?}, Expected: {:?}",
result, expected);
}
}