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.
12pub fn replace_file(from: &Path, to: &Path) -> std::io::Result<()> {
13    #[cfg(windows)]
14    {
15        use std::os::windows::ffi::OsStrExt;
16
17        const MOVEFILE_REPLACE_EXISTING: u32 = 0x1;
18        const MOVEFILE_WRITE_THROUGH: u32 = 0x8;
19        unsafe extern "system" {
20            fn MoveFileExW(
21                lp_existing_file_name: *const u16,
22                lp_new_file_name: *const u16,
23                dw_flags: u32,
24            ) -> i32;
25        }
26        fn wide(path: &Path) -> Vec<u16> {
27            path.as_os_str()
28                .encode_wide()
29                .chain(std::iter::once(0))
30                .collect()
31        }
32        let from_w = wide(from);
33        let to_w = wide(to);
34        let ok = unsafe {
35            MoveFileExW(
36                from_w.as_ptr(),
37                to_w.as_ptr(),
38                MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
39            )
40        };
41        if ok == 0 {
42            Err(std::io::Error::last_os_error())
43        } else {
44            Ok(())
45        }
46    }
47    #[cfg(not(windows))]
48    std::fs::rename(from, to)
49}
50
51/// Fsyncs the directory holding `path`, so a rename into it survives a crash.
52///
53/// A path with no parent is a no-op rather than an error.
54pub fn sync_parent_directory(path: &Path) -> std::io::Result<()> {
55    let Some(parent) = path.parent() else {
56        return Ok(());
57    };
58    open_directory_for_sync(parent).and_then(|directory| directory.sync_all())
59}
60
61fn open_directory_for_sync(path: &Path) -> std::io::Result<File> {
62    #[cfg(windows)]
63    {
64        use std::fs::OpenOptions;
65        use std::os::windows::fs::OpenOptionsExt;
66        const GENERIC_READ: u32 = 0x8000_0000;
67        const GENERIC_WRITE: u32 = 0x4000_0000;
68        const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
69        OpenOptions::new()
70            .access_mode(GENERIC_READ | GENERIC_WRITE)
71            .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
72            .open(path)
73    }
74    #[cfg(not(windows))]
75    File::open(path)
76}