1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Filesystem utility helpers for storage operations.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
use std::path::{Path, PathBuf};
use prikk_error::{PrikkError, Result};
mod anchored;
mod contract;
// DC-97 gated this whole module off Windows because the failpoint mechanism itself had no Windows
// implementation. DC-98 built and wired that mechanism -- re-evaluated per its own criterion 5, and
// the gate stays Unix-only for what remains here, but the reason is per-test, not per-file (an
// earlier version of this comment claimed "every one of these 18," found false for one test by
// review: a per-file grep is not a per-test count). 17 of 18 exercise `CreatedDirectoryParentSync`/
// `ObservedDirectoryParentSync`/`RequiredDirectorySync`/`MutableParentSync`, the directory-entry-sync
// points DC-98's classification (rows #10-#14) found have no Windows operation to inject at at all
// (no `FlushFileBuffers` contract for a directory handle) -- not a missing mechanism, a missing
// operation. The 18th, `sync_matrix::object_write_sync_failure_retains_and_classifies`, uses only
// `RequiredFileSync` (row #6, wired on Windows) and its Windows ordinals are now established by
// observation (`anchored.rs`'s own `fail_after_for_test` re-export doc comment) -- it stays here
// unmoved, but has its own Windows twin,
// `windows::tests::object_write_sync_failure_retains_and_classifies_windows`, rather than this
// module being restructured to free one test from three files' worth of shared per-file `use`
// statements. Nothing in this module uses a unix-only OS facility directly; a future caller-level
// test exercising only the points DC-98 did wire (`RequiredOpen`, `DirectoryCreate`,
// `MutableFileSync`, `MutableRename`, `RequiredFileSync`, `AppendWrite`, `Truncate`, `Unlink`) can be
// Windows-portable the same way.
#[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
mod caller_tests;
// `tests` (and its `conformance`/`directory` submodules) is genuinely mixed: some tests use
// unix-only facilities (FIFOs, symlinks) or the same failpoint mechanism above, and are individually
// gated inline where that's true; the rest -- including the whole `conformance` suite's Windows
// wrappers -- compile and run on Windows too.
#[cfg(all(
test,
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
mod tests;
pub(crate) use anchored::{
EntryKind, MutationRoot, RootFileStat, append_file_required, create_new_file_required,
ensure_directory_required, inspect_entry, list_directory, read_file_if_exists,
read_file_required, remove_file_cleanup_best_effort, remove_worktree_file_required,
set_regular_file_mode_required, stat_file_state_if_exists, sync_directory_required,
truncate_existing_file_required, truncate_file_empty_required, write_file_atomically,
write_worktree_file_atomically,
};
#[cfg(all(test, target_os = "linux"))]
pub(crate) use anchored::LinuxDurability;
#[cfg(all(test, target_os = "macos"))]
pub(crate) use anchored::MacosDurability;
// DC-97: conformance.rs's own architecture -- one shared `assert_*` body, a thin per-platform
// `#[test]` wrapper naming a concrete type -- is what a new platform plugs into. Windows is that
// platform now.
#[cfg(all(test, target_os = "windows"))]
pub(crate) use anchored::WindowsDurability;
// DC-82: visible in test builds regardless of platform (`none`'s own gate), but only re-exported
// here where `fsutil::tests` — the only consumer — actually compiles. Still Linux/macOS-only even
// though `fsutil::tests` itself now also compiles on Windows (DC-97): `NoDurability` itself has no
// Windows arm in test builds either (`anchored.rs`'s own gate excludes it there, since Windows has a
// real `WindowsDurability` now) -- the one test that uses it stays inline-gated to match.
#[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
pub(crate) use anchored::NoDurability;
// `remove_file_required` itself carries no platform gate at its own definition (it dispatches
// through `ACTIVE_DURABILITY`, which resolves per-platform internally). Unconditional since RFC 102
// Stage 6 Step 2's `unlock.rs` (design-v1.md §15.7 decision 3) is a genuine production caller, not
// just tests -- `prikk unlock` needs a real `Result`, not `remove_file_cleanup_best_effort`'s
// swallowed-error shape, since an operator-initiated removal that silently failed would be worse than
// an error surfaced.
pub(crate) use anchored::remove_file_required;
// DC-97: needed on Windows now too -- conformance.rs's shared `assert_*` functions take
// `impl DurabilityContract`, and that includes the new Windows wrapper.
#[cfg(all(
test,
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub(crate) use contract::DurabilityContract;
#[cfg(all(
test,
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub(crate) use anchored::{TestFailPoint, fail_once_for_test};
// DC-98: see `anchored.rs`'s own re-export of this for the established Windows ordinals it exists
// to carry.
#[cfg(all(
test,
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub(crate) use anchored::fail_after_for_test;
#[cfg(all(
test,
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
pub(crate) use anchored::set_directory_create_barrier_for_test;
#[cfg(all(test, target_os = "windows"))]
pub(crate) use anchored::set_anchor_verification_barrier_for_test;
/// Return a process-unique temporary path next to the destination.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn temporary_path(path: &Path) -> Result<PathBuf> {
let Some(file_name) = path.file_name() else {
return Err(PrikkError::Io(
"temporary path destination has no file name".to_string(),
));
};
let mut random = [0_u8; 16];
getrandom::fill(&mut random)
.map_err(|error| PrikkError::Io(format!("temporary path randomness failed: {error}")))?;
let mut name = file_name.to_os_string();
name.push(format!(
".tmp.{}.{:032x}",
std::process::id(),
u128::from_le_bytes(random)
));
Ok(path.with_file_name(name))
}
/// Convert a usize length to u16.
pub(crate) fn len_to_u16(len: usize) -> Result<u16> {
u16::try_from(len).map_err(|_| PrikkError::MalformedData("length exceeds u16".to_string()))
}
/// Convert a usize length to u32.
pub(crate) fn len_to_u32(len: usize) -> Result<u32> {
u32::try_from(len).map_err(|_| PrikkError::MalformedData("length exceeds u32".to_string()))
}
/// Convert a usize length to u64.
pub(crate) fn len_to_u64(len: usize) -> Result<u64> {
u64::try_from(len).map_err(|_| PrikkError::MalformedData("length exceeds u64".to_string()))
}