use crate::{
elf::{Architecture, ElfClass},
paths::{logical_parent, normalize_absolute},
source::SourceRoot,
};
use std::path::{Path, PathBuf};
const CONF_DEPTH_MAX: usize = 8;
const CONF_FILES_MAX: usize = 256;
const CONF_DIRECTORIES_MAX: usize = 128;
const CONF_PENDING_MAX: usize = 4096;
const CONF_BYTES_MAX: usize = 1024 * 1024;
pub fn default_library_paths(architecture: &Architecture) -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Some(tuple) = architecture.machine.debian_multiarch() {
paths.push(PathBuf::from(format!("/lib/{tuple}")));
paths.push(PathBuf::from(format!("/usr/lib/{tuple}")));
}
if architecture.class == ElfClass::Elf64 {
paths.push(PathBuf::from("/lib64"));
paths.push(PathBuf::from("/usr/lib64"));
}
paths.push(PathBuf::from("/lib"));
paths.push(PathBuf::from("/usr/lib"));
paths
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Directive {
Directory(PathBuf),
Include(PathBuf),
}
pub fn parse_ld_so_conf(root: &SourceRoot) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = Vec::new();
let mut visited: Vec<PathBuf> = Vec::new();
let mut pending = vec![(Directive::Include(PathBuf::from("/etc/ld.so.conf")), 0usize)];
while let Some((directive, depth)) = pending.pop() {
let file = match directive {
Directive::Directory(dir) => {
if !paths.contains(&dir) && paths.len() < CONF_DIRECTORIES_MAX {
paths.push(dir);
}
continue;
}
Directive::Include(file) => file,
};
if depth > CONF_DEPTH_MAX || visited.contains(&file) || visited.len() == CONF_FILES_MAX {
continue;
}
visited.push(file.clone());
for directive in read_conf(root, &file).into_iter().rev() {
if pending.len() >= CONF_PENDING_MAX {
break;
}
pending.push((directive, depth + 1));
}
}
paths
}
fn read_conf(root: &SourceRoot, logical: &Path) -> Vec<Directive> {
assert!(logical.is_absolute());
let Ok(Some(bytes)) = root.read_bounded(logical, CONF_BYTES_MAX) else {
return Vec::new();
};
let Ok(text) = String::from_utf8(bytes) else {
return Vec::new();
};
let mut directives = Vec::new();
for line in text.lines() {
let line = line.split('#').next().unwrap_or("").trim();
if line.is_empty() {
continue;
}
if let Some(rest) = line
.strip_prefix("include ")
.or_else(|| line.strip_prefix("include\t"))
{
for pattern in rest.split_whitespace() {
let included = expand_include(root, logical, pattern);
directives.extend(included.into_iter().map(Directive::Include));
}
continue;
}
if line.starts_with("hwcap ") {
continue;
}
directives.push(Directive::Directory(normalize_absolute(Path::new(line))));
}
directives
}
fn expand_include(root: &SourceRoot, current: &Path, pattern: &str) -> Vec<PathBuf> {
assert!(current.is_absolute());
let pattern_path = if Path::new(pattern).is_absolute() {
PathBuf::from(pattern)
} else {
logical_parent(current).join(pattern)
};
let pattern_path = normalize_absolute(&pattern_path);
let Some(name) = pattern_path.file_name().and_then(|n| n.to_str()) else {
return Vec::new();
};
if !name.contains('*') {
return vec![pattern_path];
}
let dir = logical_parent(&pattern_path);
let Ok(entries) = root.read_dir(&dir) else {
return Vec::new();
};
let (prefix, suffix) = name.split_once('*').unwrap_or((name, ""));
entries
.into_iter()
.take(CONF_PENDING_MAX)
.filter_map(|entry| {
let entry = entry.to_str()?.to_string();
let fits = entry.len() >= prefix.len() + suffix.len();
let matches = entry.starts_with(prefix) && entry.ends_with(suffix);
(fits && matches).then(|| dir.join(entry))
})
.collect()
}