diff options
| author | David Lönnhager <david.l@mullvad.net> | 2022-06-16 15:33:08 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2022-06-20 10:23:33 +0200 |
| commit | 6b1087648daff71fdb040f770d4dd93d0c6de22a (patch) | |
| tree | e8badf3a2220905ce59b58499153e4aade366229 /mullvad-api/src/fs.rs | |
| parent | 6909f3b8adf5afd69dcfe8e01ecf40d99c301249 (diff) | |
| download | mullvadvpn-6b1087648daff71fdb040f770d4dd93d0c6de22a.tar.xz mullvadvpn-6b1087648daff71fdb040f770d4dd93d0c6de22a.zip | |
Add abstraction for atomic file I/O
Diffstat (limited to 'mullvad-api/src/fs.rs')
| -rw-r--r-- | mullvad-api/src/fs.rs | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/mullvad-api/src/fs.rs b/mullvad-api/src/fs.rs new file mode 100644 index 0000000000..d643563c9a --- /dev/null +++ b/mullvad-api/src/fs.rs @@ -0,0 +1,72 @@ +use std::{ + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; +use talpid_types::ErrorExt; +use tokio::{fs, io}; + +/// Stores content in a temporary file before moving it to the +/// final destination, ensuring that consumers of the file never +/// end up with partial content. Must be moved with `finalize`. +pub struct AtomicFile { + file: Option<fs::File>, + temp_path: PathBuf, + target_path: PathBuf, +} + +impl AtomicFile { + pub async fn new(target_path: PathBuf) -> io::Result<Self> { + let temp_path = target_path.with_file_name(uuid::Uuid::new_v4().to_string()); + Ok(Self { + file: Some(fs::File::create(&temp_path).await?), + temp_path, + target_path, + }) + } + + /// Flushes and moves the file to `self.target_path`, replacing it if it exists. + pub async fn finalize(mut self) -> io::Result<()> { + let result = async { + let file = self.file.take().unwrap(); + file.sync_all().await?; + let std_file = file.into_std().await; + let _ = tokio::task::spawn_blocking(move || drop(std_file)).await; + fs::rename(&self.temp_path, &self.target_path).await + } + .await; + if result.is_err() { + let _ = tokio::task::spawn_blocking(move || try_remove_file(&self.temp_path)).await; + } + result + } +} + +impl Drop for AtomicFile { + fn drop(&mut self) { + if self.file.is_some() { + log::error!("{} was not finalized", self.target_path.display()); + try_remove_file(&self.temp_path); + } + } +} + +fn try_remove_file(temp_path: &Path) { + if let Err(error) = std::fs::remove_file(temp_path) { + let msg = format!("Failed to delete temp file: {}", temp_path.display()); + log::warn!("{}", error.display_chain_with_msg(&msg)); + } +} + +impl Deref for AtomicFile { + type Target = fs::File; + + fn deref(&self) -> &Self::Target { + self.file.as_ref().unwrap() + } +} + +impl DerefMut for AtomicFile { + fn deref_mut(&mut self) -> &mut Self::Target { + self.file.as_mut().unwrap() + } +} |
