Patchwork D11836: rust: Add Vfs::write_atomic

login
register
mail settings
Submitter phabricator
Date Dec. 2, 2021, 4:49 p.m.
Message ID <differential-rev-PHID-DREV-q7lrr2vir7plefvu6fx7-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/50161/
State Superseded
Headers show

Comments

phabricator - Dec. 2, 2021, 4:49 p.m.
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This method writes to a temporary file then renames in place

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D11836

AFFECTED FILES
  rust/hg-core/src/errors.rs
  rust/hg-core/src/vfs.rs

CHANGE DETAILS




To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs
--- a/rust/hg-core/src/vfs.rs
+++ b/rust/hg-core/src/vfs.rs
@@ -1,6 +1,6 @@ 
 use crate::errors::{HgError, IoErrorContext, IoResultExt};
 use memmap2::{Mmap, MmapOptions};
-use std::io::ErrorKind;
+use std::io::{ErrorKind, Write};
 use std::path::{Path, PathBuf};
 
 /// Filesystem access abstraction for the contents of a given "base" diretory
@@ -105,7 +105,28 @@ 
     ) -> Result<(), HgError> {
         let link_path = self.join(relative_link_path);
         std::os::unix::fs::symlink(target_path, &link_path)
-            .with_context(|| IoErrorContext::WritingFile(link_path))
+            .when_writing_file(&link_path)
+    }
+
+    /// Write `contents` into a temporary file, then rename to `relative_path`.
+    /// This makes writing to a file "atomic": a reader opening that path will
+    /// see either the previous contents of the file or the complete new
+    /// content, never a partial write.
+    pub fn atomic_write(
+        &self,
+        relative_path: impl AsRef<Path>,
+        contents: &[u8],
+    ) -> Result<(), HgError> {
+        let mut tmp = tempfile::NamedTempFile::new_in(self.base)
+            .when_writing_file(self.base)?;
+        tmp.write_all(contents)
+            .and_then(|()| tmp.flush())
+            .when_writing_file(tmp.path())?;
+        let path = self.join(relative_path);
+        tmp.persist(&path)
+            .map_err(|e| e.error)
+            .when_writing_file(&path)?;
+        Ok(())
     }
 }
 
diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs
--- a/rust/hg-core/src/errors.rs
+++ b/rust/hg-core/src/errors.rs
@@ -151,6 +151,8 @@ 
     /// Converts a `Result` with `std::io::Error` into one with `HgError`.
     fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
 
+    fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError>;
+
     fn with_context(
         self,
         context: impl FnOnce() -> IoErrorContext,
@@ -162,6 +164,10 @@ 
         self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
     }
 
+    fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError> {
+        self.with_context(|| IoErrorContext::WritingFile(path.to_owned()))
+    }
+
     fn with_context(
         self,
         context: impl FnOnce() -> IoErrorContext,