use std::fs;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
pub fn has_self_extract_trailer(path: &Path) -> bool {
const TRAILER: &[u8] = b"\n---- Bun! ----\n";
let Ok(mut file) = fs::File::open(path) else {
return false;
};
let Ok(meta) = file.metadata() else {
return false;
};
if meta.len() < 24 {
return false;
}
let mut buf = [0u8; 24];
if file.seek(SeekFrom::End(-24)).is_err() {
return false;
}
if file.read_exact(&mut buf).is_err() {
return false;
}
if &buf[8..24] == TRAILER {
return true;
}
if &buf[0..16] == TRAILER {
return true;
}
false
}
fn read_pt_interp(binary: &Path) -> io::Result<String> {
let mut data = vec![0u8; 8192];
let mut file = fs::File::open(binary)?;
let n = file.read(&mut data)?;
data.truncate(n);
let (p_offset, p_filesz) = crate::interp::pt_interp_slot(&data)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no PT_INTERP entry"))?;
if p_filesz > 4096 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PT_INTERP length exceeds PATH_MAX",
));
}
let slot_end = p_offset
.checked_add(p_filesz)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PT_INTERP slot overflow"))?;
let buf = if slot_end > data.len() {
file.seek(SeekFrom::Start(p_offset as u64))?;
let mut b = vec![0u8; p_filesz];
file.read_exact(&mut b)?;
b
} else {
data[p_offset..slot_end].to_vec()
};
let s = std::str::from_utf8(strip_nul(&buf))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "PT_INTERP not UTF-8"))?;
Ok(s.to_string())
}
fn strip_nul(buf: &[u8]) -> &[u8] {
match buf.iter().position(|&b| b == 0) {
Some(p) => &buf[..p],
None => buf,
}
}
pub fn bind_mount_interp(binary: &Path, bundled_linker: &Path) -> io::Result<PathBuf> {
let interp = read_pt_interp(binary)?;
let interp_path = PathBuf::from(&interp);
if !interp_path.is_absolute() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("PT_INTERP is not absolute: {interp}"),
));
}
let parent = interp_path.parent().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("PT_INTERP has no parent: {interp}"),
)
})?;
if !parent.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"PT_INTERP parent dir doesn't exist on host: {}",
parent.display()
),
));
}
rustix::mount::mount_bind(bundled_linker, &interp_path).map_err(|e| {
io::Error::other(format!(
"bind-mount {} -> {} failed: {e}",
bundled_linker.display(),
interp_path.display()
))
})?;
Ok(interp_path)
}
pub fn symlink_interp(binary: &Path, bundled_linker: &Path) -> io::Result<PathBuf> {
let canonical_linker = bundled_linker.canonicalize()?;
let hash = simple_hash(canonical_linker.to_string_lossy().as_bytes());
let base = crate::paths::private_dir().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"onelf: no private runtime dir for interp symlink",
)
})?;
let link_path = base.join(format!("ld-{hash:08x}"));
let needs_create = match fs::read_link(&link_path) {
Ok(existing) => existing != canonical_linker,
Err(_) => true,
};
if needs_create {
let tmp = base.join(format!("ld-{hash:08x}.tmp"));
let _ = fs::remove_file(&tmp);
std::os::unix::fs::symlink(&canonical_linker, &tmp)?;
fs::rename(&tmp, &link_path)?;
}
let link_str = link_path.to_string_lossy();
patch_pt_interp_in_place(binary, &link_str)?;
Ok(link_path)
}
fn patch_pt_interp_in_place(binary: &Path, new_interp: &str) -> io::Result<()> {
let mut data = fs::read(binary)?;
let (p_offset, p_filesz) = crate::interp::pt_interp_slot(&data)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no PT_INTERP entry"))?;
let slot_end = p_offset
.checked_add(p_filesz)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PT_INTERP slot overflow"))?;
let new_bytes = new_interp.as_bytes();
if new_bytes.len() + 1 > p_filesz {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"new PT_INTERP ({} bytes) doesn't fit in slot ({} bytes)",
new_bytes.len() + 1,
p_filesz
),
));
}
if slot_end > data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PT_INTERP slot extends past file",
));
}
let current_end = (p_offset..slot_end)
.find(|&i| data[i] == 0)
.unwrap_or(slot_end);
if &data[p_offset..current_end] == new_bytes {
return Ok(());
}
data[p_offset..p_offset + new_bytes.len()].copy_from_slice(new_bytes);
for j in (p_offset + new_bytes.len())..slot_end {
data[j] = 0;
}
let tmp = binary.with_extension("interp-patch");
fs::write(&tmp, &data)?;
let _ = fs::set_permissions(
&tmp,
<fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755),
);
fs::rename(&tmp, binary)?;
Ok(())
}
fn simple_hash(data: &[u8]) -> u32 {
let mut h: u32 = 0x811c9dc5;
for &b in data {
h ^= b as u32;
h = h.wrapping_mul(0x01000193);
}
h
}