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