From fb6f429169fdc36e8a1512284a88a19622f91e50 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Fri, 24 Apr 2026 10:58:46 +0200 Subject: [PATCH 1/3] paths: fsync after writing to a file As reported in #4886, `metadata.toml` can get get lost if the server crashes at an unfortunate point in time. Do a textbook fsync to mitigate that. While not necessarily preventing the server from starting, the same is advisable for any of the files, so the logic is put in the macro- generated `write` method of typed paths. --- crates/paths/src/utils.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/paths/src/utils.rs b/crates/paths/src/utils.rs index a03a9b87d9c..b5870e26e9f 100644 --- a/crates/paths/src/utils.rs +++ b/crates/paths/src/utils.rs @@ -104,8 +104,21 @@ macro_rules! path_type { } pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> { + use std::io::Write as _; + self.create_parent()?; - std::fs::write(self, contents) + let mut file = std::fs::File::create(self)?; + file.write_all(contents.as_ref())?; + file.sync_all()?; + + // In case the file got created, we also need to fsync the + // directory, so that the directory entry becomes durable. + if let Some(parent) = self.0.parent() + && parent != std::path::Path::new("") { + std::fs::File::open(parent)?.sync_all()?; + } + + Ok(()) } /// Opens a file at this path with the given options, ensuring its parent directory exists. From 4ce5a077bde9350af99ae0ac58c0afef62a81f76 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Tue, 28 Apr 2026 12:56:52 +0200 Subject: [PATCH 2/3] Make path type `write` replace the file atomically --- crates/paths/Cargo.toml | 4 +--- crates/paths/src/utils.rs | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml index c293807a35e..49dd1bbdece 100644 --- a/crates/paths/Cargo.toml +++ b/crates/paths/Cargo.toml @@ -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 diff --git a/crates/paths/src/utils.rs b/crates/paths/src/utils.rs index b5870e26e9f..326ba795775 100644 --- a/crates/paths/src/utils.rs +++ b/crates/paths/src/utils.rs @@ -106,17 +106,19 @@ macro_rules! path_type { pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> { use std::io::Write as _; - self.create_parent()?; - let mut file = std::fs::File::create(self)?; - file.write_all(contents.as_ref())?; - file.sync_all()?; + let path = self.0.canonicalize()?; + 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(&path)?; - // In case the file got created, we also need to fsync the - // directory, so that the directory entry becomes durable. - if let Some(parent) = self.0.parent() - && parent != std::path::Path::new("") { - std::fs::File::open(parent)?.sync_all()?; - } + let mut tmp = tempfile::NamedTempFile::new_in(parent)?; + tmp.write_all(contents.as_ref())?; + tmp.as_file().sync_all()?; + tmp.persist(&path)?; + std::fs::File::open(parent)?.sync_all()?; Ok(()) } From 9e87feac2d5bf2f29872ee198f74ad13774c7f02 Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Tue, 28 Apr 2026 14:53:13 +0200 Subject: [PATCH 3/3] fixup! Make path type `write` replace the file atomically --- crates/paths/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/paths/src/utils.rs b/crates/paths/src/utils.rs index 326ba795775..a8e629f1cf7 100644 --- a/crates/paths/src/utils.rs +++ b/crates/paths/src/utils.rs @@ -106,13 +106,13 @@ macro_rules! path_type { pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> { use std::io::Write as _; - let path = self.0.canonicalize()?; + 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(&path)?; + std::fs::create_dir_all(&parent)?; let mut tmp = tempfile::NamedTempFile::new_in(parent)?; tmp.write_all(contents.as_ref())?;