use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::io::{self, Write};
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
pub const APE_NEEDS_LOADER: bool = true;
pub const APE_SHELL: &str = "/bin/sh";
pub const APE_SYSTEM_LOADERS: &[&str] = &["/usr/bin/ape", "/usr/local/bin/ape"];
pub const APE_LOADER_HOST: crate::platform::ape::LoaderHost = crate::platform::ape::LoaderHost::Linux;
#[cfg(target_env = "musl")]
pub const APE_EXECVP_SHELL_FALLBACK: bool = false;
#[cfg(not(target_env = "musl"))]
pub const APE_EXECVP_SHELL_FALLBACK: bool = true;
pub fn is_exec_format_error(error: &io::Error) -> bool {
error.raw_os_error() == Some(libc::ENOEXEC)
}
pub fn is_executable(metadata: &std::fs::Metadata) -> bool {
metadata.permissions().mode() & 0o111 != 0
}
pub fn mark_executable(path: &Path) -> io::Result<()> {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755))
}
pub fn route_through_execvp(command: &mut std::process::Command) {
unsafe {
command.pre_exec(|| Ok(()));
}
}
#[cfg(feature = "async-process")]
pub fn route_tokio_through_execvp(command: &mut tokio::process::Command) {
unsafe {
command.pre_exec(|| Ok(()));
}
}
pub fn default_loader_dirs() -> Vec<PathBuf> {
let set = |value: Option<std::ffi::OsString>| value.filter(|value| !value.is_empty());
let mut dirs = Vec::new();
let cache = set(crate::env_vars::XDG_CACHE_HOME.os())
.map(PathBuf::from)
.or_else(|| set(crate::env_vars::HOME.os()).map(|home| PathBuf::from(home).join(".cache")));
if let Some(cache) = cache {
dirs.push(cache.join("running-process").join("ape"));
}
if let Some(runtime) = set(crate::env_vars::XDG_RUNTIME_DIR.os()) {
dirs.push(PathBuf::from(runtime).join("running-process").join("ape"));
}
let uid = unsafe { libc::geteuid() };
dirs.push(std::env::temp_dir().join(format!("running-process-ape-{uid}")));
dirs
}
pub fn private_exec_dir(dir: &Path) -> bool {
let _ = std::fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(dir);
let uid = unsafe { libc::geteuid() };
let private = std::fs::symlink_metadata(dir).is_ok_and(|meta| {
meta.file_type().is_dir() && meta.uid() == uid && meta.mode() & 0o022 == 0
});
private && mount_allows_exec(dir)
}
fn mount_allows_exec(dir: &Path) -> bool {
let Ok(path) = CString::new(dir.as_os_str().as_bytes()) else {
return false;
};
let mut stats = std::mem::MaybeUninit::<libc::statvfs>::uninit();
if unsafe { libc::statvfs(path.as_ptr(), stats.as_mut_ptr()) } != 0 {
return false;
}
let stats = unsafe { stats.assume_init() };
stats.f_flag & libc::ST_NOEXEC == 0
}
pub fn anonymous_executable(bytes: &[u8], name: &str) -> Option<PathBuf> {
memfd_loader(bytes, name)
}
static MEMFDS: Mutex<Option<HashMap<String, File>>> = Mutex::new(None);
fn memfd_loader(bytes: &[u8], name: &str) -> Option<PathBuf> {
let mut guard = MEMFDS.lock().unwrap_or_else(|error| error.into_inner());
let fds = guard.get_or_insert_with(HashMap::new);
if let Some(file) = fds.get(name) {
return Some(fd_path(file));
}
let file = {
let _fork = crate::platform::ape::exclusive_fork_guard();
create_sealed_memfd(bytes, name)?
};
let path = fd_path(&file);
if !path.exists() {
return None;
}
fds.insert(name.to_owned(), file);
Some(path)
}
fn create_sealed_memfd(bytes: &[u8], name: &str) -> Option<File> {
let cname = CString::new(name).ok()?;
let base = libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING;
let fd = [base | libc::MFD_EXEC, base].into_iter().find_map(|flags| {
let fd = unsafe { libc::syscall(libc::SYS_memfd_create, cname.as_ptr(), flags) };
i32::try_from(fd).ok().filter(|fd| *fd >= 0)
})?;
let mut file = unsafe { File::from_raw_fd(fd) };
file.write_all(bytes).ok()?;
file.set_permissions(std::fs::Permissions::from_mode(0o500))
.ok()?;
let seals = libc::F_SEAL_SHRINK | libc::F_SEAL_GROW | libc::F_SEAL_WRITE | libc::F_SEAL_SEAL;
if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_ADD_SEALS, seals) } != 0 {
return None;
}
Some(file)
}
fn fd_path(file: &File) -> PathBuf {
PathBuf::from(format!("/proc/self/fd/{}", file.as_raw_fd()))
}
#[cfg(test)]
#[path = "ape_tests.rs"]
mod tests;