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