[pbs-devel] [PATCH proxmox-backup] api: config: use guard for unmounting on failed datastore creation

Wolfgang Bumiller w.bumiller at proxmox.com
Thu Mar 20 12:57:12 CET 2025


On Thu, Mar 20, 2025 at 10:48:51AM +0100, Hannes Laimer wrote:
> Currently if any `?`/`bail!` happens between mounting and completing
> the creation process unmounting will be skipped. Adding this guard
> solves that problem and makes it easier to add things in the future
> without having to worry about a disk not being unmounted in case of a
> failed creation.
> 
> Signed-off-by: Hannes Laimer <h.laimer at proxmox.com>
> Reported-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
> ---
>  src/api2/config/datastore.rs | 86 ++++++++++++++++++++++--------------
>  1 file changed, 52 insertions(+), 34 deletions(-)
> 
> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
> index fe3260f6d..99c699814 100644
> --- a/src/api2/config/datastore.rs
> +++ b/src/api2/config/datastore.rs
> @@ -1,7 +1,7 @@
>  use std::path::{Path, PathBuf};
>  
>  use ::serde::{Deserialize, Serialize};
> -use anyhow::{bail, format_err, Error};
> +use anyhow::{bail, Error};
>  use hex::FromHex;
>  use serde_json::Value;
>  use tracing::warn;
> @@ -70,6 +70,33 @@ pub fn list_datastores(
>      Ok(list.into_iter().filter(filter_by_privs).collect())
>  }
>  
> +struct UnmountGuard {
> +    path: PathBuf,
> +    should_unmount: bool,

Instead of the extra `bool` it would be cheaper if we just make `path`
an `Option<PathBuf>`.

> +}
> +
> +impl UnmountGuard {
> +    fn new(path: PathBuf, should_unmount: bool) -> Self {
> +        UnmountGuard {
> +            path,
> +            should_unmount,
> +        }
> +    }
> +    fn disable(&mut self) {

This could take `self` by-move since we don't intend to re-enable it and
then we don't need the guard to be mutable down where it's used.

> +        self.should_unmount = false;
> +    }
> +}
> +
> +impl Drop for UnmountGuard {
> +    fn drop(&mut self) {
> +        if self.should_unmount {
> +            if let Err(e) = unmount_by_mountpoint(&self.path) {
> +                warn!("could not unmount device: {e}");
> +            }
> +        }
> +    }
> +}
> +
>  pub(crate) fn do_create_datastore(
>      _lock: BackupLockGuard,
>      mut config: SectionConfigData,
> @@ -87,59 +114,50 @@ pub(crate) fn do_create_datastore(
>          param_bail!("path", err);
>      }
>  
> -    let need_unmount = datastore.backing_device.is_some();
> -    if need_unmount {
> -        do_mount_device(datastore.clone())?;
> -    };
> -
>      let tuning: DatastoreTuning = serde_json::from_value(
>          DatastoreTuning::API_SCHEMA
>              .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
>      )?;
>  
> -    let res = if reuse_datastore {
> -        ChunkStore::verify_chunkstore(&path)
> +    let mut unmount_guard = if datastore.backing_device.is_some() {
> +        do_mount_device(datastore.clone())?;
> +        UnmountGuard::new(path.clone(), true)
> +    } else {
> +        UnmountGuard::new(path.clone(), false)

With `path` as `Option` this could be a cheap `UnmountGuard::new(None)`.

> +    };
> +
> +    if reuse_datastore {
> +        ChunkStore::verify_chunkstore(&path)?;
>      } else {
> -        let mut is_empty = true;
>          if let Ok(dir) = std::fs::read_dir(&path) {
>              for file in dir {
>                  let name = file?.file_name();
>                  let name = name.to_str();
>                  if !name.is_some_and(|name| name.starts_with('.') || name == "lost+found") {
> -                    is_empty = false;
> -                    break;
> +                    bail!("datastore path not empty");
>                  }
>              }
>          }
> -        if is_empty {
> -            let backup_user = pbs_config::backup_user()?;
> -            ChunkStore::create(
> -                &datastore.name,
> -                path.clone(),
> -                backup_user.uid,
> -                backup_user.gid,
> -                tuning.sync_level.unwrap_or_default(),
> -            )
> -            .map(|_| ())
> -        } else {
> -            Err(format_err!("datastore path not empty"))
> -        }
> +        let backup_user = pbs_config::backup_user()?;
> +        ChunkStore::create(
> +            &datastore.name,
> +            path.clone(),
> +            backup_user.uid,
> +            backup_user.gid,
> +            tuning.sync_level.unwrap_or_default(),
> +        )
> +        .map(|_| ())?;
>      };
>  
> -    if res.is_err() {
> -        if need_unmount {
> -            if let Err(e) = unmount_by_mountpoint(&path) {
> -                warn!("could not unmount device: {e}");
> -            }
> -        }
> -        return res;
> -    }
> -
>      config.set_data(&datastore.name, "datastore", &datastore)?;
>  
>      pbs_config::datastore::save_config(&config)?;
>  
> -    jobstate::create_state_file("garbage_collection", &datastore.name)
> +    jobstate::create_state_file("garbage_collection", &datastore.name)?;
> +
> +    unmount_guard.disable();
> +
> +    Ok(())
>  }
>  
>  #[api(
> -- 
> 2.39.5




More information about the pbs-devel mailing list