running_process_platform_internal/platform_linux/
ape.rs1use std::collections::HashMap;
8use std::ffi::CString;
9use std::fs::File;
10use std::io::{self, Write};
11use std::os::fd::{AsRawFd, FromRawFd};
12use std::os::unix::ffi::OsStrExt;
13use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
14use std::os::unix::process::CommandExt;
15use std::path::{Path, PathBuf};
16use std::sync::Mutex;
17
18pub const APE_NEEDS_LOADER: bool = true;
20
21pub const APE_SHELL: &str = "/bin/sh";
23
24pub const APE_SYSTEM_LOADERS: &[&str] = &["/usr/bin/ape", "/usr/local/bin/ape"];
26
27pub const APE_LOADER_HOST: crate::platform::ape::LoaderHost = crate::platform::ape::LoaderHost::Linux;
29
30#[cfg(target_env = "musl")]
33pub const APE_EXECVP_SHELL_FALLBACK: bool = false;
34#[cfg(not(target_env = "musl"))]
37pub const APE_EXECVP_SHELL_FALLBACK: bool = true;
38
39pub fn is_exec_format_error(error: &io::Error) -> bool {
41 error.raw_os_error() == Some(libc::ENOEXEC)
42}
43
44pub fn is_executable(metadata: &std::fs::Metadata) -> bool {
46 metadata.permissions().mode() & 0o111 != 0
47}
48
49pub fn mark_executable(path: &Path) -> io::Result<()> {
51 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755))
52}
53
54pub fn route_through_execvp(command: &mut std::process::Command) {
60 unsafe {
62 command.pre_exec(|| Ok(()));
63 }
64}
65
66#[cfg(feature = "async-process")]
68pub fn route_tokio_through_execvp(command: &mut tokio::process::Command) {
69 unsafe {
71 command.pre_exec(|| Ok(()));
72 }
73}
74
75pub fn default_loader_dirs() -> Vec<PathBuf> {
77 let set = |value: Option<std::ffi::OsString>| value.filter(|value| !value.is_empty());
78 let mut dirs = Vec::new();
79 let cache = set(crate::env_vars::XDG_CACHE_HOME.os())
80 .map(PathBuf::from)
81 .or_else(|| set(crate::env_vars::HOME.os()).map(|home| PathBuf::from(home).join(".cache")));
82 if let Some(cache) = cache {
83 dirs.push(cache.join("running-process").join("ape"));
84 }
85 if let Some(runtime) = set(crate::env_vars::XDG_RUNTIME_DIR.os()) {
86 dirs.push(PathBuf::from(runtime).join("running-process").join("ape"));
87 }
88 let uid = unsafe { libc::geteuid() };
90 dirs.push(std::env::temp_dir().join(format!("running-process-ape-{uid}")));
91 dirs
92}
93
94pub fn private_exec_dir(dir: &Path) -> bool {
99 let _ = std::fs::DirBuilder::new()
100 .recursive(true)
101 .mode(0o700)
102 .create(dir);
103 let uid = unsafe { libc::geteuid() };
105 let private = std::fs::symlink_metadata(dir).is_ok_and(|meta| {
106 meta.file_type().is_dir() && meta.uid() == uid && meta.mode() & 0o022 == 0
107 });
108 private && mount_allows_exec(dir)
109}
110
111fn mount_allows_exec(dir: &Path) -> bool {
112 let Ok(path) = CString::new(dir.as_os_str().as_bytes()) else {
113 return false;
114 };
115 let mut stats = std::mem::MaybeUninit::<libc::statvfs>::uninit();
116 if unsafe { libc::statvfs(path.as_ptr(), stats.as_mut_ptr()) } != 0 {
118 return false;
119 }
120 let stats = unsafe { stats.assume_init() };
122 stats.f_flag & libc::ST_NOEXEC == 0
123}
124
125pub fn anonymous_executable(bytes: &[u8], name: &str) -> Option<PathBuf> {
128 memfd_loader(bytes, name)
129}
130
131static MEMFDS: Mutex<Option<HashMap<String, File>>> = Mutex::new(None);
133
134fn memfd_loader(bytes: &[u8], name: &str) -> Option<PathBuf> {
135 let mut guard = MEMFDS.lock().unwrap_or_else(|error| error.into_inner());
136 let fds = guard.get_or_insert_with(HashMap::new);
137 if let Some(file) = fds.get(name) {
138 return Some(fd_path(file));
139 }
140 let file = {
141 let _fork = crate::platform::ape::exclusive_fork_guard();
143 create_sealed_memfd(bytes, name)?
144 };
145 let path = fd_path(&file);
146 if !path.exists() {
149 return None;
150 }
151 fds.insert(name.to_owned(), file);
152 Some(path)
153}
154
155fn create_sealed_memfd(bytes: &[u8], name: &str) -> Option<File> {
156 let cname = CString::new(name).ok()?;
157 let base = libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING;
158 let fd = [base | libc::MFD_EXEC, base].into_iter().find_map(|flags| {
161 let fd = unsafe { libc::syscall(libc::SYS_memfd_create, cname.as_ptr(), flags) };
166 i32::try_from(fd).ok().filter(|fd| *fd >= 0)
167 })?;
168 let mut file = unsafe { File::from_raw_fd(fd) };
170 file.write_all(bytes).ok()?;
171 file.set_permissions(std::fs::Permissions::from_mode(0o500))
172 .ok()?;
173 let seals = libc::F_SEAL_SHRINK | libc::F_SEAL_GROW | libc::F_SEAL_WRITE | libc::F_SEAL_SEAL;
174 if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_ADD_SEALS, seals) } != 0 {
176 return None;
177 }
178 Some(file)
179}
180
181fn fd_path(file: &File) -> PathBuf {
182 PathBuf::from(format!("/proc/self/fd/{}", file.as_raw_fd()))
183}
184
185#[cfg(test)]
186#[path = "ape_tests.rs"]
187mod tests;