use super::{KeyResolver, ResolvedKey};
use crate::{
Error, IoError, LinkerError, ParseEhdrError, Result,
input::{ElfFile, ElfReader, Path, PathBuf},
linker::{DependencyRequest, RootRequest},
relocation::RelocationArch,
sync::Arc,
};
use alloc::{boxed::Box, vec::Vec};
use core::fmt;
fn expand_origin(value: &str, origin: &Path) -> PathBuf {
PathBuf::from(
value
.replace("${ORIGIN}", origin.as_str())
.replace("$ORIGIN", origin.as_str()),
)
}
pub type SearchDirProvider =
dyn for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static;
#[derive(Clone)]
pub enum SearchDirSource {
Fixed(PathBuf),
Dynamic(Arc<SearchDirProvider>),
}
impl SearchDirSource {
#[inline]
pub fn fixed(dir: impl Into<PathBuf>) -> Self {
Self::Fixed(dir.into())
}
#[inline]
pub fn dynamic<F>(resolver: F) -> Self
where
F: for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static,
{
Self::Dynamic(Arc::from(Box::new(resolver) as Box<SearchDirProvider>))
}
}
impl fmt::Debug for SearchDirSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fixed(dir) => f.debug_tuple("Fixed").field(dir).finish(),
Self::Dynamic(_) => f.write_str("Dynamic(..)"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum CandidateRequest<'a> {
Root {
requested: &'a Path,
},
Dependency {
requested: &'a Path,
owner_name: &'a str,
owner_path: &'a Path,
runpath: Option<&'a str>,
rpath: Option<&'a str>,
},
}
impl<'a> CandidateRequest<'a> {
#[inline]
pub const fn root(requested: &'a Path) -> Self {
Self::Root { requested }
}
#[inline]
pub const fn dependency(
requested: &'a Path,
owner_name: &'a str,
owner_path: &'a Path,
runpath: Option<&'a str>,
rpath: Option<&'a str>,
) -> Self {
Self::Dependency {
requested,
owner_name,
owner_path,
runpath,
rpath,
}
}
#[inline]
pub const fn requested(&self) -> &'a Path {
match self {
Self::Root { requested } | Self::Dependency { requested, .. } => requested,
}
}
#[inline]
pub const fn owner_name(&self) -> Option<&'a str> {
match self {
Self::Root { .. } => None,
Self::Dependency { owner_name, .. } => Some(owner_name),
}
}
#[inline]
pub const fn owner_path(&self) -> Option<&'a Path> {
match self {
Self::Root { .. } => None,
Self::Dependency { owner_path, .. } => Some(owner_path),
}
}
#[inline]
pub fn origin(&self) -> Option<&'a Path> {
match self {
Self::Root { .. } => None,
Self::Dependency { owner_path, .. } => Some(owner_path.parent()),
}
}
#[inline]
pub fn runpath(&self) -> Option<Vec<PathBuf>> {
match self {
Self::Root { .. } => None,
Self::Dependency { runpath, .. } => self.expand_dynamic_path_list(*runpath),
}
}
#[inline]
pub fn rpath(&self) -> Option<Vec<PathBuf>> {
match self {
Self::Root { .. } => None,
Self::Dependency { rpath, .. } => self.expand_dynamic_path_list(*rpath),
}
}
fn expand_dynamic_path_list(&self, path_list: Option<&str>) -> Option<Vec<PathBuf>> {
let Self::Dependency {
requested,
owner_path,
..
} = self
else {
return None;
};
if requested.has_dir_separator() {
return None;
}
let origin = owner_path.parent();
Some(
path_list?
.split(':')
.filter(|dir| !dir.is_empty())
.map(|dir| expand_origin(dir, origin))
.collect(),
)
}
}
pub struct SearchPathResolver {
search_dir_sources: Vec<SearchDirSource>,
}
impl Clone for SearchPathResolver {
fn clone(&self) -> Self {
Self {
search_dir_sources: self.search_dir_sources.clone(),
}
}
}
impl Default for SearchPathResolver {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for SearchPathResolver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SearchPathResolver")
.field("search_dir_sources", &self.search_dir_sources)
.finish()
}
}
impl SearchPathResolver {
#[inline]
pub fn new() -> Self {
Self {
search_dir_sources: Vec::new(),
}
}
pub fn push_search_dir_source(&mut self, source: SearchDirSource) -> &mut Self {
self.search_dir_sources.push(source);
self
}
pub fn push_fixed_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
self.push_search_dir_source(SearchDirSource::Fixed(dir.into()))
}
pub fn push_search_dir_provider<F>(&mut self, provider: F) -> &mut Self
where
F: for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static,
{
self.push_search_dir_source(SearchDirSource::Dynamic(Arc::from(
Box::new(provider) as Box<SearchDirProvider>
)))
}
#[inline]
pub fn search_dir_sources(&self) -> &[SearchDirSource] {
&self.search_dir_sources
}
fn resolve_key(&self, request: CandidateRequest<'_>) -> Result<Option<(PathBuf, ElfFile)>> {
let try_candidate = |candidate: PathBuf| -> Result<Option<(PathBuf, ElfFile)>> {
let Some(file) = Self::open_elf(&candidate)? else {
return Ok(None);
};
let key = match request {
CandidateRequest::Root { requested } => PathBuf::from(requested),
CandidateRequest::Dependency { .. } => candidate,
};
Ok(Some((key, file)))
};
let requested = request.requested();
let has_dir_separator = requested.has_dir_separator();
if matches!(request, CandidateRequest::Root { .. }) || has_dir_separator {
if let Some(resolved) = try_candidate(PathBuf::from(requested))? {
return Ok(Some(resolved));
}
}
if has_dir_separator {
return Ok(None);
}
let mut dynamic_dirs = Vec::new();
for source in &self.search_dir_sources {
match source {
SearchDirSource::Fixed(dir) => {
if let Some(resolved) = try_candidate(dir.join(requested.as_str()))? {
return Ok(Some(resolved));
}
}
SearchDirSource::Dynamic(resolver) => {
dynamic_dirs.clear();
resolver(request, &mut dynamic_dirs)?;
for dir in &dynamic_dirs {
if let Some(resolved) = try_candidate(dir.join(requested.as_str()))? {
return Ok(Some(resolved));
}
}
}
}
}
Ok(None)
}
#[inline]
fn resolved_key<'cfg, K, Arch: RelocationArch>(
key: K,
file: ElfFile,
is_visible: bool,
) -> ResolvedKey<'cfg, K, Arch> {
if is_visible {
ResolvedKey::existing(key)
} else {
ResolvedKey::load(key, file)
}
}
fn open_elf(path: &Path) -> Result<Option<ElfFile>> {
let mut file = match ElfFile::from_path(path) {
Ok(file) => file,
Err(Error::Io(IoError::OpenFailed { .. })) => return Ok(None),
Err(err) => return Err(err),
};
let mut magic = [0; 4];
file.read(&mut magic, 0)?;
if magic == *b"\x7fELF" {
Ok(Some(file))
} else {
Err(ParseEhdrError::InvalidMagic.into())
}
}
}
impl<'cfg, K, Arch> KeyResolver<'cfg, K, Arch> for SearchPathResolver
where
K: Clone + AsRef<Path> + From<PathBuf>,
Arch: RelocationArch,
{
fn load_root(&mut self, req: &RootRequest<'_, K>) -> Result<ResolvedKey<'cfg, K, Arch>> {
if let Some((_, file)) = self.resolve_key(CandidateRequest::root(req.key().as_ref()))? {
let key = req.key().clone();
let is_visible = req.is_visible(&key);
return Ok(Self::resolved_key(key, file, is_visible));
}
Err(LinkerError::resolver("root module was not found by SearchPathResolver").into())
}
fn resolve_dependency(
&mut self,
req: &DependencyRequest<'_, K>,
) -> Result<ResolvedKey<'cfg, K, Arch>> {
let origin = req.owner_path().parent();
let needed = expand_origin(req.needed(), origin);
let request = CandidateRequest::dependency(
needed.as_path(),
req.owner_name(),
req.owner_path(),
req.runpath(),
req.rpath(),
);
if let Some((key, file)) = self.resolve_key(request)? {
let key = K::from(key);
let is_visible = req.is_visible(&key);
return Ok(Self::resolved_key(key, file, is_visible));
}
Err(req.unresolved())
}
}