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) = 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(crate) fn pt_interp_slot(data: &[u8]) -> Option<(usize, usize)> {
if data.len() < 64 || data[0..4] != *b"\x7fELF" {
return None;
}
let class = data[4];
let (e_phoff, e_phentsize, e_phnum) = match class {
2 => (
u64::from_le_bytes(data.get(32..40)?.try_into().ok()?) as usize,
u16::from_le_bytes(data.get(54..56)?.try_into().ok()?) as usize,
u16::from_le_bytes(data.get(56..58)?.try_into().ok()?) as usize,
),
1 => (
u32::from_le_bytes(data.get(28..32)?.try_into().ok()?) as usize,
u16::from_le_bytes(data.get(42..44)?.try_into().ok()?) as usize,
u16::from_le_bytes(data.get(44..46)?.try_into().ok()?) as usize,
),
_ => return None,
};
let min_phentsize = if class == 2 { 56 } else { 32 };
if e_phentsize < min_phentsize {
return None;
}
for i in 0..e_phnum {
let off = e_phoff.checked_add(i.checked_mul(e_phentsize)?)?;
let end = off.checked_add(e_phentsize)?;
if end > data.len() {
break;
}
let p_type = u32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?);
if p_type != 3 {
continue;
}
return match class {
2 => Some((
u64::from_le_bytes(data.get(off + 8..off + 16)?.try_into().ok()?) as usize,
u64::from_le_bytes(data.get(off + 32..off + 40)?.try_into().ok()?) as usize,
)),
1 => Some((
u32::from_le_bytes(data.get(off + 4..off + 8)?.try_into().ok()?) as usize,
u32::from_le_bytes(data.get(off + 16..off + 20)?.try_into().ok()?) as usize,
)),
_ => None,
};
}
None
}
#[cfg(test)]
fn parse_elf_interp(data: &[u8]) -> Option<String> {
let (p_offset, p_filesz) = pt_interp_slot(data)?;
let interp = data.get(p_offset..p_offset.checked_add(p_filesz)?)?;
interp_from_bytes(interp)
}
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;
}
if read_elf_interp(target).is_none() {
return None;
}
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()
}
#[cfg(test)]
mod interp_tests {
use super::*;
fn elf64_with_interp(interp: &str) -> Vec<u8> {
let phoff = 64usize;
let phentsize = 56usize;
let interp_off = phoff + phentsize;
let mut v = vec![0u8; interp_off + interp.len() + 1];
v[0..4].copy_from_slice(b"\x7fELF");
v[4] = 2; v[32..40].copy_from_slice(&(phoff as u64).to_le_bytes());
v[54..56].copy_from_slice(&(phentsize as u16).to_le_bytes());
v[56..58].copy_from_slice(&1u16.to_le_bytes()); v[phoff..phoff + 4].copy_from_slice(&3u32.to_le_bytes()); v[phoff + 8..phoff + 16].copy_from_slice(&(interp_off as u64).to_le_bytes());
v[phoff + 32..phoff + 40].copy_from_slice(&((interp.len() + 1) as u64).to_le_bytes());
v[interp_off..interp_off + interp.len()].copy_from_slice(interp.as_bytes());
v
}
#[test]
fn slot_and_parse_agree() {
let name = "/lib64/ld-linux-x86-64.so.2";
let elf = elf64_with_interp(name);
let (off, sz) = pt_interp_slot(&elf).expect("slot");
assert_eq!(off, 120);
assert_eq!(sz, name.len() + 1);
assert_eq!(parse_elf_interp(&elf).as_deref(), Some(name));
}
#[test]
fn malformed_returns_none_without_panic() {
assert!(pt_interp_slot(b"not an elf").is_none());
assert!(pt_interp_slot(&[]).is_none());
let mut short = vec![0u8; 64];
short[0..4].copy_from_slice(b"\x7fELF");
short[4] = 2;
short[56..58].copy_from_slice(&9999u16.to_le_bytes());
short[32..40].copy_from_slice(&(1u64 << 40).to_le_bytes());
assert!(pt_interp_slot(&short).is_none());
}
}