artifactize_core/platform/
unix.rs1mod descriptors;
5pub(crate) mod ipc;
6mod process;
7
8use std::{
9 fs::{self, DirBuilder, File, OpenOptions, Permissions},
10 future::Future,
11 io,
12 os::unix::{
13 fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt},
14 process::ExitStatusExt,
15 },
16 path::Path,
17 process::ExitStatus,
18};
19
20pub(crate) use tempfile::TempDir as PrivateTempDir;
21use tokio::signal::unix::{SignalKind, signal};
22
23pub(crate) use process::{
24 Child, hide_console_window, kill_saved_group, kill_saved_session, process_start_time,
25 running_process_start_time, spawn_detached, spawn_gated, spawn_now, spawn_run, spawn_watched,
26};
27
28pub(crate) fn release_free_memory() {
32 #[cfg(all(target_os = "linux", target_env = "gnu"))]
33 unsafe {
35 libc::malloc_trim(0);
36 }
37}
38
39pub(crate) fn remove_abandoned_directory(path: &Path) -> io::Result<()> {
41 std::fs::remove_dir_all(path)
42}
43
44pub(crate) fn transient_file_access(_error: &io::Error) -> bool {
47 false
48}
49
50const PRIVATE_DIR_MODE: u32 = 0o700;
53
54const PRIVATE_FILE_MODE: u32 = 0o600;
57
58pub(crate) const PRIVATE_DIRECTORY: &str = "owner-only (mode 0700)";
60pub(crate) const PRIVATE_FILE: &str = "readable by their owner only (mode 0600)";
61
62pub(crate) const GROUP_OTHER_BITS: u32 = 0o077;
64
65const GROUP_OTHER_WRITE_BITS: u32 = 0o022;
68
69const PERMISSION_BITS: u32 = 0o777;
72
73pub(crate) fn create_private_dir_all(path: &Path) -> io::Result<()> {
75 DirBuilder::new()
76 .recursive(true)
77 .mode(PRIVATE_DIR_MODE)
78 .create(path)
79}
80
81pub(crate) fn create_private_dir(path: &Path) -> io::Result<()> {
83 DirBuilder::new().mode(PRIVATE_DIR_MODE).create(path)?;
84 fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIR_MODE))
85}
86
87pub(crate) fn private_tempdir_in(prefix: &str, parent: &Path) -> io::Result<PrivateTempDir> {
89 let directory = tempfile::Builder::new()
90 .prefix(prefix)
91 .permissions(Permissions::from_mode(PRIVATE_DIR_MODE))
92 .tempdir_in(parent)?;
93 fs::set_permissions(directory.path(), Permissions::from_mode(PRIVATE_DIR_MODE))?;
94 Ok(directory)
95}
96
97pub(crate) fn is_private_dir(path: &Path) -> io::Result<bool> {
99 Ok(fs::metadata(path)?.mode() & GROUP_OTHER_BITS == 0)
100}
101
102pub(crate) fn is_private_file(file: &File) -> io::Result<bool> {
104 let metadata = file.metadata()?;
105 Ok(metadata.is_file()
106 && metadata.mode() & PERMISSION_BITS == PRIVATE_FILE_MODE
107 && metadata.nlink() == 1)
108}
109
110pub(crate) fn private_options() -> OpenOptions {
112 let mut options = OpenOptions::new();
113 options.mode(PRIVATE_FILE_MODE);
114 options
115}
116
117pub(crate) fn restrict_file(file: &File) -> io::Result<()> {
119 file.set_permissions(Permissions::from_mode(PRIVATE_FILE_MODE))
120}
121
122pub(crate) fn sync_dir(path: &Path) -> io::Result<()> {
124 File::open(path).and_then(|directory| directory.sync_all())
125}
126
127pub(crate) fn file_identity(file: &File) -> io::Result<super::FileIdentity> {
129 let metadata = file.metadata()?;
130 Ok(super::FileIdentity {
131 volume: metadata.dev(),
132 index: metadata.ino(),
133 })
134}
135
136pub(crate) fn current_user() -> u32 {
138 unsafe { libc::geteuid() }
140}
141
142pub(crate) fn is_owner_only(file: &File) -> io::Result<bool> {
145 let metadata = file.metadata()?;
146 Ok(metadata.mode() & GROUP_OTHER_BITS == 0 && metadata.uid() == current_user())
147}
148
149pub(crate) const ENV_NAMES_IGNORE_CASE: bool = false;
151
152pub(crate) const HOME_VARIABLE: &str = "HOME";
154
155pub(crate) const TEMP_VARIABLES: &[&str] = &["TMPDIR", "TMP", "TEMP"];
158
159pub(crate) const STATE_VARIABLES: &[&str] = &["XDG_STATE_HOME"];
162
163pub(crate) const USER_VARIABLES: &[&str] = &["USER", "LOGNAME"];
165
166pub(crate) fn home_directory() -> Option<std::path::PathBuf> {
168 std::env::var_os(HOME_VARIABLE)
169 .filter(|path| !path.is_empty())
170 .map(Into::into)
171}
172
173pub(crate) fn path_from_bytes(bytes: &[u8]) -> std::path::PathBuf {
175 use std::os::unix::ffi::OsStringExt;
176 std::ffi::OsString::from_vec(bytes.to_vec()).into()
177}
178
179pub(crate) const CRLF_LINE_ENDINGS: bool = false;
181
182pub const VISIBLE_INPUT_FALLBACK: bool = false;
185
186pub(crate) fn exit_signal(status: &ExitStatus) -> Option<i32> {
188 status.signal()
189}
190
191pub(crate) fn host_name() -> Option<String> {
193 hostname::get().ok()?.into_string().ok()
194}
195
196#[cfg(target_os = "macos")]
200pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
201 let absolute = std::path::absolute(path)?;
202 for name in ["tmp", "var", "etc"] {
203 let alias = Path::new("/").join(name);
204 if let Ok(suffix) = absolute.strip_prefix(&alias) {
205 let metadata = alias.symlink_metadata()?;
206 let target = Path::new("/private").join(name);
207 let link = fs::read_link(&alias)?;
208 let link = if link.is_absolute() {
209 link
210 } else {
211 Path::new("/").join(link)
212 };
213 if !metadata.is_symlink() || metadata.uid() != 0 || link != target {
214 return Err(io::Error::other(format!(
215 "Refusing an untrusted system alias: {}",
216 alias.display()
217 )));
218 }
219 for parent in [Path::new("/"), Path::new("/private")] {
220 let metadata = parent.symlink_metadata()?;
221 if !metadata.is_dir()
222 || metadata.uid() != 0
223 || metadata.mode() & GROUP_OTHER_WRITE_BITS != 0
224 {
225 return Err(io::Error::other(
226 "System alias parents must not be replaceable.",
227 ));
228 }
229 }
230 return Ok(target.join(suffix));
231 }
232 }
233 Ok(absolute)
234}
235
236#[cfg(not(target_os = "macos"))]
238pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
239 std::path::absolute(path)
240}
241
242pub fn stop_requested() -> io::Result<impl Future<Output = ()> + Send + 'static> {
244 let mut interrupt = signal(SignalKind::interrupt())?;
245 let mut terminate = signal(SignalKind::terminate())?;
246 Ok(async move {
247 tokio::select! { _ = interrupt.recv() => {}, _ = terminate.recv() => {} }
248 })
249}
250
251pub struct HiddenInput(libc::termios);
253
254impl HiddenInput {
255 pub fn new() -> io::Result<Self> {
256 let mut termios = std::mem::MaybeUninit::uninit();
257 let saved = unsafe {
259 if libc::tcgetattr(libc::STDIN_FILENO, termios.as_mut_ptr()) != 0 {
260 return Err(io::Error::last_os_error());
261 }
262 termios.assume_init()
263 };
264 let mut hidden = saved;
265 hidden.c_lflag &= !libc::ECHO;
266 if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &hidden) } != 0 {
268 return Err(io::Error::last_os_error());
269 }
270 Ok(Self(saved))
271 }
272}
273
274impl Drop for HiddenInput {
275 fn drop(&mut self) {
276 unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.0) };
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
288 fn transient_file_access_is_always_false() {
289 assert!(!transient_file_access(&io::Error::from_raw_os_error(5)));
290 assert!(!transient_file_access(&io::Error::new(
291 io::ErrorKind::PermissionDenied,
292 "permission denied"
293 )));
294 }
295}