[pbs-devel] [PATCH proxmox-backup v2 05/12] backup_info: add generics and separate functions into impl blocks

Hannes Laimer h.laimer at proxmox.com
Mon May 26 16:14:38 CEST 2025


Signed-off-by: Hannes Laimer <h.laimer at proxmox.com>
---
 pbs-datastore/src/backup_info.rs | 583 ++++++++++++++++---------------
 pbs-datastore/src/datastore.rs   |  26 +-
 2 files changed, 313 insertions(+), 296 deletions(-)

diff --git a/pbs-datastore/src/backup_info.rs b/pbs-datastore/src/backup_info.rs
index d4732fdd..f3ca283c 100644
--- a/pbs-datastore/src/backup_info.rs
+++ b/pbs-datastore/src/backup_info.rs
@@ -18,7 +18,10 @@ use pbs_api_types::{
 use pbs_config::{open_backup_lockfile, BackupLockGuard};
 
 use crate::manifest::{BackupManifest, MANIFEST_LOCK_NAME};
-use crate::{DataBlob, DataStore};
+use crate::{
+    chunk_store::{CanRead, CanWrite},
+    DataBlob, DataStore,
+};
 
 pub const DATASTORE_LOCKS_DIR: &str = "/run/proxmox-backup/locks";
 
@@ -34,14 +37,14 @@ pub(crate) static OLD_LOCKING: LazyLock<bool> = LazyLock::new(|| {
 
 /// BackupGroup is a directory containing a list of BackupDir
 #[derive(Clone)]
-pub struct BackupGroup {
-    store: Arc<DataStore>,
+pub struct BackupGroup<T> {
+    store: Arc<DataStore<T>>,
 
     ns: BackupNamespace,
     group: pbs_api_types::BackupGroup,
 }
 
-impl fmt::Debug for BackupGroup {
+impl<T> fmt::Debug for BackupGroup<T> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.debug_struct("BackupGroup")
             .field("store", &self.store.name())
@@ -51,45 +54,12 @@ impl fmt::Debug for BackupGroup {
     }
 }
 
-impl BackupGroup {
-    pub(crate) fn new(
-        store: Arc<DataStore>,
-        ns: BackupNamespace,
-        group: pbs_api_types::BackupGroup,
-    ) -> Self {
-        Self { store, ns, group }
-    }
-
-    /// Access the underlying [`BackupGroup`](pbs_api_types::BackupGroup).
-    #[inline]
-    pub fn group(&self) -> &pbs_api_types::BackupGroup {
-        &self.group
-    }
-
-    #[inline]
-    pub fn backup_ns(&self) -> &BackupNamespace {
-        &self.ns
-    }
-
-    #[inline]
-    pub fn backup_type(&self) -> BackupType {
-        self.group.ty
-    }
-
-    #[inline]
-    pub fn backup_id(&self) -> &str {
-        &self.group.id
-    }
-
-    pub fn full_group_path(&self) -> PathBuf {
-        self.store.group_path(&self.ns, &self.group)
-    }
-
-    pub fn relative_group_path(&self) -> PathBuf {
-        let mut path = self.ns.path();
-        path.push(self.group.ty.as_str());
-        path.push(&self.group.id);
-        path
+impl<T: CanRead> BackupGroup<T> {
+    /// Returns the backup owner.
+    ///
+    /// The backup owner is the entity who first created the backup group.
+    pub fn get_owner(&self) -> Result<Authid, Error> {
+        self.store.get_owner(&self.ns, self.as_ref())
     }
 
     /// Simple check whether a group exists. This does not check whether there are any snapshots,
@@ -98,7 +68,7 @@ impl BackupGroup {
         self.full_group_path().exists()
     }
 
-    pub fn list_backups(&self) -> Result<Vec<BackupInfo>, Error> {
+    pub fn list_backups(&self) -> Result<Vec<BackupInfo<T>>, Error> {
         let mut list = vec![];
 
         let path = self.full_group_path();
@@ -130,7 +100,7 @@ impl BackupGroup {
     }
 
     /// Finds the latest backup inside a backup group
-    pub fn last_backup(&self, only_finished: bool) -> Result<Option<BackupInfo>, Error> {
+    pub fn last_backup(&self, only_finished: bool) -> Result<Option<BackupInfo<T>>, Error> {
         let backups = self.list_backups()?;
         Ok(backups
             .into_iter()
@@ -190,24 +160,13 @@ impl BackupGroup {
 
         Ok(last)
     }
+}
 
-    pub fn matches(&self, filter: &GroupFilter) -> bool {
-        self.group.matches(filter)
-    }
-
-    pub fn backup_dir(&self, time: i64) -> Result<BackupDir, Error> {
-        BackupDir::with_group(self.clone(), time)
-    }
-
-    pub fn backup_dir_with_rfc3339<T: Into<String>>(
-        &self,
-        time_string: T,
-    ) -> Result<BackupDir, Error> {
-        BackupDir::with_rfc3339(self.clone(), time_string.into())
-    }
-
-    pub fn iter_snapshots(&self) -> Result<crate::ListSnapshots, Error> {
-        crate::ListSnapshots::new(self.clone())
+impl<T: CanWrite> BackupGroup<T> {
+    /// Set the backup owner.
+    pub fn set_owner(&self, auth_id: &Authid, force: bool) -> Result<(), Error> {
+        self.store
+            .set_owner(&self.ns, self.as_ref(), auth_id, force)
     }
 
     /// Destroy the group inclusive all its backup snapshots (BackupDir's)
@@ -260,32 +219,6 @@ impl BackupGroup {
         Ok(())
     }
 
-    /// Returns the backup owner.
-    ///
-    /// The backup owner is the entity who first created the backup group.
-    pub fn get_owner(&self) -> Result<Authid, Error> {
-        self.store.get_owner(&self.ns, self.as_ref())
-    }
-
-    /// Set the backup owner.
-    pub fn set_owner(&self, auth_id: &Authid, force: bool) -> Result<(), Error> {
-        self.store
-            .set_owner(&self.ns, self.as_ref(), auth_id, force)
-    }
-
-    /// Returns a file name for locking a group.
-    ///
-    /// The lock file will be located in:
-    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}`
-    /// where `rpath` is the relative path of the group.
-    fn lock_path(&self) -> PathBuf {
-        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
-
-        let rpath = Path::new(self.group.ty.as_str()).join(&self.group.id);
-
-        path.join(lock_file_path_helper(&self.ns, rpath))
-    }
-
     /// Locks a group exclusively.
     pub fn lock(&self) -> Result<BackupLockGuard, Error> {
         if *OLD_LOCKING {
@@ -304,34 +237,108 @@ impl BackupGroup {
     }
 }
 
-impl AsRef<pbs_api_types::BackupNamespace> for BackupGroup {
+impl<T: Clone> BackupGroup<T> {
+    pub(crate) fn new(
+        store: Arc<DataStore<T>>,
+        ns: BackupNamespace,
+        group: pbs_api_types::BackupGroup,
+    ) -> Self {
+        Self { store, ns, group }
+    }
+
+    /// Access the underlying [`BackupGroup`](pbs_api_types::BackupGroup).
+    #[inline]
+    pub fn group(&self) -> &pbs_api_types::BackupGroup {
+        &self.group
+    }
+
+    #[inline]
+    pub fn backup_ns(&self) -> &BackupNamespace {
+        &self.ns
+    }
+
+    #[inline]
+    pub fn backup_type(&self) -> BackupType {
+        self.group.ty
+    }
+
+    #[inline]
+    pub fn backup_id(&self) -> &str {
+        &self.group.id
+    }
+
+    pub fn full_group_path(&self) -> PathBuf {
+        self.store.group_path(&self.ns, &self.group)
+    }
+
+    pub fn relative_group_path(&self) -> PathBuf {
+        let mut path = self.ns.path();
+        path.push(self.group.ty.as_str());
+        path.push(&self.group.id);
+        path
+    }
+
+    pub fn matches(&self, filter: &GroupFilter) -> bool {
+        self.group.matches(filter)
+    }
+
+    pub fn backup_dir(&self, time: i64) -> Result<BackupDir<T>, Error> {
+        BackupDir::with_group(self.clone(), time)
+    }
+
+    pub fn backup_dir_with_rfc3339<D: Into<String>>(
+        &self,
+        time_string: D,
+    ) -> Result<BackupDir<T>, Error> {
+        BackupDir::with_rfc3339(self.clone(), time_string.into())
+    }
+
+    pub fn iter_snapshots(&self) -> Result<crate::ListSnapshots, Error> {
+        crate::ListSnapshots::new(self.clone())
+    }
+
+    /// Returns a file name for locking a group.
+    ///
+    /// The lock file will be located in:
+    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}`
+    /// where `rpath` is the relative path of the group.
+    fn lock_path(&self) -> PathBuf {
+        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
+
+        let rpath = Path::new(self.group.ty.as_str()).join(&self.group.id);
+
+        path.join(lock_file_path_helper(&self.ns, rpath))
+    }
+}
+
+impl<T> AsRef<pbs_api_types::BackupNamespace> for BackupGroup<T> {
     #[inline]
     fn as_ref(&self) -> &pbs_api_types::BackupNamespace {
         &self.ns
     }
 }
 
-impl AsRef<pbs_api_types::BackupGroup> for BackupGroup {
+impl<T> AsRef<pbs_api_types::BackupGroup> for BackupGroup<T> {
     #[inline]
     fn as_ref(&self) -> &pbs_api_types::BackupGroup {
         &self.group
     }
 }
 
-impl From<&BackupGroup> for pbs_api_types::BackupGroup {
-    fn from(group: &BackupGroup) -> pbs_api_types::BackupGroup {
+impl<T> From<&BackupGroup<T>> for pbs_api_types::BackupGroup {
+    fn from(group: &BackupGroup<T>) -> pbs_api_types::BackupGroup {
         group.group.clone()
     }
 }
 
-impl From<BackupGroup> for pbs_api_types::BackupGroup {
-    fn from(group: BackupGroup) -> pbs_api_types::BackupGroup {
+impl<T> From<BackupGroup<T>> for pbs_api_types::BackupGroup {
+    fn from(group: BackupGroup<T>) -> pbs_api_types::BackupGroup {
         group.group
     }
 }
 
-impl From<BackupDir> for BackupGroup {
-    fn from(dir: BackupDir) -> BackupGroup {
+impl<T> From<BackupDir<T>> for BackupGroup<T> {
+    fn from(dir: BackupDir<T>) -> BackupGroup<T> {
         BackupGroup {
             store: dir.store,
             ns: dir.ns,
@@ -340,8 +347,8 @@ impl From<BackupDir> for BackupGroup {
     }
 }
 
-impl From<&BackupDir> for BackupGroup {
-    fn from(dir: &BackupDir) -> BackupGroup {
+impl<T> From<&BackupDir<T>> for BackupGroup<T> {
+    fn from(dir: &BackupDir<T>) -> BackupGroup<T> {
         BackupGroup {
             store: Arc::clone(&dir.store),
             ns: dir.ns.clone(),
@@ -354,15 +361,15 @@ impl From<&BackupDir> for BackupGroup {
 ///
 /// We also call this a backup snaphost.
 #[derive(Clone)]
-pub struct BackupDir {
-    store: Arc<DataStore>,
+pub struct BackupDir<T> {
+    store: Arc<DataStore<T>>,
     ns: BackupNamespace,
     dir: pbs_api_types::BackupDir,
     // backup_time as rfc3339
     backup_time_string: String,
 }
 
-impl fmt::Debug for BackupDir {
+impl<T> fmt::Debug for BackupDir<T> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.debug_struct("BackupDir")
             .field("store", &self.store.name())
@@ -373,102 +380,12 @@ impl fmt::Debug for BackupDir {
     }
 }
 
-impl BackupDir {
-    /// Temporarily used for tests.
-    #[doc(hidden)]
-    pub fn new_test(dir: pbs_api_types::BackupDir) -> Self {
-        Self {
-            store: unsafe { DataStore::new_test() },
-            backup_time_string: Self::backup_time_to_string(dir.time).unwrap(),
-            ns: BackupNamespace::root(),
-            dir,
-        }
-    }
-
-    pub(crate) fn with_group(group: BackupGroup, backup_time: i64) -> Result<Self, Error> {
-        let backup_time_string = Self::backup_time_to_string(backup_time)?;
-        Ok(Self {
-            store: group.store,
-            ns: group.ns,
-            dir: (group.group, backup_time).into(),
-            backup_time_string,
-        })
-    }
-
-    pub(crate) fn with_rfc3339(
-        group: BackupGroup,
-        backup_time_string: String,
-    ) -> Result<Self, Error> {
-        let backup_time = proxmox_time::parse_rfc3339(&backup_time_string)?;
-        Ok(Self {
-            store: group.store,
-            ns: group.ns,
-            dir: (group.group, backup_time).into(),
-            backup_time_string,
-        })
-    }
-
-    #[inline]
-    pub fn backup_ns(&self) -> &BackupNamespace {
-        &self.ns
-    }
-
-    #[inline]
-    pub fn backup_type(&self) -> BackupType {
-        self.dir.group.ty
-    }
-
-    #[inline]
-    pub fn backup_id(&self) -> &str {
-        &self.dir.group.id
-    }
-
-    #[inline]
-    pub fn backup_time(&self) -> i64 {
-        self.dir.time
-    }
-
-    pub fn backup_time_string(&self) -> &str {
-        &self.backup_time_string
-    }
-
-    pub fn dir(&self) -> &pbs_api_types::BackupDir {
-        &self.dir
-    }
-
-    pub fn group(&self) -> &pbs_api_types::BackupGroup {
-        &self.dir.group
-    }
-
-    pub fn relative_path(&self) -> PathBuf {
-        let mut path = self.ns.path();
-        path.push(self.dir.group.ty.as_str());
-        path.push(&self.dir.group.id);
-        path.push(&self.backup_time_string);
-        path
-    }
-
-    /// Returns the absolute path for backup_dir, using the cached formatted time string.
-    pub fn full_path(&self) -> PathBuf {
-        let mut path = self.store.base_path();
-        path.push(self.relative_path());
-        path
-    }
-
-    pub fn protected_file(&self) -> PathBuf {
-        let mut path = self.full_path();
-        path.push(".protected");
-        path
-    }
-
-    pub fn is_protected(&self) -> bool {
-        let path = self.protected_file();
-        path.exists()
-    }
-
-    pub fn backup_time_to_string(backup_time: i64) -> Result<String, Error> {
-        // fixme: can this fail? (avoid unwrap)
-        proxmox_time::epoch_to_rfc3339_utc(backup_time)
+impl<T: CanRead> BackupDir<T> {
+    /// Returns the backup owner.
+    ///
+    /// The backup owner is the entity who first created the backup group.
+    pub fn get_owner(&self) -> Result<Authid, Error> {
+        self.store.get_owner(&self.ns, self.as_ref())
     }
 
     /// load a `DataBlob` from this snapshot's backup dir.
@@ -483,22 +400,38 @@ impl BackupDir {
         .map_err(|err| format_err!("unable to load blob '{:?}' - {}", path, err))
     }
 
-    /// Returns the filename to lock a manifest
-    ///
-    /// Also creates the basedir. The lockfile is located in
-    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}.index.json.lck`
-    /// where rpath is the relative path of the snapshot.
-    fn manifest_lock_path(&self) -> PathBuf {
-        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
+    /// Acquires a shared lock on a snapshot.
+    pub fn lock_shared(&self) -> Result<BackupLockGuard, Error> {
+        if *OLD_LOCKING {
+            lock_dir_noblock_shared(
+                &self.full_path(),
+                "snapshot",
+                "backup is running or snapshot is in use, could not acquire shared lock",
+            )
+            .map(BackupLockGuard::from)
+        } else {
+            lock_helper(self.store.name(), &self.lock_path(), |p| {
+                open_backup_lockfile(p, Some(Duration::from_secs(0)), false)
+                    .with_context(|| format!("unable to acquire shared snapshot lock {p:?}"))
+            })
+        }
+    }
 
-        let rpath = Path::new(self.dir.group.ty.as_str())
-            .join(&self.dir.group.id)
-            .join(&self.backup_time_string)
-            .join(MANIFEST_LOCK_NAME);
+    /// Load the manifest without a lock. Must not be written back.
+    pub fn load_manifest(&self) -> Result<(BackupManifest, u64), Error> {
+        let blob = self.load_blob(MANIFEST_BLOB_NAME.as_ref())?;
+        let raw_size = blob.raw_size();
+        let manifest = BackupManifest::try_from(blob)?;
+        Ok((manifest, raw_size))
+    }
 
-        path.join(lock_file_path_helper(&self.ns, rpath))
+    /// Load the verify state from the manifest.
+    pub fn verify_state(&self) -> Result<Option<VerifyState>, anyhow::Error> {
+        Ok(self.load_manifest()?.0.verify_state()?.map(|svs| svs.state))
     }
+}
 
+impl<T: CanWrite> BackupDir<T> {
     /// Locks the manifest of a snapshot, for example, to update or delete it.
     pub(crate) fn lock_manifest(&self) -> Result<BackupLockGuard, Error> {
         let path = if *OLD_LOCKING {
@@ -523,21 +456,6 @@ impl BackupDir {
         })
     }
 
-    /// Returns a file name for locking a snapshot.
-    ///
-    /// The lock file will be located in:
-    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}`
-    /// where `rpath` is the relative path of the snapshot.
-    fn lock_path(&self) -> PathBuf {
-        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
-
-        let rpath = Path::new(self.dir.group.ty.as_str())
-            .join(&self.dir.group.id)
-            .join(&self.backup_time_string);
-
-        path.join(lock_file_path_helper(&self.ns, rpath))
-    }
-
     /// Locks a snapshot exclusively.
     pub fn lock(&self) -> Result<BackupLockGuard, Error> {
         if *OLD_LOCKING {
@@ -555,23 +473,6 @@ impl BackupDir {
         }
     }
 
-    /// Acquires a shared lock on a snapshot.
-    pub fn lock_shared(&self) -> Result<BackupLockGuard, Error> {
-        if *OLD_LOCKING {
-            lock_dir_noblock_shared(
-                &self.full_path(),
-                "snapshot",
-                "backup is running or snapshot is in use, could not acquire shared lock",
-            )
-            .map(BackupLockGuard::from)
-        } else {
-            lock_helper(self.store.name(), &self.lock_path(), |p| {
-                open_backup_lockfile(p, Some(Duration::from_secs(0)), false)
-                    .with_context(|| format!("unable to acquire shared snapshot lock {p:?}"))
-            })
-        }
-    }
-
     /// Destroy the whole snapshot, bails if it's protected
     ///
     /// Setting `force` to true skips locking and thus ignores if the backup is currently in use.
@@ -624,31 +525,6 @@ impl BackupDir {
         Ok(())
     }
 
-    /// Get the datastore.
-    pub fn datastore(&self) -> &Arc<DataStore> {
-        &self.store
-    }
-
-    /// Returns the backup owner.
-    ///
-    /// The backup owner is the entity who first created the backup group.
-    pub fn get_owner(&self) -> Result<Authid, Error> {
-        self.store.get_owner(&self.ns, self.as_ref())
-    }
-
-    /// Lock the snapshot and open a reader.
-    pub fn locked_reader(&self) -> Result<crate::SnapshotReader, Error> {
-        crate::SnapshotReader::new_do(self.clone())
-    }
-
-    /// Load the manifest without a lock. Must not be written back.
-    pub fn load_manifest(&self) -> Result<(BackupManifest, u64), Error> {
-        let blob = self.load_blob(MANIFEST_BLOB_NAME.as_ref())?;
-        let raw_size = blob.raw_size();
-        let manifest = BackupManifest::try_from(blob)?;
-        Ok((manifest, raw_size))
-    }
-
     /// Update the manifest of the specified snapshot. Never write a manifest directly,
     /// only use this method - anything else may break locking guarantees.
     pub fn update_manifest(
@@ -706,68 +582,203 @@ impl BackupDir {
 
         Ok(())
     }
+}
+
+impl<T> BackupDir<T> {
+    /// Temporarily used for tests.
+    #[doc(hidden)]
+    pub fn new_test(dir: pbs_api_types::BackupDir) -> Self {
+        Self {
+            store: DataStore::new_test(),
+            backup_time_string: Self::backup_time_to_string(dir.time).unwrap(),
+            ns: BackupNamespace::root(),
+            dir,
+        }
+    }
 
-    /// Load the verify state from the manifest.
-    pub fn verify_state(&self) -> Result<Option<VerifyState>, anyhow::Error> {
-        Ok(self.load_manifest()?.0.verify_state()?.map(|svs| svs.state))
+    pub(crate) fn with_group(group: BackupGroup<T>, backup_time: i64) -> Result<Self, Error> {
+        let backup_time_string = Self::backup_time_to_string(backup_time)?;
+        Ok(Self {
+            store: group.store,
+            ns: group.ns,
+            dir: (group.group, backup_time).into(),
+            backup_time_string,
+        })
+    }
+
+    pub(crate) fn with_rfc3339(
+        group: BackupGroup<T>,
+        backup_time_string: String,
+    ) -> Result<Self, Error> {
+        let backup_time = proxmox_time::parse_rfc3339(&backup_time_string)?;
+        Ok(Self {
+            store: group.store,
+            ns: group.ns,
+            dir: (group.group, backup_time).into(),
+            backup_time_string,
+        })
+    }
+
+    #[inline]
+    pub fn backup_ns(&self) -> &BackupNamespace {
+        &self.ns
+    }
+
+    #[inline]
+    pub fn backup_type(&self) -> BackupType {
+        self.dir.group.ty
+    }
+
+    #[inline]
+    pub fn backup_id(&self) -> &str {
+        &self.dir.group.id
+    }
+
+    #[inline]
+    pub fn backup_time(&self) -> i64 {
+        self.dir.time
+    }
+
+    pub fn backup_time_string(&self) -> &str {
+        &self.backup_time_string
+    }
+
+    pub fn dir(&self) -> &pbs_api_types::BackupDir {
+        &self.dir
+    }
+
+    pub fn group(&self) -> &pbs_api_types::BackupGroup {
+        &self.dir.group
+    }
+
+    pub fn relative_path(&self) -> PathBuf {
+        let mut path = self.ns.path();
+        path.push(self.dir.group.ty.as_str());
+        path.push(&self.dir.group.id);
+        path.push(&self.backup_time_string);
+        path
+    }
+
+    /// Returns the absolute path for backup_dir, using the cached formatted time string.
+    pub fn full_path(&self) -> PathBuf {
+        let mut path = self.store.base_path();
+        path.push(self.relative_path());
+        path
+    }
+
+    pub fn protected_file(&self) -> PathBuf {
+        let mut path = self.full_path();
+        path.push(".protected");
+        path
+    }
+
+    pub fn is_protected(&self) -> bool {
+        let path = self.protected_file();
+        path.exists()
+    }
+
+    pub fn backup_time_to_string(backup_time: i64) -> Result<String, Error> {
+        // fixme: can this fail? (avoid unwrap)
+        proxmox_time::epoch_to_rfc3339_utc(backup_time)
+    }
+
+    /// Returns the filename to lock a manifest
+    ///
+    /// Also creates the basedir. The lockfile is located in
+    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}.index.json.lck`
+    /// where rpath is the relative path of the snapshot.
+    fn manifest_lock_path(&self) -> PathBuf {
+        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
+
+        let rpath = Path::new(self.dir.group.ty.as_str())
+            .join(&self.dir.group.id)
+            .join(&self.backup_time_string)
+            .join(MANIFEST_LOCK_NAME);
+
+        path.join(lock_file_path_helper(&self.ns, rpath))
+    }
+
+    /// Returns a file name for locking a snapshot.
+    ///
+    /// The lock file will be located in:
+    /// `${DATASTORE_LOCKS_DIR}/${datastore name}/${lock_file_path_helper(rpath)}`
+    /// where `rpath` is the relative path of the snapshot.
+    fn lock_path(&self) -> PathBuf {
+        let path = Path::new(DATASTORE_LOCKS_DIR).join(self.store.name());
+
+        let rpath = Path::new(self.dir.group.ty.as_str())
+            .join(&self.dir.group.id)
+            .join(&self.backup_time_string);
+
+        path.join(lock_file_path_helper(&self.ns, rpath))
+    }
+
+    /// Get the datastore.
+    pub fn datastore(&self) -> &Arc<DataStore<T>> {
+        &self.store
+    }
+
+    /// Lock the snapshot and open a reader.
+    pub fn locked_reader(&self) -> Result<crate::SnapshotReader, Error> {
+        crate::SnapshotReader::new_do(self.clone())
     }
 }
 
-impl AsRef<pbs_api_types::BackupNamespace> for BackupDir {
+impl<T> AsRef<pbs_api_types::BackupNamespace> for BackupDir<T> {
     fn as_ref(&self) -> &pbs_api_types::BackupNamespace {
         &self.ns
     }
 }
 
-impl AsRef<pbs_api_types::BackupDir> for BackupDir {
+impl<T> AsRef<pbs_api_types::BackupDir> for BackupDir<T> {
     fn as_ref(&self) -> &pbs_api_types::BackupDir {
         &self.dir
     }
 }
 
-impl AsRef<pbs_api_types::BackupGroup> for BackupDir {
+impl<T> AsRef<pbs_api_types::BackupGroup> for BackupDir<T> {
     fn as_ref(&self) -> &pbs_api_types::BackupGroup {
         &self.dir.group
     }
 }
 
-impl From<&BackupDir> for pbs_api_types::BackupGroup {
-    fn from(dir: &BackupDir) -> pbs_api_types::BackupGroup {
+impl<T> From<&BackupDir<T>> for pbs_api_types::BackupGroup {
+    fn from(dir: &BackupDir<T>) -> pbs_api_types::BackupGroup {
         dir.dir.group.clone()
     }
 }
 
-impl From<BackupDir> for pbs_api_types::BackupGroup {
-    fn from(dir: BackupDir) -> pbs_api_types::BackupGroup {
+impl<T> From<BackupDir<T>> for pbs_api_types::BackupGroup {
+    fn from(dir: BackupDir<T>) -> pbs_api_types::BackupGroup {
         dir.dir.group
     }
 }
 
-impl From<&BackupDir> for pbs_api_types::BackupDir {
-    fn from(dir: &BackupDir) -> pbs_api_types::BackupDir {
+impl<T> From<&BackupDir<T>> for pbs_api_types::BackupDir {
+    fn from(dir: &BackupDir<T>) -> pbs_api_types::BackupDir {
         dir.dir.clone()
     }
 }
 
-impl From<BackupDir> for pbs_api_types::BackupDir {
-    fn from(dir: BackupDir) -> pbs_api_types::BackupDir {
+impl<T> From<BackupDir<T>> for pbs_api_types::BackupDir {
+    fn from(dir: BackupDir<T>) -> pbs_api_types::BackupDir {
         dir.dir
     }
 }
 
 /// Detailed Backup Information, lists files inside a BackupDir
 #[derive(Clone, Debug)]
-pub struct BackupInfo {
+pub struct BackupInfo<T> {
     /// the backup directory
-    pub backup_dir: BackupDir,
+    pub backup_dir: BackupDir<T>,
     /// List of data files
     pub files: Vec<String>,
     /// Protection Status
     pub protected: bool,
 }
 
-impl BackupInfo {
-    pub fn new(backup_dir: BackupDir) -> Result<BackupInfo, Error> {
+impl<T: CanRead> BackupInfo<T> {
+    pub fn new(backup_dir: BackupDir<T>) -> Result<BackupInfo<T>, Error> {
         let path = backup_dir.full_path();
 
         let files = list_backup_files(libc::AT_FDCWD, &path)?;
@@ -779,8 +790,10 @@ impl BackupInfo {
             protected,
         })
     }
+}
 
-    pub fn sort_list(list: &mut [BackupInfo], ascendending: bool) {
+impl<T> BackupInfo<T> {
+    pub fn sort_list(list: &mut [BackupInfo<T>], ascendending: bool) {
         if ascendending {
             // oldest first
             list.sort_unstable_by(|a, b| a.backup_dir.dir.time.cmp(&b.backup_dir.dir.time));
diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
index 66a2e209..9356750b 100644
--- a/pbs-datastore/src/datastore.rs
+++ b/pbs-datastore/src/datastore.rs
@@ -303,7 +303,7 @@ impl<T: CanRead + 'static> DataStore<T> {
         self: &Arc<DataStore<T>>,
         ns: BackupNamespace,
         ty: BackupType,
-    ) -> Result<impl Iterator<Item = BackupGroup> + 'static, Error> {
+    ) -> Result<impl Iterator<Item = BackupGroup<T>> + 'static, Error> {
         Ok(self.iter_backup_type(ns, ty)?.ok())
     }
 
@@ -314,7 +314,7 @@ impl<T: CanRead + 'static> DataStore<T> {
     pub fn iter_backup_groups_ok(
         self: &Arc<DataStore<T>>,
         ns: BackupNamespace,
-    ) -> Result<impl Iterator<Item = BackupGroup> + 'static, Error> {
+    ) -> Result<impl Iterator<Item = BackupGroup<T>> + 'static, Error> {
         Ok(self.iter_backup_groups(ns)?.ok())
     }
 }
@@ -644,7 +644,7 @@ impl<T: CanRead> DataStore<T> {
     pub fn list_backup_groups(
         self: &Arc<DataStore<T>>,
         ns: BackupNamespace,
-    ) -> Result<Vec<BackupGroup>, Error> {
+    ) -> Result<Vec<BackupGroup<T>>, Error> {
         ListGroups::new(Arc::clone(self), ns)?.collect()
     }
 
@@ -837,7 +837,7 @@ impl<T: CanRead> DataStore<T> {
         ty: BackupType,
         id: D,
         time: i64,
-    ) -> Result<BackupDir, Error>
+    ) -> Result<BackupDir<T>, Error>
     where
         D: Into<String>,
     {
@@ -847,10 +847,10 @@ impl<T: CanRead> DataStore<T> {
     /// Open a snapshot (backup directory) from this datastore with a cached rfc3339 time string.
     pub fn backup_dir_with_rfc3339<D: Into<String>>(
         self: &Arc<Self>,
-        group: BackupGroup,
+        group: BackupGroup<T>,
         time_string: D,
-    ) -> Result<BackupDir, Error> {
-        BackupDir::with_rfc3339(group, time_string.into())
+    ) -> Result<BackupDir<T>, Error> {
+        BackupDir::<T>::with_rfc3339(group, time_string.into())
     }
 
     /// Open a backup group from this datastore.
@@ -859,7 +859,7 @@ impl<T: CanRead> DataStore<T> {
         ns: BackupNamespace,
         ty: BackupType,
         id: D,
-    ) -> BackupGroup
+    ) -> BackupGroup<T>
     where
         D: Into<String>,
     {
@@ -889,7 +889,7 @@ impl<T: CanRead> DataStore<T> {
         self: &Arc<Self>,
         ns: BackupNamespace,
         dir: pbs_api_types::BackupDir,
-    ) -> Result<BackupDir, Error> {
+    ) -> Result<BackupDir<T>, Error> {
         BackupDir::with_group(self.backup_group(ns, dir.group), dir.time)
     }
 }
@@ -1258,7 +1258,7 @@ impl<T: CanWrite> DataStore<T> {
                         _ => bail!("exhausted retries and unexpected counter overrun"),
                     };
 
-                    let mut snapshots = match group.list_backups() {
+                    let mut snapshots: Vec<BackupInfo<T>> = match group.list_backups() {
                         Ok(snapshots) => snapshots,
                         Err(err) => {
                             if group.exists() {
@@ -1526,7 +1526,11 @@ impl<T: CanWrite> DataStore<T> {
     }
 
     /// Updates the protection status of the specified snapshot.
-    pub fn update_protection(&self, backup_dir: &BackupDir, protection: bool) -> Result<(), Error> {
+    pub fn update_protection(
+        &self,
+        backup_dir: &BackupDir<T>,
+        protection: bool,
+    ) -> Result<(), Error> {
         let full_path = backup_dir.full_path();
 
         if !full_path.exists() {
-- 
2.39.5





More information about the pbs-devel mailing list