summaryrefslogtreecommitdiffhomepage
path: root/mullvad-fs
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2023-03-27 15:34:46 +0200
committerDavid Lönnhager <david.l@mullvad.net>2023-03-29 15:50:34 +0200
commitd630325c55920cb4c294812e40221fea7e6d7510 (patch)
treeed9460a6213e9402d7a719b8a73c43af3175dfab /mullvad-fs
parent2616889e0ab64afb98bf67056b4e5c977608b660 (diff)
downloadmullvadvpn-d630325c55920cb4c294812e40221fea7e6d7510.tar.xz
mullvadvpn-d630325c55920cb4c294812e40221fea7e6d7510.zip
Remove getters and setters from SettingsPersister
Diffstat (limited to 'mullvad-fs')
-rw-r--r--mullvad-fs/Cargo.toml15
-rw-r--r--mullvad-fs/src/lib.rs73
2 files changed, 88 insertions, 0 deletions
diff --git a/mullvad-fs/Cargo.toml b/mullvad-fs/Cargo.toml
new file mode 100644
index 0000000000..b2ae4176e0
--- /dev/null
+++ b/mullvad-fs/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "mullvad-fs"
+version = "0.0.0"
+authors = ["Mullvad VPN"]
+description = "File utility library"
+license = "GPL-3.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+log = "0.4"
+tokio = { version = "1.8", features = ["fs"] }
+uuid = { version = "0.8", features = ["v4"] }
+
+talpid-types = { path = "../talpid-types" }
diff --git a/mullvad-fs/src/lib.rs b/mullvad-fs/src/lib.rs
new file mode 100644
index 0000000000..22c6a80588
--- /dev/null
+++ b/mullvad-fs/src/lib.rs
@@ -0,0 +1,73 @@
+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<P: Into<PathBuf>>(target_path: P) -> io::Result<Self> {
+ let target_path = target_path.into();
+ 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()
+ }
+}