pub(crate) mod fs;
pub(crate) mod mount;
mod protocol;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicI32, Ordering};
use rustix::io::FdFlags;
use rustix::process::{Pid, Signal, WaitOptions, kill_process, waitpid};
use rustix::runtime::{KernelSigSet, KernelSigaction, KernelSigactionFlags, kernel_sigaction};
use crate::loader::PackageData;
static CHILD_PID: AtomicI32 = AtomicI32::new(0);
static SIGCHLD_PIPE: AtomicI32 = AtomicI32::new(-1);
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
".global __onelf_signal_restorer",
".type __onelf_signal_restorer, @function",
"__onelf_signal_restorer:",
"mov rax, 15", "syscall",
);
#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
".global __onelf_signal_restorer",
".type __onelf_signal_restorer, @function",
"__onelf_signal_restorer:",
"mov x8, #139", "svc #0",
);
#[cfg(target_arch = "x86")]
core::arch::global_asm!(
".global __onelf_signal_restorer",
".type __onelf_signal_restorer, @function",
"__onelf_signal_restorer:",
"pop eax", "mov eax, 119", "int 0x80",
);
unsafe extern "C" {
fn __onelf_signal_restorer();
}
unsafe extern "C" fn signal_handler(sig: core::ffi::c_int) {
if sig == 17 {
let fd = SIGCHLD_PIPE.load(Ordering::Relaxed);
if fd >= 0 {
let borrowed = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) };
let _ = rustix::io::write(borrowed, b"c");
}
return;
}
let pid = CHILD_PID.load(Ordering::Relaxed);
if pid > 0
&& let Some(pid) = Pid::from_raw(pid)
&& let Some(signal) = Signal::from_named_raw(sig)
{
let _ = kill_process(pid, signal);
}
}
fn install_signal_handlers() {
let mut mask = KernelSigSet::empty();
mask.insert(Signal::INT);
mask.insert(Signal::TERM);
mask.insert(Signal::HUP);
mask.insert(Signal::QUIT);
let flags = KernelSigactionFlags::RESTORER;
for &sig in &[
Signal::INT,
Signal::TERM,
Signal::HUP,
Signal::QUIT,
Signal::CHILD,
] {
let action = KernelSigaction {
sa_handler_kernel: Some(signal_handler),
sa_flags: flags,
sa_restorer: Some(__onelf_signal_restorer),
sa_mask: mask.clone(),
};
unsafe {
let _ = kernel_sigaction(sig, Some(action));
}
}
}
fn is_mountpoint(path: &Path) -> bool {
let target = path.to_string_lossy();
let Ok(info) = std::fs::read_to_string("/proc/self/mountinfo") else {
return false;
};
info.lines().any(|line| {
line.split(' ')
.nth(4)
.map(|mp| mp.replace("\\040", " ") == *target)
.unwrap_or(false)
})
}
#[allow(clippy::too_many_arguments)]
fn exec_from_mount(
pkg: &mut PackageData,
ep_idx: usize,
argv0: &str,
exec_path: &str,
args: &[String],
interp_data: Option<&[u8]>,
env_data: Option<&[u8]>,
mountpoint: &Path,
) -> bool {
use std::os::unix::process::CommandExt;
let ep_target_entry = pkg.manifest.entrypoints[ep_idx].target_entry as usize;
let ep_working_dir = pkg.manifest.entrypoints[ep_idx].working_dir;
let ep_name = pkg
.manifest
.get_string(pkg.manifest.entrypoints[ep_idx].name)
.to_string();
let target_path_str = pkg.manifest.entry_path(ep_target_entry);
let target_path = mountpoint.join(&target_path_str);
let mountpoint_str = mountpoint.to_str().unwrap_or("").to_string();
let lib_paths_str = pkg.manifest.lib_dirs().join(":");
let exe_path = std::path::Path::new(exec_path);
let exe_dir = exe_path.parent().unwrap_or(std::path::Path::new("."));
let exe_name = exe_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("onelf");
crate::portable::setup_portable(exe_dir, exe_name);
let child_cwd: Option<PathBuf> = match ep_working_dir {
onelf_format::WorkingDir::PackageRoot => Some(mountpoint.to_path_buf()),
onelf_format::WorkingDir::EntrypointParent => target_path.parent().map(|p| p.to_path_buf()),
onelf_format::WorkingDir::Inherit => None,
};
let target_path_s = target_path.to_str().unwrap_or("");
let lib_path = crate::env::setup_env(
&mountpoint_str,
argv0,
exec_path,
&ep_name,
"fuse",
&lib_paths_str,
target_path_s,
crate::env::expose_host_libs(pkg),
);
if let Some(data) = env_data {
crate::env::apply_custom_env(data, &mountpoint_str);
}
let lib_dirs = pkg.manifest.lib_dirs();
let bundled_interp_rel = interp_data.and_then(crate::interp::parse_bundled_interp_rel);
if let Some(interp) =
crate::interp::should_use_userland_exec(&target_path, mountpoint, bundled_interp_rel)
{
if let Some(cwd) = &child_cwd {
let _ = std::env::set_current_dir(cwd);
}
crate::interp::exec_userland(&target_path, &interp, &lib_path, argv0, args);
}
let mut cmd = crate::interp::build_exec_command(
&target_path,
mountpoint,
&lib_dirs,
&lib_path,
true, argv0,
args,
);
if let Some(cwd) = &child_cwd {
cmd.current_dir(cwd);
}
let err = cmd.exec();
eprintln!("onelf-rt: exec failed: {err}");
std::process::exit(1);
}
fn mount_in_use(mountpoint: &Path) -> bool {
let me = std::process::id();
let Ok(entries) = std::fs::read_dir("/proc") else {
return false;
};
for entry in entries.filter_map(Result::ok) {
let name = entry.file_name();
let Some(pid) = name.to_str().and_then(|s| s.parse::<u32>().ok()) else {
continue;
};
if pid == me {
continue;
}
let proc_dir = entry.path();
for link in ["exe", "cwd", "root"] {
if let Ok(target) = std::fs::read_link(proc_dir.join(link))
&& target.starts_with(mountpoint)
{
return true;
}
}
}
false
}
fn cleanup_mountpoint(mountpoint: &Path, used_namespace: bool) {
if used_namespace {
mount::fuse_unmount_direct(mountpoint);
} else {
mount::fuse_unmount(mountpoint);
}
let _ = std::fs::remove_dir(mountpoint);
}
#[allow(clippy::too_many_arguments)]
pub fn execute_fuse(
pkg: &mut PackageData,
ep_idx: usize,
argv0: &str,
exec_path: &str,
args: &[String],
interp_data: Option<&[u8]>,
env_data: Option<&[u8]>,
needs_setuid: bool,
) -> bool {
use std::os::unix::process::CommandExt;
crate::paths::sweep_stale_mountpoints();
let claim =
match crate::paths::create_mountpoint(pkg.manifest.name(), &pkg.manifest.header.package_id)
{
Some(m) => m,
None => return false,
};
let mountpoint = claim.path().to_path_buf();
if is_mountpoint(&mountpoint) {
if mountpoint.read_dir().is_ok() {
return exec_from_mount(
pkg,
ep_idx,
argv0,
exec_path,
args,
interp_data,
env_data,
&mountpoint,
);
}
mount::fuse_unmount(&mountpoint);
}
let skip_namespace = needs_setuid
|| std::env::var_os("ONELF_FUSE_NO_NAMESPACE")
.map(|v| v != "0" && !v.is_empty())
.unwrap_or(false);
let ns_result = if skip_namespace {
let why = if needs_setuid {
"package needs setuid; using fusermount3"
} else {
"ONELF_FUSE_NO_NAMESPACE set; using fusermount3"
};
Err(std::io::Error::other(why))
} else {
mount::fuse_mount_unshare(&mountpoint)
};
let (fuse_fd, used_namespace) = match ns_result {
Ok(fd) => (fd, true),
Err(ns_err) => {
if !mount::fusermount3_available() {
if !needs_setuid {
eprintln!("onelf-rt: fuse: namespace mount failed: {ns_err}");
eprintln!("onelf-rt: fuse: fusermount3 not available either; cannot continue");
}
let _ = std::fs::remove_dir(&mountpoint);
return false;
}
match mount::fuse_mount(&mountpoint) {
Ok(fd) => (fd, false),
Err(e) => {
eprintln!("onelf-rt: fuse: mount failed: {e}");
let _ = std::fs::remove_dir(&mountpoint);
return false;
}
}
}
};
let _ = rustix::io::fcntl_setfd(&fuse_fd, FdFlags::CLOEXEC);
let ep_target_entry = pkg.manifest.entrypoints[ep_idx].target_entry as usize;
let ep_working_dir = pkg.manifest.entrypoints[ep_idx].working_dir;
let ep_name = pkg
.manifest
.get_string(pkg.manifest.entrypoints[ep_idx].name)
.to_string();
let target_path_str = pkg.manifest.entry_path(ep_target_entry);
let target_path = mountpoint.join(&target_path_str);
let mountpoint_str = mountpoint.to_str().unwrap_or("").to_string();
let lib_paths_str = pkg.manifest.lib_dirs().join(":");
let exe_path = std::path::Path::new(exec_path);
let exe_dir = exe_path.parent().unwrap_or(std::path::Path::new("."));
let exe_name = exe_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("onelf");
crate::portable::setup_portable(exe_dir, exe_name);
let child_cwd: Option<PathBuf> = match ep_working_dir {
onelf_format::WorkingDir::PackageRoot => Some(mountpoint.clone()),
onelf_format::WorkingDir::EntrypointParent => target_path.parent().map(|p| p.to_path_buf()),
onelf_format::WorkingDir::Inherit => None,
};
let bundled_interp_rel = interp_data.and_then(crate::interp::parse_bundled_interp_rel);
let (pipe_read, pipe_write) = match rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC) {
Ok(p) => p,
Err(_) => {
cleanup_mountpoint(&mountpoint, used_namespace);
return false;
}
};
let _ = rustix::io::fcntl_setfd(&pipe_write, FdFlags::empty());
use rustix::runtime::{Fork, kernel_fork};
match unsafe { kernel_fork() } {
Ok(Fork::Child(_)) => {
let target_path_s = target_path.to_str().unwrap_or("");
let lib_path = crate::env::setup_env(
&mountpoint_str,
argv0,
exec_path,
&ep_name,
"fuse",
&lib_paths_str,
target_path_s,
crate::env::expose_host_libs(pkg),
);
if let Some(data) = env_data {
crate::env::apply_custom_env(data, &mountpoint_str);
}
let lib_dirs = pkg.manifest.lib_dirs();
if let Some(interp) = crate::interp::should_use_userland_exec(
&target_path,
&mountpoint,
bundled_interp_rel,
) {
if let Some(cwd) = &child_cwd {
let _ = std::env::set_current_dir(cwd);
}
crate::interp::exec_userland(&target_path, &interp, &lib_path, argv0, args);
}
let mut cmd = crate::interp::build_exec_command(
&target_path,
&mountpoint,
&lib_dirs,
&lib_path,
true, argv0,
args,
);
if let Some(cwd) = &child_cwd {
cmd.current_dir(cwd);
}
let err = cmd.exec();
eprintln!("onelf-rt: exec failed: {err}");
std::process::exit(1);
}
Ok(Fork::ParentOf(child_pid)) => {
drop(pipe_write);
CHILD_PID.store(child_pid.as_raw_nonzero().get(), Ordering::Relaxed);
let sigchld = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC).ok();
if let Some((_, w)) = &sigchld {
SIGCHLD_PIPE.store(rustix::fd::AsRawFd::as_raw_fd(w), Ordering::Relaxed);
}
install_signal_handlers();
let mut state = fs::FuseState::new(
&pkg.manifest,
&mut pkg.file,
&pkg.footer,
pkg.dict.as_deref(),
);
let mut fuse_buf = vec![0u8; 1024 * 1024 + 4096];
state.run_loop(
&fuse_fd,
&pipe_read,
sigchld.as_ref().map(|(r, _)| r),
&mut fuse_buf,
);
let exit_status = loop {
match waitpid(Some(child_pid), WaitOptions::NOHANG) {
Ok(Some((_pid, status))) => break status,
Ok(None) => match waitpid(Some(child_pid), WaitOptions::empty()) {
Ok(Some((_pid, status))) => break status,
Ok(None) => continue,
Err(rustix::io::Errno::INTR) => continue,
Err(_) => {
cleanup_mountpoint(&mountpoint, used_namespace);
std::process::exit(1);
}
},
Err(rustix::io::Errno::INTR) => continue,
Err(_) => {
cleanup_mountpoint(&mountpoint, used_namespace);
std::process::exit(1);
}
}
};
let handed_off = match unsafe { kernel_fork() } {
Ok(Fork::Child(_)) => {
let _ = rustix::process::setsid();
if let Ok(null) = rustix::fs::open(
"/dev/null",
rustix::fs::OFlags::RDWR,
rustix::fs::Mode::empty(),
) {
for target in 0..=2 {
use std::os::fd::FromRawFd;
let mut slot = std::mem::ManuallyDrop::new(unsafe {
std::os::fd::OwnedFd::from_raw_fd(target)
});
let _ = rustix::io::dup2(&null, &mut slot);
}
}
state.serve_detached(&fuse_fd, &mut fuse_buf, || !mount_in_use(&mountpoint));
drop(fuse_fd);
cleanup_mountpoint(&mountpoint, used_namespace);
std::process::exit(0);
}
Ok(Fork::ParentOf(_)) => true,
Err(_) => false,
};
if !handed_off {
drop(fuse_fd);
cleanup_mountpoint(&mountpoint, used_namespace);
}
if let Some(code) = exit_status.exit_status() {
std::process::exit(code)
} else if let Some(sig) = exit_status.terminating_signal() {
unsafe {
let action = KernelSigaction {
sa_handler_kernel: None,
sa_flags: KernelSigactionFlags::RESTORER,
sa_restorer: Some(__onelf_signal_restorer),
sa_mask: KernelSigSet::empty(),
};
if let Some(signal) = Signal::from_named_raw(sig) {
let _ = kernel_sigaction(signal, Some(action));
let _ = kill_process(rustix::process::getpid(), signal);
}
}
std::process::exit(128 + sig)
} else {
std::process::exit(1)
}
}
Err(e) => {
cleanup_mountpoint(&mountpoint, used_namespace);
eprintln!("onelf-rt: fork failed: {e}");
std::process::exit(1);
}
}
}