1pub mod environment;
20mod hidden_input;
21pub(crate) mod path_serde;
22pub(crate) mod paths;
23pub(crate) mod program;
24#[cfg(unix)]
25mod unix;
26#[cfg(windows)]
27mod windows;
28
29#[cfg(unix)]
30use unix as os;
31#[cfg(windows)]
32use windows as os;
33
34pub(crate) use os::{
35 CRLF_LINE_ENDINGS, Child, ENV_NAMES_IGNORE_CASE, HOME_VARIABLE, PrivateTempDir,
36 STATE_VARIABLES, TEMP_VARIABLES, USER_VARIABLES, create_private_dir, create_private_dir_all,
37 exit_signal, file_identity, home_directory, host_name, ipc, is_owner_only, is_private_dir,
38 is_private_file, kill_saved_group, kill_saved_session, path_from_bytes, private_options,
39 private_tempdir_in, process_start_time, remove_abandoned_directory, resolve_system_aliases,
40 restrict_file, running_process_start_time, spawn_detached, spawn_gated, spawn_run,
41 spawn_watched, sync_dir, transient_file_access,
42};
43pub use os::{HiddenInput, VISIBLE_INPUT_FALLBACK, stop_requested};
44pub(crate) use os::{PRIVATE_DIRECTORY, PRIVATE_FILE};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub(crate) struct FileIdentity {
50 volume: u64,
51 index: u64,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub(crate) enum ProcessEnd {
57 Exited(i32),
59 Signaled(Option<i32>),
61}
62
63pub(crate) fn process_end(status: &std::process::ExitStatus) -> ProcessEnd {
65 match status.code() {
66 Some(code) => ProcessEnd::Exited(code),
67 None => ProcessEnd::Signaled(exit_signal(status)),
68 }
69}
70
71pub(crate) fn exit_description(status: &std::process::ExitStatus) -> String {
74 status.to_string()
75}
76
77pub(crate) const FILE_LOCK_RETRY_INTERVAL: std::time::Duration =
80 std::time::Duration::from_millis(25);
81
82pub(crate) use artifactize_tools::files::{
83 EntryName, FileKind, canonicalize, entry_kind, link_target, open_directory, open_entry,
84 open_no_follow, read_dir,
85};
86pub use artifactize_tools::files::{open_nonblocking, path_text};
87
88pub fn open_regular(path: &std::path::Path) -> std::io::Result<std::fs::File> {
91 let file = open_no_follow(std::fs::OpenOptions::new().read(true), path)?;
92 if file.metadata()?.is_file() {
93 Ok(file)
94 } else {
95 Err(std::io::Error::new(
96 std::io::ErrorKind::InvalidInput,
97 "not a regular file",
98 ))
99 }
100}
101
102pub(crate) fn path_from_line(bytes: &[u8]) -> std::path::PathBuf {
105 let path = if CRLF_LINE_ENDINGS {
106 bytes
107 .strip_suffix(b"\r\n")
108 .or_else(|| bytes.strip_suffix(b"\n"))
109 .unwrap_or(bytes)
110 } else {
111 bytes.strip_suffix(b"\n").unwrap_or(bytes)
112 };
113 path_from_bytes(path)
114}
115
116pub(crate) fn state_directory() -> Option<std::path::PathBuf> {
118 resolve_state_directory(
119 environment::var("ARTIFACTIZE_STATE_HOME").map(Into::into),
120 STATE_VARIABLES
121 .iter()
122 .find_map(|name| environment::var(name).filter(|v| !v.is_empty()))
123 .map(Into::into),
124 home_directory(),
125 )
126}
127
128fn resolve_state_directory(
129 artifactize: Option<std::path::PathBuf>,
130 state: Option<std::path::PathBuf>,
131 home: Option<std::path::PathBuf>,
132) -> Option<std::path::PathBuf> {
133 artifactize
134 .filter(|p| !p.as_os_str().is_empty())
135 .or_else(|| {
136 state
137 .filter(|p| !p.as_os_str().is_empty())
138 .map(|p| p.join("artifactize"))
139 })
140 .or_else(|| {
141 home.filter(|p| !p.as_os_str().is_empty())
142 .map(|p| p.join(".local/state/artifactize"))
143 })
144}
145
146pub(crate) fn user_name() -> Option<String> {
148 USER_VARIABLES
149 .iter()
150 .find_map(|name| environment::var_text(name).filter(|value| !value.is_empty()))
151}
152
153pub(crate) fn fingerprint_line_ending(stdout: &[u8]) -> (&[u8], &'static str) {
155 if CRLF_LINE_ENDINGS {
156 (
157 stdout
158 .strip_suffix(b"\r\n")
159 .or_else(|| stdout.strip_suffix(b"\n"))
160 .unwrap_or(stdout),
161 "LF or CRLF",
162 )
163 } else {
164 (stdout.strip_suffix(b"\n").unwrap_or(stdout), "LF")
165 }
166}
167
168pub(crate) fn path_kind(path: &std::path::Path) -> std::io::Result<FileKind> {
170 match (path.parent(), path.file_name()) {
171 (Some(parent), Some(name)) => entry_kind(&open_directory(parent)?, name),
172 _ => open_directory(path).map(|_| FileKind::Directory),
173 }
174}
175
176pub(crate) fn serialize_paths<S: serde::Serializer>(
178 paths: &[std::path::PathBuf],
179 serializer: S,
180) -> Result<S::Ok, S::Error> {
181 use serde::Serialize;
182 paths
183 .iter()
184 .map(|path| path_text(path))
185 .collect::<Vec<_>>()
186 .serialize(serializer)
187}
188
189#[cfg(test)]
190mod state_directory_tests {
191 use super::*;
192 use std::path::PathBuf;
193
194 #[test]
195 fn state_home_precedence_and_empty_values() {
196 let artifactize = PathBuf::from("custom/state");
197 let state = PathBuf::from("state");
198 let home = PathBuf::from("reviewer");
199 assert_eq!(
200 resolve_state_directory(
201 Some(artifactize.clone()),
202 Some(state.clone()),
203 Some(home.clone())
204 ),
205 Some(artifactize)
206 );
207 assert_eq!(
208 resolve_state_directory(None, Some(state.clone()), Some(home.clone())),
209 Some(state.join("artifactize"))
210 );
211 assert_eq!(
212 resolve_state_directory(None, None, Some(home.clone())),
213 Some(home.join(".local/state/artifactize"))
214 );
215 let empty = Some(PathBuf::new());
216 assert_eq!(
217 resolve_state_directory(empty.clone(), empty.clone(), Some(home.clone())),
218 Some(home.join(".local/state/artifactize"))
219 );
220 assert_eq!(
221 resolve_state_directory(empty.clone(), empty.clone(), empty),
222 None
223 );
224 assert_eq!(resolve_state_directory(None, None, None), None);
225 }
226}
227
228mod console;
229pub use console::pause_after_help;
230
231mod interaction;
232pub(crate) use interaction::{is_within, label, paths_equal, private_anonymous_file};
233
234pub(crate) fn entry_exists(path: &std::path::Path) -> std::io::Result<bool> {
236 let Some(parent) = path.parent() else {
237 return Ok(false);
238 };
239 let directory = match open_directory(parent) {
240 Ok(directory) => directory,
241 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
242 Err(error) => return Err(error),
243 };
244 let Some(name) = path.file_name() else {
245 return Ok(false);
246 };
247 match entry_kind(&directory, name) {
248 Ok(_) => Ok(true),
249 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
250 Err(error) => Err(error),
251 }
252}
253
254pub(crate) fn marker_metadata(path: &std::path::Path) -> std::io::Result<std::fs::Metadata> {
259 path.symlink_metadata()
260}