use std::{
borrow::Cow,
ffi::OsStr,
path::{Path, PathBuf},
};
use shared_buffer::OwnedBuffer;
use tracing::trace;
use virtual_fs::{AsyncReadExt, FileSystem, FsError};
use crate::{WasiFs, fs::WasiFsRoot};
use super::{LinkError, LocateModuleError};
const DEFAULT_RUNTIME_PATH: [&str; 3] = ["/lib", "/usr/lib", "/usr/local/lib"];
pub(super) async fn locate_module(
module_path: &Path,
library_path: &[impl AsRef<Path>],
runtime_path: &[impl AsRef<str>],
calling_module_path: Option<impl AsRef<Path>>,
fs: &WasiFs,
) -> Result<(PathBuf, OwnedBuffer), LinkError> {
async fn try_load(
fs: &WasiFsRoot,
path: impl AsRef<Path>,
) -> Result<(PathBuf, OwnedBuffer), FsError> {
let mut file = match fs.new_open_options().read(true).open(path.as_ref()) {
Ok(f) => f,
Err(_) if path.as_ref().extension() == Some(OsStr::new("so")) => fs
.new_open_options()
.read(true)
.open(path.as_ref().with_extension("wasm"))?,
Err(e) => return Err(e),
};
let buf = if let Some(buf) = file.as_owned_buffer() {
buf
} else {
let mut buf = Vec::new();
file.read_to_end(&mut buf).await?;
OwnedBuffer::from(buf)
};
Ok((path.as_ref().to_owned(), buf))
}
if module_path.is_absolute() {
trace!(?module_path, "Locating module with absolute path");
try_load(&fs.root_fs, module_path).await.map_err(|e| {
LinkError::SharedLibraryMissing(
module_path.to_string_lossy().into_owned(),
LocateModuleError::Single(e),
)
})
} else if module_path.components().count() > 1 {
trace!(?module_path, "Locating module with relative path");
try_load(
&fs.root_fs,
fs.relative_path_to_absolute(module_path.to_string_lossy().into_owned()),
)
.await
.map_err(|e| {
LinkError::SharedLibraryMissing(
module_path.to_string_lossy().into_owned(),
LocateModuleError::Single(e),
)
})
} else {
trace!(
?module_path,
"Locating module by name in default runtime path"
);
let calling_module_dir = calling_module_path
.as_ref()
.map(|p| p.as_ref().parent().unwrap_or_else(|| p.as_ref()));
let runtime_path = runtime_path.iter().map(|path| {
let path = path.as_ref();
let relative = path
.strip_prefix("$ORIGIN")
.or_else(|| path.strip_prefix("${ORIGIN}"));
match relative {
Some(relative) => {
let Some(calling_module_dir) = calling_module_dir else {
panic!(
"Internal error: $ORIGIN or ${{ORIGIN}} in RUNPATH, but \
no calling module path provided"
);
};
Cow::Owned(PathBuf::from(
fs.relative_path_to_absolute(
calling_module_dir
.join(relative)
.to_string_lossy()
.into_owned(),
),
))
}
None => Cow::Borrowed(Path::new(path)),
}
});
let search_paths = library_path
.iter()
.map(|path| Cow::Borrowed(path.as_ref()))
.chain(runtime_path)
.chain(
DEFAULT_RUNTIME_PATH
.iter()
.map(|path| Cow::Borrowed(Path::new(path))),
);
let mut errors: Vec<(PathBuf, FsError)> = Vec::new();
for path in search_paths {
let full_path = path.join(module_path);
trace!(search_path = ?path, full_path = ?full_path, "Searching module");
match try_load(&fs.root_fs, &full_path).await {
Ok(ret) => {
trace!(?module_path, full_path = ?ret.0, "Located module");
return Ok(ret);
}
Err(e) => errors.push((full_path, e)),
};
}
trace!(?module_path, "Failed to locate module");
Err(LinkError::SharedLibraryMissing(
module_path.to_string_lossy().into_owned(),
LocateModuleError::Multiple(errors),
))
}
}