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, kill_saved_group, kill_saved_session, process_start_time, running_process_start_time,
24 spawn_detached, spawn_gated, spawn_run, spawn_watched,
25};
26
27pub(crate) fn remove_abandoned_directory(path: &Path) -> io::Result<()> {
29 std::fs::remove_dir_all(path)
30}
31
32pub(crate) fn transient_file_access(_error: &io::Error) -> bool {
35 false
36}
37
38const PRIVATE_DIR_MODE: u32 = 0o700;
41
42const PRIVATE_FILE_MODE: u32 = 0o600;
45
46pub(crate) const PRIVATE_DIRECTORY: &str = "owner-only (mode 0700)";
48pub(crate) const PRIVATE_FILE: &str = "readable by their owner only (mode 0600)";
49
50pub(crate) const GROUP_OTHER_BITS: u32 = 0o077;
52
53const GROUP_OTHER_WRITE_BITS: u32 = 0o022;
56
57const PERMISSION_BITS: u32 = 0o777;
60
61pub(crate) fn create_private_dir_all(path: &Path) -> io::Result<()> {
63 DirBuilder::new()
64 .recursive(true)
65 .mode(PRIVATE_DIR_MODE)
66 .create(path)
67}
68
69pub(crate) fn create_private_dir(path: &Path) -> io::Result<()> {
71 DirBuilder::new().mode(PRIVATE_DIR_MODE).create(path)?;
72 fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIR_MODE))
73}
74
75pub(crate) fn private_tempdir_in(prefix: &str, parent: &Path) -> io::Result<PrivateTempDir> {
77 let directory = tempfile::Builder::new()
78 .prefix(prefix)
79 .permissions(Permissions::from_mode(PRIVATE_DIR_MODE))
80 .tempdir_in(parent)?;
81 fs::set_permissions(directory.path(), Permissions::from_mode(PRIVATE_DIR_MODE))?;
82 Ok(directory)
83}
84
85pub(crate) fn is_private_dir(path: &Path) -> io::Result<bool> {
87 Ok(fs::metadata(path)?.mode() & GROUP_OTHER_BITS == 0)
88}
89
90pub(crate) fn is_private_file(file: &File) -> io::Result<bool> {
92 let metadata = file.metadata()?;
93 Ok(metadata.is_file()
94 && metadata.mode() & PERMISSION_BITS == PRIVATE_FILE_MODE
95 && metadata.nlink() == 1)
96}
97
98pub(crate) fn private_options() -> OpenOptions {
100 let mut options = OpenOptions::new();
101 options.mode(PRIVATE_FILE_MODE);
102 options
103}
104
105pub(crate) fn restrict_file(file: &File) -> io::Result<()> {
107 file.set_permissions(Permissions::from_mode(PRIVATE_FILE_MODE))
108}
109
110pub(crate) fn sync_dir(path: &Path) -> io::Result<()> {
112 File::open(path).and_then(|directory| directory.sync_all())
113}
114
115pub(crate) fn file_identity(file: &File) -> io::Result<super::FileIdentity> {
117 let metadata = file.metadata()?;
118 Ok(super::FileIdentity {
119 volume: metadata.dev(),
120 index: metadata.ino(),
121 })
122}
123
124pub(crate) fn current_user() -> u32 {
126 unsafe { libc::geteuid() }
128}
129
130pub(crate) fn is_owner_only(file: &File) -> io::Result<bool> {
133 let metadata = file.metadata()?;
134 Ok(metadata.mode() & GROUP_OTHER_BITS == 0 && metadata.uid() == current_user())
135}
136
137pub(crate) const ENV_NAMES_IGNORE_CASE: bool = false;
139
140pub(crate) const HOME_VARIABLE: &str = "HOME";
142
143pub(crate) const TEMP_VARIABLES: &[&str] = &["TMPDIR", "TMP", "TEMP"];
146
147pub(crate) const STATE_VARIABLES: &[&str] = &["XDG_STATE_HOME"];
150
151pub(crate) const USER_VARIABLES: &[&str] = &["USER", "LOGNAME"];
153
154pub(crate) fn home_directory() -> Option<std::path::PathBuf> {
156 std::env::var_os(HOME_VARIABLE)
157 .filter(|path| !path.is_empty())
158 .map(Into::into)
159}
160
161pub(crate) fn path_from_bytes(bytes: &[u8]) -> std::path::PathBuf {
163 use std::os::unix::ffi::OsStringExt;
164 std::ffi::OsString::from_vec(bytes.to_vec()).into()
165}
166
167pub(crate) const CRLF_LINE_ENDINGS: bool = false;
169
170pub const VISIBLE_INPUT_FALLBACK: bool = false;
173
174pub(crate) fn exit_signal(status: &ExitStatus) -> Option<i32> {
176 status.signal()
177}
178
179pub(crate) fn host_name() -> Option<String> {
181 hostname::get().ok()?.into_string().ok()
182}
183
184#[cfg(target_os = "macos")]
188pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
189 let absolute = std::path::absolute(path)?;
190 for name in ["tmp", "var", "etc"] {
191 let alias = Path::new("/").join(name);
192 if let Ok(suffix) = absolute.strip_prefix(&alias) {
193 let metadata = alias.symlink_metadata()?;
194 let target = Path::new("/private").join(name);
195 let link = fs::read_link(&alias)?;
196 let link = if link.is_absolute() {
197 link
198 } else {
199 Path::new("/").join(link)
200 };
201 if !metadata.is_symlink() || metadata.uid() != 0 || link != target {
202 return Err(io::Error::other(format!(
203 "Refusing an untrusted system alias: {}",
204 alias.display()
205 )));
206 }
207 for parent in [Path::new("/"), Path::new("/private")] {
208 let metadata = parent.symlink_metadata()?;
209 if !metadata.is_dir()
210 || metadata.uid() != 0
211 || metadata.mode() & GROUP_OTHER_WRITE_BITS != 0
212 {
213 return Err(io::Error::other(
214 "System alias parents must not be replaceable.",
215 ));
216 }
217 }
218 return Ok(target.join(suffix));
219 }
220 }
221 Ok(absolute)
222}
223
224#[cfg(not(target_os = "macos"))]
226pub(crate) fn resolve_system_aliases(path: &Path) -> io::Result<std::path::PathBuf> {
227 std::path::absolute(path)
228}
229
230pub fn stop_requested() -> io::Result<impl Future<Output = ()> + Send + 'static> {
232 let mut interrupt = signal(SignalKind::interrupt())?;
233 let mut terminate = signal(SignalKind::terminate())?;
234 Ok(async move {
235 tokio::select! { _ = interrupt.recv() => {}, _ = terminate.recv() => {} }
236 })
237}
238
239pub struct HiddenInput(libc::termios);
241
242impl HiddenInput {
243 pub fn new() -> io::Result<Self> {
244 let mut termios = std::mem::MaybeUninit::uninit();
245 let saved = unsafe {
247 if libc::tcgetattr(libc::STDIN_FILENO, termios.as_mut_ptr()) != 0 {
248 return Err(io::Error::last_os_error());
249 }
250 termios.assume_init()
251 };
252 let mut hidden = saved;
253 hidden.c_lflag &= !libc::ECHO;
254 if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &hidden) } != 0 {
256 return Err(io::Error::last_os_error());
257 }
258 Ok(Self(saved))
259 }
260}
261
262impl Drop for HiddenInput {
263 fn drop(&mut self) {
264 unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &self.0) };
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
276 fn transient_file_access_is_always_false() {
277 assert!(!transient_file_access(&io::Error::from_raw_os_error(5)));
278 assert!(!transient_file_access(&io::Error::new(
279 io::ErrorKind::PermissionDenied,
280 "permission denied"
281 )));
282 }
283}