Skip to main content

rightkit_fs/
lib.rs

1//! Safe filesystem state for Right Suite apps.
2//!
3//! - [`atomic`]: temp + `fsync` + rename replacement, streaming writer, crash-orphan sweep.
4//!   [`atomic_write`] is the one-call entry point.
5//! - [`lock`]: OS advisory cross-process locks with owner/stale reporting. Two primitives:
6//!   [`FileLock::acquire`] / [`FileLock::try_acquire`] take the exclusive lock, and
7//!   [`FileLock::acquire_shared`] takes a shared (reader) lock.
8//! - [`digest`] (feature `digest`): SHA-256 of bytes or files, and lowercase hex.
9//! - [`private`]: owner-only files and directories (0600/0700, Windows DACL).
10//! - [`state`]: [`StateDir`], a descriptor-pinned state directory (`O_NOFOLLOW`, fd-relative
11//!   `*at` operations) that a swapped or symlinked path cannot redirect.
12//! - [`identity`] (feature `json`): stable file identity across renames and atomic replacement.
13//! - [`clone`]: macOS `clonefile(2)` with `CLONE_NOFOLLOW`; never falls back to a byte copy.
14//! - [`swap`]: macOS `renamex_np(RENAME_SWAP)`; atomically exchanges two paths ([`swap_paths`]).
15
16#[cfg(feature = "fs")]
17pub mod atomic;
18#[cfg(feature = "fs")]
19pub mod clone;
20#[cfg(feature = "digest")]
21pub mod digest;
22#[cfg(feature = "fs")]
23pub mod error;
24#[cfg(feature = "json")]
25pub mod identity;
26#[cfg(feature = "lock")]
27pub mod lock;
28#[cfg(feature = "fs")]
29pub mod netfs;
30#[cfg(feature = "fs")]
31pub mod private;
32#[cfg(feature = "fs")]
33pub mod state;
34#[cfg(feature = "fs")]
35pub mod swap;
36
37#[cfg(feature = "fs")]
38pub use atomic::{
39    atomic_write, atomic_write_private, atomic_write_with, durable_sync, sweep_stale_temps,
40    AtomicOptions, AtomicWriter,
41};
42#[cfg(feature = "fs")]
43pub use clone::{clone_file_nofollow, CloneError};
44#[cfg(feature = "fs")]
45pub use error::{FsError, Result};
46#[cfg(feature = "json")]
47pub use identity::{FileKey, IdentityStore, Resolution, StableId, Tracked};
48#[cfg(feature = "lock")]
49pub use lock::{with_lock, FileLock, LockOptions, LockOwner};
50#[cfg(feature = "fs")]
51pub use netfs::is_network_filesystem;
52#[cfg(feature = "fs")]
53pub use private::{
54    create_private_new, ensure_private_dir, restrict_owner_only, sweep_owner_only,
55    write_private_file, write_private_text,
56};
57#[cfg(feature = "fs")]
58pub use state::{EntryKind, EntryStat, StateDir};
59#[cfg(feature = "fs")]
60pub use swap::{is_swap_unsupported, swap_paths};
61
62/// Whether process `pid` currently exists.
63#[cfg(feature = "fs")]
64pub fn process_alive(pid: u32) -> bool {
65    #[cfg(unix)]
66    {
67        // SAFETY: signal 0 performs only the existence/permission check.
68        let rc = unsafe { libc::kill(pid as libc::pid_t, 0) };
69        rc == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
70    }
71    #[cfg(windows)]
72    {
73        #[link(name = "kernel32")]
74        unsafe extern "system" {
75            fn OpenProcess(access: u32, inherit: i32, pid: u32) -> *mut std::ffi::c_void;
76            fn GetExitCodeProcess(handle: *mut std::ffi::c_void, code: *mut u32) -> i32;
77            fn CloseHandle(handle: *mut std::ffi::c_void) -> i32;
78        }
79        const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
80        const STILL_ACTIVE: u32 = 259;
81        unsafe {
82            let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
83            if handle.is_null() {
84                return false;
85            }
86            let mut code = 0u32;
87            let ok = GetExitCodeProcess(handle, &mut code);
88            CloseHandle(handle);
89            ok != 0 && code == STILL_ACTIVE
90        }
91    }
92    #[cfg(not(any(unix, windows)))]
93    {
94        let _ = pid;
95        true
96    }
97}