1#[cfg(all(feature = "testing", not(debug_assertions)))]
45compile_error!(
46 "rightkit-fs feature `testing` injects faults into atomic writes and is for debug and test builds only; \
47 remove it from release builds"
48);
49
50#[cfg(all(feature = "fs", unix))]
51pub(crate) mod acl;
52#[cfg(feature = "app-dirs")]
53pub mod app_dirs;
54#[cfg(feature = "fs")]
55pub mod append;
56#[cfg(feature = "fs")]
57pub mod atomic;
58#[cfg(feature = "fs")]
59pub mod clone;
60pub mod contain;
61#[cfg(any(feature = "digest", feature = "blake3"))]
62pub mod digest;
63#[cfg(feature = "fs")]
64pub mod error;
65#[cfg(feature = "fs")]
66pub mod file_key;
67#[cfg(feature = "fs")]
68pub mod handle;
69#[cfg(feature = "json")]
70pub mod identity;
71#[cfg(feature = "fs")]
72pub mod lease;
73#[cfg(feature = "lock")]
74pub mod lock;
75#[cfg(feature = "fs")]
76pub mod migrate;
77pub mod names;
78#[cfg(feature = "fs")]
79pub mod netfs;
80pub mod plain;
81#[cfg(feature = "fs")]
82pub mod private;
83#[cfg(feature = "fs")]
84pub mod publish_dir;
85#[cfg(feature = "fs")]
86pub mod replaced;
87#[cfg(feature = "fs")]
88pub mod space;
89#[cfg(feature = "fs")]
90pub mod state;
91#[cfg(feature = "fs")]
92pub mod swap;
93#[cfg(feature = "test-mode")]
94pub mod test_mode;
95#[cfg(feature = "transaction")]
96pub mod transaction;
97#[cfg(feature = "fs")]
98pub mod trash;
99#[cfg(feature = "fs")]
100pub mod tree;
101#[cfg(all(feature = "fs", any(feature = "digest", feature = "blake3")))]
102pub mod verified;
103#[cfg(all(feature = "fs", windows))]
104mod windows_temp_sweep;
105#[cfg(feature = "lock")]
106pub mod write_locks;
107pub mod zip;
108
109#[cfg(feature = "app-dirs")]
110pub use app_dirs::{
111 account_home, AppDirs, AppDirsError, AppId, Options as AppDirsOptions, Os as AppDirsOs,
112};
113#[cfg(feature = "fs")]
114pub use append::{append_record, open_private_append, AppendOptions};
115#[cfg(all(feature = "fs", feature = "digest"))]
116pub use atomic::atomic_write_with_digest;
117#[cfg(feature = "fs")]
118pub use atomic::{
119 atomic_write, atomic_write_private, atomic_write_reporting, atomic_write_with,
120 atomic_write_with_access_denied_retry, atomic_write_with_options, durable_sync,
121 durable_sync_with, publication_durability, publish_file, publish_file_first_free,
122 publish_file_with, publish_file_with_access_denied_retry, rename_exclusive,
123 rename_exclusive_reporting, sweep_stale_temps, sweep_stale_temps_report, try_sync_dir,
124 AccessDeniedRetry, AtomicOptions, AtomicWriteOptions, AtomicWriter, ExclusiveMethod, Privacy,
125 PublicationDurability, ReplaceMode, RetryPolicy, SweepReport, SyncLevel, SyncPolicy,
126 TempWriter, WriteReport,
127};
128#[cfg(feature = "fs")]
129pub use clone::{
130 clone_file_nofollow, clone_or_link_or_copy_nofollow, clone_or_link_or_copy_nofollow_with,
131 CloneError, CloneOptions, CloneOutcome,
132};
133pub use contain::{contain, contain_lenient, is_within, ContainError, Roots};
134#[cfg(feature = "fs")]
135pub use contain::{open_confined, ConfinedOptions};
136#[cfg(feature = "fs")]
137pub use error::{FsError, PostPublishStep, Result};
138#[cfg(feature = "fs")]
139pub use file_key::FileKey;
140#[cfg(feature = "fs")]
141pub use handle::{final_path, link_count, open_read_nofollow, ReadOptions};
142#[cfg(feature = "json")]
143pub use identity::{IdentityStore, Resolution, StableId, Tracked};
144#[cfg(feature = "fs")]
145pub use lease::{Acquire, Lease, LeaseInfo, LeaseOptions, Staleness};
146#[cfg(feature = "lock")]
147pub use lock::{probe_owner, with_lock, FileLock, LockKind, LockOptions, LockOwner};
148#[cfg(feature = "fs")]
149pub use migrate::{migrate_dir, MigrateMethod, Migrated};
150#[cfg(feature = "fs")]
151pub use names::{create_download_file, create_unique_file};
152pub use names::{
153 is_windows_reserved_name, numbered_candidates, sanitize_file_name, sanitize_file_name_with,
154 NameOptions,
155};
156#[cfg(feature = "fs")]
157pub use netfs::is_network_filesystem;
158pub use plain::{canonicalize_plain, plain_path};
159#[cfg(feature = "fs")]
160pub use private::{
161 create_private_new, ensure_private_dir, restrict_owner_only, sweep_owner_only,
162 write_private_executable, write_private_file, write_private_text,
163};
164#[cfg(feature = "fs")]
165pub use publish_dir::{publish_dir, DirPublishMethod, PublishedDir};
166#[cfg(feature = "fs")]
167pub use replaced::{open_replaced, read_replaced, read_to_string_replaced};
168#[cfg(feature = "fs")]
169pub use space::free_bytes;
170#[cfg(feature = "fs")]
171pub use state::{DirEntry, EntryKind, EntryStat, StateDir, StateOptions};
172#[cfg(feature = "fs")]
173pub use swap::{is_swap_unsupported, swap_paths};
174#[cfg(feature = "transaction")]
175pub use transaction::{
176 recover_transactions, recover_transactions_with, CommittedReport, ExpectedPrior,
177 FileTransaction, PreparedTransaction, Publication, RecoveredTransaction, RecoveryOutcome,
178 RecoveryReport, RecoveryRequiredReport, RetainedItem, RolledBackReport, TransactionEntry,
179 TransactionOptions, TransactionReport,
180};
181#[cfg(feature = "fs")]
182pub use trash::{move_to_trash, TrashEntry, TrashIdentity};
183#[cfg(feature = "fs")]
184pub use tree::{copy_tree, CopyOptions};
185#[cfg(all(feature = "fs", feature = "digest"))]
186pub use verified::{copy_verified, copy_verified_with_progress, VerifiedCopy};
187#[cfg(all(feature = "fs", any(feature = "digest", feature = "blake3")))]
188pub use verified::{copy_verified_with_digest, DigestAlgorithm, VerifiedDigestCopy};
189#[cfg(feature = "lock")]
190pub use write_locks::{KeyedWriteLocks, LockedAtomicWriter, LockedDestination, WriteLockOptions};
191pub use zip::{crc32, stored_zip_bytes, StoredZipWriter};
192
193#[cfg(feature = "fs")]
198#[derive(Clone, Copy, Debug, PartialEq, Eq)]
199pub(crate) enum ProcessLiveness {
200 Alive,
201 Dead,
202 Unknown,
203}
204
205#[cfg(feature = "fs")]
207pub(crate) fn process_liveness(pid: u32) -> ProcessLiveness {
208 #[cfg(unix)]
209 {
210 if pid == 0 || pid > libc::pid_t::MAX as u32 {
211 return ProcessLiveness::Unknown;
212 }
213 let rc = unsafe { libc::kill(pid as libc::pid_t, 0) };
215 if rc == 0 {
216 ProcessLiveness::Alive
217 } else if std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH) {
218 ProcessLiveness::Dead
219 } else {
220 ProcessLiveness::Unknown
221 }
222 }
223 #[cfg(windows)]
224 {
225 if pid == 0 {
226 return ProcessLiveness::Unknown;
227 }
228 const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
229 const STILL_ACTIVE: u32 = 259;
230 unsafe {
232 let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
233 if handle.is_null() {
234 if std::io::Error::last_os_error().raw_os_error() == Some(ERROR_INVALID_PARAMETER) {
237 return snapshot_liveness(pid);
238 }
239 return ProcessLiveness::Unknown;
240 }
241 let mut code = 0u32;
242 let ok = GetExitCodeProcess(handle, &mut code);
243 CloseHandle(handle);
244 if ok == 0 {
245 ProcessLiveness::Unknown
246 } else if code == STILL_ACTIVE {
247 ProcessLiveness::Alive
248 } else {
249 ProcessLiveness::Dead
250 }
251 }
252 }
253 #[cfg(not(any(unix, windows)))]
254 {
255 let _ = pid;
256 ProcessLiveness::Unknown
257 }
258}
259
260#[cfg(all(feature = "fs", windows))]
261const ERROR_INVALID_PARAMETER: i32 = 87;
262#[cfg(all(feature = "fs", windows))]
263const ERROR_NO_MORE_FILES: i32 = 18;
264#[cfg(all(feature = "fs", windows))]
265const PROCESS_SNAPSHOT_LIMIT: usize = 4096;
266
267#[cfg(all(feature = "fs", windows))]
268#[repr(C)]
269struct ProcessEntry32W {
270 size: u32,
271 usage: u32,
272 process_id: u32,
273 default_heap_id: usize,
274 module_id: u32,
275 threads: u32,
276 parent_process_id: u32,
277 priority_class: i32,
278 flags: u32,
279 executable: [u16; 260],
280}
281
282#[cfg(all(feature = "fs", windows))]
284fn snapshot_liveness_from_pids(pid: u32, pids: &[u32], complete: bool) -> ProcessLiveness {
285 if complete && pids.len() < PROCESS_SNAPSHOT_LIMIT && !pids.contains(&pid) {
286 ProcessLiveness::Dead
287 } else {
288 ProcessLiveness::Unknown
289 }
290}
291
292#[cfg(all(feature = "fs", windows))]
294fn snapshot_liveness(pid: u32) -> ProcessLiveness {
295 const TH32CS_SNAPPROCESS: u32 = 0x0000_0002;
296 let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
298 if snapshot as isize == -1 {
299 return ProcessLiveness::Unknown;
300 }
301 let mut pids = Vec::with_capacity(64);
302 let mut entry: ProcessEntry32W = unsafe { std::mem::zeroed() };
304 entry.size = std::mem::size_of::<ProcessEntry32W>() as u32;
305 if unsafe { Process32FirstW(snapshot, &mut entry) } == 0 {
307 unsafe { CloseHandle(snapshot) };
309 return ProcessLiveness::Unknown;
310 }
311 let complete = loop {
312 pids.push(entry.process_id);
313 if pids.len() >= PROCESS_SNAPSHOT_LIMIT {
314 break false;
315 }
316 entry.size = std::mem::size_of::<ProcessEntry32W>() as u32;
317 if unsafe { Process32NextW(snapshot, &mut entry) } == 0 {
319 break std::io::Error::last_os_error().raw_os_error() == Some(ERROR_NO_MORE_FILES);
320 }
321 };
322 unsafe { CloseHandle(snapshot) };
324 snapshot_liveness_from_pids(pid, &pids, complete)
325}
326
327#[cfg(all(feature = "fs", windows))]
328#[link(name = "kernel32")]
329unsafe extern "system" {
330 fn OpenProcess(access: u32, inherit: i32, pid: u32) -> *mut std::ffi::c_void;
331 fn GetExitCodeProcess(handle: *mut std::ffi::c_void, code: *mut u32) -> i32;
332 fn CloseHandle(handle: *mut std::ffi::c_void) -> i32;
333 fn CreateToolhelp32Snapshot(flags: u32, process_id: u32) -> *mut std::ffi::c_void;
334 fn Process32FirstW(snapshot: *mut std::ffi::c_void, entry: *mut ProcessEntry32W) -> i32;
335 fn Process32NextW(snapshot: *mut std::ffi::c_void, entry: *mut ProcessEntry32W) -> i32;
336}
337
338#[cfg(feature = "fs")]
343pub fn process_alive(pid: u32) -> bool {
344 !matches!(process_liveness(pid), ProcessLiveness::Dead)
345}
346
347#[cfg(all(test, feature = "fs"))]
348mod tests {
349 use super::{process_alive, process_liveness, ProcessLiveness};
350
351 #[test]
352 fn current_process_is_alive() {
353 assert_eq!(process_liveness(std::process::id()), ProcessLiveness::Alive);
354 assert!(process_alive(std::process::id()));
355 }
356
357 #[test]
358 fn invalid_pid_is_unknown_and_conservatively_alive() {
359 assert_eq!(process_liveness(0), ProcessLiveness::Unknown);
360 assert!(process_alive(0));
361 }
362
363 #[cfg(windows)]
364 #[test]
365 fn complete_snapshot_absence_confirms_death() {
366 assert_eq!(
367 super::snapshot_liveness_from_pids(42, &[1, 2, 3], true),
368 ProcessLiveness::Dead
369 );
370 }
371
372 #[cfg(windows)]
373 #[test]
374 fn snapshot_presence_is_unknown() {
375 assert_eq!(
376 super::snapshot_liveness_from_pids(42, &[1, 42, 3], true),
377 ProcessLiveness::Unknown
378 );
379 }
380
381 #[cfg(windows)]
382 #[test]
383 fn failed_snapshot_is_unknown() {
384 assert_eq!(
385 super::snapshot_liveness_from_pids(42, &[], false),
386 ProcessLiveness::Unknown
387 );
388 }
389
390 #[cfg(windows)]
391 #[test]
392 fn capped_snapshot_is_unknown() {
393 assert_eq!(
394 super::snapshot_liveness_from_pids(42, &[1; super::PROCESS_SNAPSHOT_LIMIT], true),
395 ProcessLiveness::Unknown
396 );
397 }
398}