use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn read_elf_interp(path: &Path) -> Option<String> {
const MAX_INTERP: usize = 4096;
let mut file = std::fs::File::open(path).ok()?;
let file_len = file.metadata().ok()?.len();
let mut head = vec![0u8; 8192];
let n = file.read(&mut head).ok()?;
head.truncate(n);
let (p_offset, p_filesz) = onelf_format::elf::pt_interp_slot(&head)?;
if p_filesz == 0 || p_filesz > MAX_INTERP {
return None;
}
let end = p_offset.checked_add(p_filesz)?;
if end as u64 > file_len {
return None;
}
if let Some(s) = head.get(p_offset..end) {
return interp_from_bytes(s);
}
file.seek(SeekFrom::Start(p_offset as u64)).ok()?;
let mut buf = vec![0u8; p_filesz];
file.read_exact(&mut buf).ok()?;
interp_from_bytes(&buf)
}
fn interp_from_bytes(bytes: &[u8]) -> Option<String> {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
std::str::from_utf8(&bytes[..end]).ok().map(String::from)
}
pub fn should_use_userland_exec(
target: &Path,
pkg_root: &Path,
bundled_interp_rel: Option<&str>,
) -> Option<PathBuf> {
if !crate::ulexec::is_supported() {
return None;
}
let rel_path = bundled_interp_rel?;
let interp = pkg_root.join(rel_path);
if !interp.exists() {
return None;
}
read_elf_interp(target)?;
if !is_pie(target) {
return None;
}
if crate::selfextract::has_self_extract_trailer(target) {
return None;
}
Some(interp)
}
fn is_pie(path: &Path) -> bool {
use std::io::Read;
let Ok(mut f) = std::fs::File::open(path) else {
return false;
};
let mut buf = [0u8; 20];
if f.read(&mut buf).unwrap_or(0) < 20 {
return false;
}
if buf[0..4] != *b"\x7fELF" {
return false;
}
let e_type = u16::from_le_bytes([buf[16], buf[17]]);
e_type == 3
}
pub fn exec_userland(
target: &Path,
interpreter: &Path,
lib_path: &str,
argv0: &str,
args: &[String],
) -> ! {
crate::ulexec::exec_with_interp(target, interpreter, lib_path, argv0, args)
}
fn find_bundled_interp(interp: &str, pkg_root: &Path, lib_dirs: &[&str]) -> Option<PathBuf> {
let interp_name = Path::new(interp).file_name()?.to_os_string();
for dir in lib_dirs {
let candidate = pkg_root.join(dir).join(&interp_name);
if candidate.exists() {
return Some(candidate);
}
}
let candidate = pkg_root.join(&interp_name);
if candidate.exists() {
return Some(candidate);
}
None
}
pub fn build_exec_command(
target: &Path,
pkg_root: &Path,
lib_dirs: &[&str],
lib_path: &str,
private_ns: bool,
argv0: &str,
args: &[String],
) -> Command {
use std::os::unix::process::CommandExt;
if let Some(interp) = read_elf_interp(target) {
let interp_path = Path::new(&interp);
if let Some(bundled) = find_bundled_interp(&interp, pkg_root, lib_dirs) {
if crate::selfextract::has_self_extract_trailer(target) {
let prepped = if private_ns {
crate::selfextract::bind_mount_interp(target, &bundled).map(|_| ())
} else {
crate::selfextract::symlink_interp(target, &bundled).map(|_| ())
};
match prepped {
Ok(()) => {
let mut cmd = Command::new(target);
cmd.arg0(argv0).args(args);
if !lib_path.is_empty() {
cmd.env("LD_LIBRARY_PATH", lib_path);
}
return cmd;
}
Err(e) => {
eprintln!(
"onelf-rt: warning: self-extract prep failed for {}: {e}; \
falling back to explicit linker invocation",
target.display()
);
}
}
}
let is_musl = interp_path
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with("ld-musl-"));
let mut cmd = Command::new(&bundled);
if !is_musl {
cmd.arg("--inhibit-cache");
}
if !lib_path.is_empty() {
cmd.arg("--library-path").arg(lib_path);
}
cmd.arg("--argv0").arg(argv0).arg(target).args(args);
return cmd;
}
if !interp_path.exists() {
eprintln!(
"onelf-rt: warning: ELF interpreter '{}' not found on this system \
and no bundled equivalent in the AppDir",
interp
);
}
}
let mut cmd = Command::new(target);
cmd.arg0(argv0).args(args);
let host_only: Vec<&str> = lib_path
.split(':')
.filter(|dir| !dir.is_empty() && !Path::new(dir).starts_with(pkg_root))
.collect();
if !host_only.is_empty() {
cmd.env("LD_LIBRARY_PATH", host_only.join(":"));
}
cmd
}
pub fn parse_bundled_interp_rel(interp_data: &[u8]) -> Option<&str> {
std::str::from_utf8(interp_data).ok()?.lines().next()
}