use std::{fs, path::Path, sync::OnceLock};
#[cfg(any(feature = "libc", feature = "use-syscall"))]
use elf_loader::linker::SearchPathResolver;
#[cfg(any(feature = "libc", feature = "use-syscall"))]
use std::path::PathBuf;
use crate::fixture_build::FixtureBuild;
pub(crate) struct Fixtures {
pub(crate) provider: Vec<u8>,
pub(crate) dependent: Vec<u8>,
pub(crate) global: Vec<u8>,
pub(crate) plain: Vec<u8>,
#[cfg(any(
feature = "use-syscall",
all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
pub(crate) exec: Vec<u8>,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) provider_path: PathBuf,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) root_path: PathBuf,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) rpath_root_path: PathBuf,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) rpath_caller_path: PathBuf,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) rpath_middle_path: PathBuf,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) rpath_leaf_path: PathBuf,
}
pub(crate) fn fixtures() -> &'static Fixtures {
static FIXTURES: OnceLock<Fixtures> = OnceLock::new();
FIXTURES.get_or_init(build)
}
#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) fn search_path_resolver() -> SearchPathResolver {
let mut resolver = SearchPathResolver::new();
resolver.push_rpath();
resolver.push_runpath();
resolver
}
fn build() -> Fixtures {
let build = FixtureBuild::rust("tests/linker/fixtures", "linker");
let dylib = |name: &str, dependency: Option<(&str, &Path)>| {
let dependencies = dependency.iter().map(|(_, path)| *path).collect::<Vec<_>>();
build.rust_cdylib(format!("{name}.rs"), name, &dependencies, |command| {
command
.arg("-C")
.arg("link-arg=--emit-relocs")
.arg("-C")
.arg("link-arg=-rpath")
.arg("-C")
.arg("link-arg=$ORIGIN");
if let Some((dependency, _)) = dependency {
command
.arg("-L")
.arg(format!("native={}", build.output("").display()))
.arg("-l")
.arg(format!("dylib={dependency}"));
}
})
};
let provider_path = dylib("provider", None);
let dependent_path = dylib("dependent", Some(("provider", &provider_path)));
let global_path = dylib("global", None);
#[cfg(any(feature = "libc", feature = "use-syscall"))]
let root_path = dylib("root", Some(("dependent", &dependent_path)));
#[cfg(any(feature = "libc", feature = "use-syscall"))]
let (rpath_root_path, rpath_caller_path, rpath_middle_path, rpath_leaf_path) = {
let build = FixtureBuild::c("tests/linker/fixtures", "linker");
let dir = build.output("rpath");
fs::create_dir_all(&dir).expect("failed to create RPATH fixture directory");
let leaf = build.c_shared("rpath_leaf.c", "rpath/libleaf.so", &[], |command| {
command.arg("-Wl,-soname,libleaf.so");
});
let middle = build.c_shared(
"rpath_middle.c",
"rpath/libmiddle.so",
&[leaf.as_path()],
|command| {
command
.arg("-Wl,-soname,libmiddle.so")
.arg(format!("-L{}", dir.display()))
.arg("-lleaf");
},
);
let root = build.c_shared(
"rpath_root.c",
"librpath_root.so",
&[middle.as_path()],
|command| {
command
.arg(format!("-L{}", dir.display()))
.arg("-lmiddle")
.arg("-Wl,--disable-new-dtags,-rpath,$ORIGIN/rpath");
},
);
let caller = build.c_shared("rpath_caller.c", "librpath_caller.so", &[], |command| {
command.arg("-Wl,--disable-new-dtags,-rpath,$ORIGIN/rpath");
});
(root, caller, middle, leaf)
};
let plain = build.rust_cdylib("plain.rs", "plain", &[], |_| {});
#[cfg(any(
feature = "use-syscall",
all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
let exec = build.compile(
"exec.rs",
"dynamic_exec",
&[provider_path.as_path()],
|command| {
command
.arg("--crate-type=bin")
.arg("--crate-name")
.arg("dynamic_exec")
.arg("-O")
.arg("-C")
.arg("panic=abort")
.arg("-C")
.arg("linker=rust-lld")
.arg("-C")
.arg("link-arg=-no-pie")
.arg("-C")
.arg("link-arg=-e")
.arg("-C")
.arg("link-arg=_start")
.arg("-C")
.arg("link-arg=-rpath")
.arg("-C")
.arg("link-arg=$ORIGIN")
.arg("-L")
.arg(format!("native={}", build.output("").display()))
.arg("-l")
.arg("dylib=provider");
},
);
Fixtures {
provider: fs::read(&provider_path).expect("failed to read linker provider fixture"),
dependent: fs::read(dependent_path).expect("failed to read linker dependent fixture"),
global: fs::read(global_path).expect("failed to read linker global fixture"),
plain: fs::read(plain).expect("failed to read linker plain fixture"),
#[cfg(any(
feature = "use-syscall",
all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
exec: fs::read(exec).expect("failed to read linker executable fixture"),
#[cfg(any(feature = "libc", feature = "use-syscall"))]
provider_path,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
root_path,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
rpath_root_path,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
rpath_caller_path,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
rpath_middle_path,
#[cfg(any(feature = "libc", feature = "use-syscall"))]
rpath_leaf_path,
}
}