use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn read_elf_interp(path: &Path) -> Option<String> {
let mut file = std::fs::File::open(path).ok()?;
let mut buf = vec![0u8; 8192];
let n = file.read(&mut buf).ok()?;
buf.truncate(n);
parse_elf_interp(&buf)
}
fn parse_elf_interp(data: &[u8]) -> Option<String> {
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 => {
let e_phoff = u64::from_le_bytes(data[32..40].try_into().ok()?) as usize;
let e_phentsize = u16::from_le_bytes(data[54..56].try_into().ok()?) as usize;
let e_phnum = u16::from_le_bytes(data[56..58].try_into().ok()?) as usize;
(e_phoff, e_phentsize, e_phnum)
}
1 => {
let e_phoff = u32::from_le_bytes(data[28..32].try_into().ok()?) as usize;
let e_phentsize = u16::from_le_bytes(data[42..44].try_into().ok()?) as usize;
let e_phnum = u16::from_le_bytes(data[44..46].try_into().ok()?) as usize;
(e_phoff, e_phentsize, e_phnum)
}
_ => return None,
};
for i in 0..e_phnum {
let off = e_phoff + i * e_phentsize;
if off + e_phentsize > data.len() {
break;
}
let p_type = u32::from_le_bytes(data[off..off + 4].try_into().ok()?);
if p_type != 3 {
continue;
}
let (p_offset, p_filesz) = match class {
2 => {
let p_offset =
u64::from_le_bytes(data[off + 8..off + 16].try_into().ok()?) as usize;
let p_filesz =
u64::from_le_bytes(data[off + 32..off + 40].try_into().ok()?) as usize;
(p_offset, p_filesz)
}
1 => {
let p_offset = u32::from_le_bytes(data[off + 4..off + 8].try_into().ok()?) as usize;
let p_filesz =
u32::from_le_bytes(data[off + 16..off + 20].try_into().ok()?) as usize;
(p_offset, p_filesz)
}
_ => return None,
};
if p_offset + p_filesz > data.len() {
return None;
}
let interp = &data[p_offset..p_offset + p_filesz];
let interp = match interp.iter().position(|&b| b == 0) {
Some(pos) => &interp[..pos],
None => interp,
};
return std::str::from_utf8(interp).ok().map(String::from);
}
None
}
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;
}
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, argv0: &str, args: &[String]) -> ! {
crate::ulexec::exec_with_interp(target, interpreter, 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],
argv0: &str,
args: &[String],
) -> (Command, Option<PathBuf>) {
use std::os::unix::process::CommandExt;
if let Some(interp) = read_elf_interp(target) {
let interp_path = Path::new(&interp);
if interp_path.is_relative() {
let resolved = pkg_root.join(interp_path);
if resolved.exists() {
let mut cmd = Command::new(target);
cmd.arg0(argv0).args(args);
return (cmd, Some(pkg_root.to_path_buf()));
}
}
if let Some(bundled) = find_bundled_interp(&interp, pkg_root, lib_dirs) {
let lib_path = std::env::var("LD_LIBRARY_PATH").unwrap_or_default();
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, None);
}
if !interp_path.exists() {
eprintln!(
"onelf-rt: warning: ELF interpreter '{}' not found on this system \
and no bundled equivalent in the AppDir",
interp
);
}
}
let force_cwd = if has_bundled_loader(pkg_root, lib_dirs) {
Some(pkg_root.to_path_buf())
} else {
None
};
let mut cmd = Command::new(target);
cmd.arg0(argv0).args(args);
(cmd, force_cwd)
}
fn has_bundled_loader(pkg_root: &Path, lib_dirs: &[&str]) -> bool {
for dir in lib_dirs {
let d = pkg_root.join(dir);
let Ok(entries) = std::fs::read_dir(&d) else {
continue;
};
for entry in entries.filter_map(Result::ok) {
let name = entry.file_name();
let Some(name) = name.to_str() else {
continue;
};
if name.starts_with("ld-linux") || name.starts_with("ld-musl-") {
return true;
}
}
}
false
}
pub fn parse_bundled_interp_rel(interp_data: &[u8]) -> Option<&str> {
std::str::from_utf8(interp_data).ok()?.lines().next()
}