paths: fsync after writing to a file (#4892)

As reported in #4886, `metadata.toml` can get get lost if the server
crashes at an unfortunate point in time. To mitigate that, make
`path_type::write` replace the file atomically, and issue `fsync` on the
file and enclosing directory.


# Expected complexity level and risk

1
This commit is contained in:
Kim Altintop
2026-04-30 18:26:14 +02:00
committed by GitHub
parent 280818631d
commit 2ddcc0a1e7
3 changed files with 22 additions and 5 deletions
+1 -3
View File
@@ -12,6 +12,7 @@ chrono = { workspace = true, features = ["now"] }
fs2.workspace = true
itoa.workspace = true
serde.workspace = true
tempfile.workspace = true
thiserror.workspace = true
[target.'cfg(windows)'.dependencies]
@@ -21,8 +22,5 @@ junction.workspace = true
[target.'cfg(not(windows))'.dependencies]
xdg.workspace = true
[dev-dependencies]
tempfile.workspace = true
[lints]
workspace = true
+2
View File
@@ -162,6 +162,8 @@ mod utils;
#[doc(hidden)]
pub use serde as __serde;
#[doc(hidden)]
pub use tempfile as __tempfile;
/// Implemented for path types. Use `from_path_unchecked()` to construct a strongly-typed
/// path directly from a `PathBuf`.
+19 -2
View File
@@ -104,8 +104,25 @@ macro_rules! path_type {
}
pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> {
self.create_parent()?;
std::fs::write(self, contents)
use std::io::Write as _;
let path = &self.0;
let parent = path.parent().ok_or_else(||
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("cannot replace {} without enclosing directory", path.display()))
)?;
std::fs::create_dir_all(&parent)?;
let mut tmp = $crate::__tempfile::NamedTempFile::new_in(parent)?;
tmp.write_all(contents.as_ref())?;
tmp.as_file().sync_all()?;
tmp.persist(&path)?;
// On Windows, syncing the directory is not necessary and doesn't even work.
#[cfg(not(target_os = "windows"))]
std::fs::File::open(parent)?.sync_all()?;
Ok(())
}
/// Opens a file at this path with the given options, ensuring its parent directory exists.