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)
}
pub fn rewrite_interp_absolute(path: &Path, pkg_root: &Path) -> std::io::Result<bool> {
let data = match std::fs::read(path) {
Ok(d) => d,
Err(_) => return Ok(false),
};
if data.len() < 64 || data[0..4] != *b"\x7fELF" {
return Ok(false);
}
let class = data[4];
if class != 2 {
return Ok(false);
}
let e_phoff = u64::from_le_bytes(data[32..40].try_into().unwrap()) as usize;
let e_phentsize = u16::from_le_bytes(data[54..56].try_into().unwrap()) as usize;
let e_phnum = u16::from_le_bytes(data[56..58].try_into().unwrap()) as usize;
for i in 0..e_phnum {
let phdr_off = e_phoff + i * e_phentsize;
if phdr_off + e_phentsize > data.len() {
return Ok(false);
}
let p_type = u32::from_le_bytes(data[phdr_off..phdr_off + 4].try_into().unwrap());
if p_type != 3 {
continue; }
let p_offset = u64::from_le_bytes(data[phdr_off + 8..phdr_off + 16].try_into().unwrap())
as usize;
let p_filesz = u64::from_le_bytes(data[phdr_off + 32..phdr_off + 40].try_into().unwrap())
as usize;
if p_offset + p_filesz > data.len() {
return Ok(false);
}
let cur = &data[p_offset..p_offset + p_filesz];
let cur = match cur.iter().position(|&b| b == 0) {
Some(pos) => &cur[..pos],
None => cur,
};
if cur.is_empty() || cur[0] == b'/' {
return Ok(false);
}
let mut absolute = pkg_root.as_os_str().as_encoded_bytes().to_vec();
if !absolute.ends_with(b"/") {
absolute.push(b'/');
}
absolute.extend_from_slice(cur);
let mut modified = data.clone();
let new_len = absolute.len() + 1; if new_len <= p_filesz {
modified[p_offset..p_offset + absolute.len()].copy_from_slice(&absolute);
for b in &mut modified[p_offset + absolute.len()..p_offset + p_filesz] {
*b = 0;
}
modified[phdr_off + 32..phdr_off + 40]
.copy_from_slice(&(new_len as u64).to_le_bytes());
modified[phdr_off + 40..phdr_off + 48]
.copy_from_slice(&(new_len as u64).to_le_bytes());
} else {
let new_offset = modified.len() as u64;
modified.extend_from_slice(&absolute);
modified.push(0);
modified[phdr_off + 8..phdr_off + 16].copy_from_slice(&new_offset.to_le_bytes());
modified[phdr_off + 32..phdr_off + 40]
.copy_from_slice(&(new_len as u64).to_le_bytes());
modified[phdr_off + 40..phdr_off + 48]
.copy_from_slice(&(new_len as u64).to_le_bytes());
}
let mode = std::fs::metadata(path)
.map(|m| std::os::unix::fs::PermissionsExt::mode(&m.permissions()))
.unwrap_or(0o755);
let _ = std::fs::remove_file(path);
std::fs::write(path, &modified)?;
let _ = std::fs::set_permissions(
path,
<std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(mode),
);
return Ok(true);
}
Ok(false)
}
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()
}