Skip to main content

pb_mapper_core/
durable_file.rs

1//! Atomic replace and parent-directory durability.
2//!
3//! These are general file primitives, not credential ones — they lived in the
4//! auth persistence layer only because that is where the first caller was. Both
5//! report `io::Result` and leave it to the caller to map into its own error
6//! type.
7
8use std::fs::File;
9use std::path::Path;
10
11/// Replaces `to` with `from`, atomically where the platform allows it.
12///
13/// This is `std::fs::rename` everywhere. On Windows, std tries `MoveFileExW` and,
14/// when that is refused with `ERROR_ACCESS_DENIED` — as it is while any handle
15/// is open on `to` — retries through `FileRenameInfoEx` with POSIX semantics,
16/// which replace `to` regardless (Windows 10 1607 and later, on a file system
17/// that supports it). A bare `MoveFileExW` stops at the refusal.
18///
19/// The rename is not durable on its own on any platform: callers follow it
20/// with [`sync_parent_directory`]. They rename from the same directory, so the
21/// `MOVEFILE_WRITE_THROUGH` flag std does not pass would add nothing — it only
22/// flushes a move that crosses volumes.
23pub fn replace_file(from: &Path, to: &Path) -> std::io::Result<()> {
24    std::fs::rename(from, to)
25}
26
27/// Fsyncs the directory holding `path`, so a rename into it survives a crash.
28///
29/// A path with no parent is a no-op rather than an error.
30pub fn sync_parent_directory(path: &Path) -> std::io::Result<()> {
31    let Some(parent) = path.parent() else {
32        return Ok(());
33    };
34    open_directory_for_sync(parent).and_then(|directory| directory.sync_all())
35}
36
37fn open_directory_for_sync(path: &Path) -> std::io::Result<File> {
38    #[cfg(windows)]
39    {
40        use std::fs::OpenOptions;
41        use std::os::windows::fs::OpenOptionsExt;
42        const GENERIC_READ: u32 = 0x8000_0000;
43        const GENERIC_WRITE: u32 = 0x4000_0000;
44        const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
45        OpenOptions::new()
46            .access_mode(GENERIC_READ | GENERIC_WRITE)
47            .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
48            .open(path)
49    }
50    #[cfg(not(windows))]
51    File::open(path)
52}