summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-31 11:02:09 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-31 12:46:16 +0100
commit8dbdc97b7dafaab15dc877538e5ff48af007b46e (patch)
treec051cf7e6e71e1f1824804bc3ec1380f6c7d6877
parent9b9da3ebbd90d52cec84c17edb4d99a472fd9a61 (diff)
downloadmullvadvpn-8dbdc97b7dafaab15dc877538e5ff48af007b46e.tar.xz
mullvadvpn-8dbdc97b7dafaab15dc877538e5ff48af007b46e.zip
Allow fallible settings update functions
Implement `SettingsPersister.try_update`, which allow the caller to signal a failed settings update, hindering a bad settings update from being applied.
-rw-r--r--mullvad-daemon/src/settings/mod.rs79
1 files changed, 73 insertions, 6 deletions
diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs
index 47a2edc989..d411bce2b4 100644
--- a/mullvad-daemon/src/settings/mod.rs
+++ b/mullvad-daemon/src/settings/mod.rs
@@ -38,6 +38,9 @@ pub enum Error {
#[error(display = "Unable to write settings to {}", _0)]
WriteError(String, #[error(source)] io::Error),
+
+ #[error(display = "Failed to apply settings update")]
+ UpdateFailed(Box<dyn std::error::Error + Send + Sync>),
}
/// Converts an [Error] to a management interface status
@@ -50,7 +53,7 @@ impl From<Error> for mullvad_management_interface::Status {
Error::DeleteError(..) | Error::WriteError(..) | Error::ReadError(..) => {
Status::new(Code::FailedPrecondition, error.to_string())
}
- Error::SerializeError(..) | Error::ParseError(..) => {
+ Error::SerializeError(..) | Error::ParseError(..) | Error::UpdateFailed(..) => {
Status::new(Code::Internal, error.to_string())
}
}
@@ -224,18 +227,82 @@ impl SettingsPersister {
settings
}
- /// Edit the settings in a closure, and write the changes, if any, to disk.
+ /// Edit the settings in a closure and write the changes to disk.
+ ///
+ /// # On success
+ ///
+ /// Returns a boolean indicating whether any settings were changed.
+ ///
+ /// # On failure
+ ///
+ /// If the settings could not be written to disk, all changes are rolled
+ /// back, and an error is returned.
+ ///
+ /// # Note
///
- /// On success, the function returns a boolean indicating whether any settings were changed.
- /// If the settings could not be written to disk, all changes are rolled back, and an error is
- /// returned.
+ /// If no settings were changed, no I/O will be performed.
pub async fn update(
&mut self,
update_fn: impl FnOnce(&mut Settings),
) -> Result<MadeChanges, Error> {
+ self.try_update(|settings| -> Result<(), Error> {
+ update_fn(settings);
+ Ok(())
+ })
+ .await
+ }
+
+ /// Edit the settings in a closure, and write the changes to disk.
+ ///
+ /// # On success
+ ///
+ /// Returns a boolean indicating whether any settings were changed.
+ ///
+ /// # On failure
+ ///
+ /// `try_update` may fail in two scenarios
+ ///
+ /// ## The settings could not be written to disk
+ ///
+ /// In this case, all changes are rolled back and an error is returned.
+ ///
+ /// ## `update_fn` failed
+ ///
+ /// If `update_fn` were to fail the error will be propagated through the
+ /// [`Error::UpdateFailed`] error variant. Since the error will be boxed, it
+ /// has to be downcasted at runtime using [`Box::downcast`] in case you want
+ /// to inspect the error closer.
+ ///
+ /// ```ignore
+ /// #[derive(Debug, err_derive::Error)]
+ /// pub enum MyError {
+ /// #[error(display = "Failed for this reason: {:?}", _0)]
+ /// Failed(String),
+ /// }
+ ///
+ /// let settings = Settings::default_settings();
+ /// let err = settings.try_update(|settings| {
+ /// // Perform some update on the settings
+ /// settings.allow_lan = !settings.allow_lan;
+ /// // Fail the update procedure due to some error
+ /// Err(MyError::Failed("No particular reason".to_string()))
+ /// });
+ ///
+ /// matches!(err, Error::UpdateFailed(_)) ;
+ /// assert_eq!(settings, Settings::default_settings())
+ /// ```
+ pub async fn try_update<E>(
+ &mut self,
+ update_fn: impl FnOnce(&mut Settings) -> Result<(), E>,
+ ) -> Result<MadeChanges, Error>
+ where
+ E: std::error::Error + Send + Sync + 'static,
+ {
let mut new_settings = self.settings.clone();
- update_fn(&mut new_settings);
+ update_fn(&mut new_settings)
+ .map_err(Box::from)
+ .map_err(Error::UpdateFailed)?;
if self.settings == new_settings {
return Ok(false);