Skip to content

Commit 2ddcc0a

Browse files
authored
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
1 parent 2808186 commit 2ddcc0a

3 files changed

Lines changed: 22 additions & 5 deletions

File tree

crates/paths/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ chrono = { workspace = true, features = ["now"] }
1212
fs2.workspace = true
1313
itoa.workspace = true
1414
serde.workspace = true
15+
tempfile.workspace = true
1516
thiserror.workspace = true
1617

1718
[target.'cfg(windows)'.dependencies]
@@ -21,8 +22,5 @@ junction.workspace = true
2122
[target.'cfg(not(windows))'.dependencies]
2223
xdg.workspace = true
2324

24-
[dev-dependencies]
25-
tempfile.workspace = true
26-
2725
[lints]
2826
workspace = true

crates/paths/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ mod utils;
162162

163163
#[doc(hidden)]
164164
pub use serde as __serde;
165+
#[doc(hidden)]
166+
pub use tempfile as __tempfile;
165167

166168
/// Implemented for path types. Use `from_path_unchecked()` to construct a strongly-typed
167169
/// path directly from a `PathBuf`.

crates/paths/src/utils.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,25 @@ macro_rules! path_type {
104104
}
105105

106106
pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> {
107-
self.create_parent()?;
108-
std::fs::write(self, contents)
107+
use std::io::Write as _;
108+
109+
let path = &self.0;
110+
let parent = path.parent().ok_or_else(||
111+
std::io::Error::new(
112+
std::io::ErrorKind::InvalidInput,
113+
format!("cannot replace {} without enclosing directory", path.display()))
114+
)?;
115+
std::fs::create_dir_all(&parent)?;
116+
117+
let mut tmp = $crate::__tempfile::NamedTempFile::new_in(parent)?;
118+
tmp.write_all(contents.as_ref())?;
119+
tmp.as_file().sync_all()?;
120+
tmp.persist(&path)?;
121+
// On Windows, syncing the directory is not necessary and doesn't even work.
122+
#[cfg(not(target_os = "windows"))]
123+
std::fs::File::open(parent)?.sync_all()?;
124+
125+
Ok(())
109126
}
110127

111128
/// Opens a file at this path with the given options, ensuring its parent directory exists.

0 commit comments

Comments
 (0)