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 }
fn find_bundled_interp(interp: &str, pkg_root: &Path, lib_dirs: &[&str]) -> Option<PathBuf> {
let interp_path = Path::new(interp);
let interp_name = std::fs::read_link(interp_path)
.ok()
.and_then(|target| target.file_name().map(|n| n.to_os_string()))
.or_else(|| interp_path.file_name().map(|n| n.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 setup_interp_symlink(interp_data: &[u8], pkg_root: &Path) {
let text = match std::str::from_utf8(interp_data) {
Ok(t) => t,
Err(_) => return,
};
let mut lines = text.lines();
let original = match lines.next() {
Some(s) if !s.is_empty() => s,
_ => return,
};
let symlink_str = match lines.next() {
Some(s) if !s.is_empty() => s,
_ => return,
};
let bundled_rel = match lines.next() {
Some(s) if !s.is_empty() => s,
_ => return,
};
let symlink_path = Path::new(symlink_str);
let target = if Path::new(original).exists() {
PathBuf::from(original)
} else {
pkg_root.join(bundled_rel)
};
if let Ok(existing) = std::fs::read_link(symlink_path) {
if existing == target {
return;
}
let _ = std::fs::remove_file(symlink_path);
}
let _ = std::os::unix::fs::symlink(&target, symlink_path);
}
pub fn build_exec_command(
target: &Path,
pkg_root: &Path,
lib_dirs: &[&str],
argv0: &str,
args: &[String],
) -> Command {
use std::os::unix::process::CommandExt;
if let Some(interp) = read_elf_interp(target) {
if !Path::new(&interp).exists() {
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 mut cmd = Command::new(&bundled);
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;
}
eprintln!(
"onelf-rt: warning: ELF interpreter '{}' not found on this system",
interp
);
eprintln!(
"onelf-rt: hint: bundle the interpreter with: onelf bundle-libs --exclude ''"
);
}
}
let mut cmd = Command::new(target);
cmd.arg0(argv0).args(args);
cmd
}