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 spawn_windowless,
27};
28
29pub(crate) fn release_free_memory() {
33 #[cfg(all(target_os = "linux", target_env = "gnu"))]
34 unsafe {
36 libc::malloc_trim(0);
37 }
38}
39
40pub(crate) fn remove_abandoned_directory(path: &Path) -> io::Result<()> {
42 std::fs::remove_dir_all(path)
43}
44
45pub(crate) fn transient_file_access(_error: &io::Error) -> bool {
48 false
49}
50
51const PRIVATE_DIR_MODE: u32 = 0o700;
54
55const PRIVATE_FILE_MODE: u32 = 0o600;
58
59pub(crate) const PRIVATE_DIRECTORY: &str = "owner-only (mode 0700)";
61pub(crate) const PRIVATE_FILE: &str = "readable by their owner only (mode 0600)";
62
63pub(crate) const GROUP_OTHER_BITS: u32 = 0o077;
65
66const GROUP_OTHER_WRITE_BITS: u32 = 0o022;
69
70const PERMISSION_BITS: u32 = 0o777;
73
74pub(crate) fn create_private_dir_all(path: &Path) -> io::Result<()> {
76 DirBuilder::new()
77 .recursive(true)
78 .mode(PRIVATE_DIR_MODE)
79 .create(path)
80}
81
82pub(crate) fn create_private_dir(path: &Path) -> io::Result<()> {
84 DirBuilder::new().mode(PRIVATE_DIR_MODE).create(path)?;
85 fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIR_MODE))
86}
87
88pub(crate) fn private_tempdir_in(prefix: &str, parent: &Path) -> io::Result<PrivateTempDir> {
90 let directory = tempfile::Builder::new()
91 .prefix(prefix)
92 .permissions(Permissions::from_mode(PRIVATE_DIR_MODE))
93 .tempdir_in(parent)?;
94 fs::set_permissions(directory.path(), Permissions::from_mode(PRIVATE_DIR_MODE))?;
95 Ok(directory)
96}
97
98pub(crate) fn is_private_dir(path: &Path) -> io::Result<bool> {
100 Ok(fs::metadata(path)?.mode() & GROUP_OTHER_BITS == 0)
101}
102
103pub(crate) fn is_private_file(file: &File) -> io::Result<bool> {
105 let metadata = file.metadata()?;
106 Ok(metadata.is_file()
107 && metadata.mode() & PERMISSION_BITS == PRIVATE_FILE_MODE
108 && metadata.nlink() == 1)
109}
110
111pub(crate) fn private_options() -> OpenOptions {
113 let mut options = OpenOptions::new();
114 options.mode(PRIVATE_FILE_MODE);
115 options
116}
117
118pub(crate) fn restrict_file(file: &File) -> io::Result<()> {
120 file.set_permissions(Permissions::from_mode(PRIVATE_FILE_MODE))
121}
122
123pub(crate) fn sync_dir(path: &Path) -> io::Result<()> {
125 File::open(path).and_then(|directory| directory.sync_all())
126}
127
128pub(crate) fn file_identity(file: &File) -> io::Result<super::FileIdentity> {
130 let metadata = file.metadata()?;
131 Ok(super::FileIdentity {
132 volume: metadata.dev(),
133 index: metadata.ino(),
134 })
135}
136
137pub(crate) fn current_user() -> u32 {
139 unsafe { libc::geteuid() }
141}
142
143pub(crate) fn is_owner_only(file: &File) -> io::Result<bool> {
146 let metadata = file.metadata()?;
147 Ok(metadata.mode() & GROUP_OTHER_BITS == 0 && metadata.uid() == current_user())
148}
149
150pub(crate) const ENV_NAMES_IGNORE_CASE: bool = false;
152
153pub(crate) const HOME_VARIABLE: &str = "HOME";
155
156pub(crate) const TEMP_VARIABLES: &[&str] = &["TMPDIR", "TMP", "TEMP"];
159
160pub(crate) const STATE_VARIABLES: &[&str] = &["XDG_STATE_HOME"];
163
164pub(crate) const USER_VARIABLES: &[&str] = &["USER", "LOGNAME"];
166
167pub(crate) fn home_directory() -> Option<std::path::PathBuf> {
169 std::env::var_os(HOME_VARIABLE)
170 .filter(|path| !path.is_empty())
171 .map(Into::into)
172}
173
174pub(crate) fn path_from_bytes(bytes: &[u8]) -> std::path::PathBuf {
176 use std::os::unix::ffi::OsStringExt;
177 std::ffi::OsString::from_vec(bytes.to_vec()).into()
178}
179
180pub(crate) const CRLF_LINE_ENDINGS: bool = false;
182
183pub const VISIBLE_INPUT_FALLBACK: bool = false;
186
187pub(crate) fn exit_signal(status: &ExitStatus) -> Option<i32> {
189 status.signal()
190}
191
192pub(crate) fn host_name() -> Option<String> {
194 hostname::get().ok()?.into_string().ok()
195}
196
197#[cfg(target_os = "macos")]
201pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
202 let absolute = std::path::absolute(path)?;
203 for name in ["tmp", "var", "etc"] {
204 let alias = Path::new("/").join(name);
205 if let Ok(suffix) = absolute.strip_prefix(&alias) {
206 let metadata = alias.symlink_metadata()?;
207 let target = Path::new("/private").join(name);
208 let link = fs::read_link(&alias)?;
209 let link = if link.is_absolute() {
210 link
211 } else {
212 Path::new("/").join(link)
213 };
214 if !metadata.is_symlink() || metadata.uid() != 0 || link != target {
215 return Err(io::Error::other(format!(
216 "Refusing an untrusted system alias: {}",
217 alias.display()
218 )));
219 }
220 for parent in [Path::new("/"), Path::new("/private")] {
221 let metadata = parent.symlink_metadata()?;
222 if !metadata.is_dir()
223 || metadata.uid() != 0
224 || metadata.mode() & GROUP_OTHER_WRITE_BITS != 0
225 {
226 return Err(io::Error::other(
227 "System alias parents must not be replaceable.",
228 ));
229 }
230 }
231 return Ok(target.join(suffix));
232 }
233 }
234 Ok(absolute)
235}
236
237#[cfg(not(target_os = "macos"))]
239pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
240 std::path::absolute(path)
241}
242
243pub fn stop_requested() -> io::Result<impl Future<Output = ()> + Send + 'static> {
245 let mut interrupt = signal(SignalKind::interrupt())?;
246 let mut terminate = signal(SignalKind::terminate())?;
247 Ok(async move {
248 tokio::select! { _ = interrupt.recv() => {}, _ = terminate.recv() => {} }
249 })
250}
251
252pub struct HiddenInput(libc::termios);
254
255impl HiddenInput {
256 pub fn new() -> io::Result<Self> {
257 let mut termios = std::mem::MaybeUninit::uninit();
258 let saved = unsafe {
260 if libc::tcgetattr(libc::STDIN_FILENO, termios.as_mut_ptr()) != 0 {
261 return Err(io::Error::last_os_error());
262 }
263 termios.assume_init()
264 };
265 let mut hidden = saved;
266 hidden.c_lflag &= !libc::ECHO;
267 if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &hidden) } != 0 {
269 return Err(io::Error::last_os_error());
270 }
271 Ok(Self(saved))
272 }
273}
274
275impl Drop for HiddenInput {
276 fn drop(&mut self) {
277 unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.0) };
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
289 fn transient_file_access_is_always_false() {
290 assert!(!transient_file_access(&io::Error::from_raw_os_error(5)));
291 assert!(!transient_file_access(&io::Error::new(
292 io::ErrorKind::PermissionDenied,
293 "permission denied"
294 )));
295 }
296}