pool: allow pool re-use

and improve the naming of the two dirs.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2022-09-09 10:04:49 +02:00
parent 54c8397731
commit ff3d11a5d8

View File

@ -17,12 +17,13 @@ use walkdir::WalkDir;
#[derive(Debug)] #[derive(Debug)]
/// Pool consisting of two (possibly overlapping) directory trees: /// Pool consisting of two (possibly overlapping) directory trees:
/// - pool_dir contains checksum files added by `add_file` /// - pool_dir contains checksum files added by `add_file`
/// - base_dir contains directories and hardlinks to checksum files created by `link_file` /// - link_dir contains directories and hardlinks to checksum files created by `link_file`
/// ///
/// Files are considered orphaned and eligible for GC if they either only exist in pool_dir or only exist in base_dir /// Files are considered orphaned and eligible for GC if they either only exist in pool_dir
/// or only exist in link dir.
pub(crate) struct Pool { pub(crate) struct Pool {
pool_dir: PathBuf, pool_dir: PathBuf,
base_dir: PathBuf, link_dir: PathBuf,
} }
/// Lock guard used to guard against concurrent modification /// Lock guard used to guard against concurrent modification
@ -32,38 +33,39 @@ pub(crate) struct PoolLockGuard<'lock> {
} }
impl Pool { impl Pool {
/// Create a new pool by creating `pool_dir` and `base_dir`. They must not exist before calling this function. /// Create a new pool by creating `pool_dir` and `link_dir`.
pub(crate) fn create(base: &Path, pool: &Path) -> Result<Self, Error> { ///
if base.exists() { /// Pool dir can already exist, link dir must not exist before calling this function.
bail!("Pool base dir already exists."); pub(crate) fn create(link_dir: &Path, pool: &Path) -> Result<Self, Error> {
} if link_dir.exists() {
bail!("Pool link dir {link_dir:?} already exists.");
if pool.exists() {
bail!("Pool dir already exists.");
}
create_path(base, None, None)?;
create_path(pool, None, None)?;
Ok(Self {
pool_dir: pool.to_path_buf(),
base_dir: base.to_path_buf(),
})
}
/// Open an existing pool. `pool_dir` and `base_dir` must exist.
pub(crate) fn open(base: &Path, pool: &Path) -> Result<Self, Error> {
if !base.exists() {
bail!("Pool base dir doesn't exist.")
} }
if !pool.exists() { if !pool.exists() {
bail!("Pool dir doesn't exist."); create_path(pool, None, None)?;
}
create_path(link_dir, None, None)?;
Ok(Self {
pool_dir: pool.to_path_buf(),
link_dir: link_dir.to_path_buf(),
})
}
/// Open an existing pool. `pool_dir` and `link_dir` must exist.
pub(crate) fn open(link_dir: &Path, pool: &Path) -> Result<Self, Error> {
if !link_dir.exists() {
bail!("Pool link dir {link_dir:?} doesn't exist.")
}
if !pool.exists() {
bail!("Pool dir {pool:?} doesn't exist.");
} }
Ok(Self { Ok(Self {
pool_dir: pool.to_path_buf(), pool_dir: pool.to_path_buf(),
base_dir: base.to_path_buf(), link_dir: link_dir.to_path_buf(),
}) })
} }
@ -143,8 +145,8 @@ impl Pool {
path.starts_with(&self.pool_dir) path.starts_with(&self.pool_dir)
} }
fn path_in_base(&self, path: &Path) -> bool { fn path_in_link_dir(&self, path: &Path) -> bool {
path.starts_with(&self.base_dir) path.starts_with(&self.link_dir)
} }
fn lock_path(&self) -> PathBuf { fn lock_path(&self) -> PathBuf {
@ -154,19 +156,21 @@ impl Pool {
} }
pub(crate) fn get_path(&self, rel_path: &Path) -> Result<PathBuf, Error> { pub(crate) fn get_path(&self, rel_path: &Path) -> Result<PathBuf, Error> {
let mut path = self.base_dir.clone(); let mut path = self.link_dir.clone();
path.push(rel_path); path.push(rel_path);
if self.path_in_base(&path) { if self.path_in_link_dir(&path) {
Ok(path) Ok(path)
} else { } else {
bail!("Relative path not inside pool's base directory."); bail!("Relative path not inside pool's link directory.");
} }
} }
} }
impl PoolLockGuard<'_> { impl PoolLockGuard<'_> {
// Helper to scan the pool for all checksum files and the total link count. The resulting HashMap can be used to check whether files in `base_dir` are properly registered in the pool or orphaned. // Helper to scan the pool for all checksum files and the total link count. The resulting
// HashMap can be used to check whether files in `link_dir` are properly registered in the
// pool or orphaned.
fn get_inode_csum_map(&self) -> Result<(HashMap<u64, CheckSums>, u64), Error> { fn get_inode_csum_map(&self) -> Result<(HashMap<u64, CheckSums>, u64), Error> {
let mut inode_map: HashMap<u64, CheckSums> = HashMap::new(); let mut inode_map: HashMap<u64, CheckSums> = HashMap::new();
let mut link_count = 0; let mut link_count = 0;
@ -277,8 +281,8 @@ impl PoolLockGuard<'_> {
checked_count = 0; checked_count = 0;
let progress_modulo = max(total_link_count / 50, 10) as usize; let progress_modulo = max(total_link_count / 50, 10) as usize;
for base_entry in WalkDir::new(&self.pool.base_dir).into_iter() { for link_entry in WalkDir::new(&self.pool.link_dir).into_iter() {
let path = base_entry?.into_path(); let path = link_entry?.into_path();
if self.path_in_pool(&path) { if self.path_in_pool(&path) {
continue; continue;
}; };
@ -292,7 +296,7 @@ impl PoolLockGuard<'_> {
match inode_map.get(&meta.st_ino()) { match inode_map.get(&meta.st_ino()) {
Some(csum) => { Some(csum) => {
let path = path.strip_prefix(&self.pool.base_dir)?; let path = path.strip_prefix(&self.pool.link_dir)?;
if target.link_file(csum, path)? { if target.link_file(csum, path)? {
link_count += 1; link_count += 1;
@ -311,8 +315,8 @@ impl PoolLockGuard<'_> {
let mut vanished_count = 0usize; let mut vanished_count = 0usize;
let (target_inode_map, _target_link_count) = target.get_inode_csum_map()?; let (target_inode_map, _target_link_count) = target.get_inode_csum_map()?;
for base_entry in WalkDir::new(&target.base_dir).into_iter() { for link_entry in WalkDir::new(&target.link_dir).into_iter() {
let path = base_entry?.into_path(); let path = link_entry?.into_path();
if target.path_in_pool(&path) { if target.path_in_pool(&path) {
continue; continue;
}; };
@ -375,13 +379,13 @@ impl PoolLockGuard<'_> {
Ok(()) Ok(())
} }
/// Links previously added file into `path` (relative to `base_dir`). Missing parent directories will be created automatically. /// Links previously added file into `path` (relative to `link_dir`). Missing parent directories will be created automatically.
pub(crate) fn link_file(&self, checksums: &CheckSums, path: &Path) -> Result<bool, Error> { pub(crate) fn link_file(&self, checksums: &CheckSums, path: &Path) -> Result<bool, Error> {
let path = self.pool.get_path(path)?; let path = self.pool.get_path(path)?;
if !self.pool.path_in_base(&path) { if !self.pool.path_in_link_dir(&path) {
bail!( bail!(
"Cannot link file outside of pool - {:?} -> {:?}.", "Cannot link file outside of pool - {:?} -> {:?}.",
self.pool.base_dir, self.pool.link_dir,
path path
); );
} }
@ -400,13 +404,13 @@ impl PoolLockGuard<'_> {
link_file_do(source, &path) link_file_do(source, &path)
} }
/// Unlink a previously linked file at `path` (absolute, must be below `base_dir`). Optionally remove any parent directories that became empty. /// Unlink a previously linked file at `path` (absolute, must be below `link_dir`). Optionally remove any parent directories that became empty.
pub(crate) fn unlink_file( pub(crate) fn unlink_file(
&self, &self,
mut path: &Path, mut path: &Path,
remove_empty_parents: bool, remove_empty_parents: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
if !self.pool.path_in_base(path) { if !self.pool.path_in_link_dir(path) {
bail!("Cannot unlink file outside of pool."); bail!("Cannot unlink file outside of pool.");
} }
@ -419,7 +423,7 @@ impl PoolLockGuard<'_> {
while let Some(parent) = path.parent() { while let Some(parent) = path.parent() {
path = parent; path = parent;
if !self.pool.path_in_base(path) || !path.is_empty() { if !self.pool.path_in_link_dir(path) || !path.is_empty() {
break; break;
} }
@ -429,9 +433,9 @@ impl PoolLockGuard<'_> {
Ok(()) Ok(())
} }
/// Remove a directory tree at `path` (absolute, must be below `base_dir`) /// Remove a directory tree at `path` (absolute, must be below `link_dir`)
pub(crate) fn remove_dir(&self, path: &Path) -> Result<(), Error> { pub(crate) fn remove_dir(&self, path: &Path) -> Result<(), Error> {
if !self.pool.path_in_base(path) { if !self.pool.path_in_link_dir(path) {
bail!("Cannot unlink file outside of pool."); bail!("Cannot unlink file outside of pool.");
} }
@ -441,7 +445,7 @@ impl PoolLockGuard<'_> {
/// Run a garbage collection, removing /// Run a garbage collection, removing
/// - any checksum files that have no links outside of `pool_dir` /// - any checksum files that have no links outside of `pool_dir`
/// - any files in `base_dir` that have no corresponding checksum files /// - any files in `link_dir` that have no corresponding checksum files
pub(crate) fn gc(&self) -> Result<(usize, u64), Error> { pub(crate) fn gc(&self) -> Result<(usize, u64), Error> {
let (inode_map, _link_count) = self.get_inode_csum_map()?; let (inode_map, _link_count) = self.get_inode_csum_map()?;
@ -497,7 +501,7 @@ impl PoolLockGuard<'_> {
Ok(()) Ok(())
}; };
WalkDir::new(&self.pool.base_dir) WalkDir::new(&self.pool.link_dir)
.into_iter() .into_iter()
.try_for_each(|entry| handle_entry(entry, &mut count, &mut size))?; .try_for_each(|entry| handle_entry(entry, &mut count, &mut size))?;
WalkDir::new(&self.pool.pool_dir) WalkDir::new(&self.pool.pool_dir)
@ -507,23 +511,23 @@ impl PoolLockGuard<'_> {
Ok((count, size)) Ok((count, size))
} }
/// Destroy pool by removing `base_dir` and `pool_dir`. /// Destroy pool by removing `link_dir` and `pool_dir`.
pub(crate) fn destroy(self) -> Result<(), Error> { pub(crate) fn destroy(self) -> Result<(), Error> {
// TODO - this removes the lock file.. // TODO - this removes the lock file..
std::fs::remove_dir_all(self.pool_dir.clone())?; std::fs::remove_dir_all(self.pool_dir.clone())?;
std::fs::remove_dir_all(self.base_dir.clone())?; std::fs::remove_dir_all(self.link_dir.clone())?;
Ok(()) Ok(())
} }
/// Rename a link or directory from `from` to `to` (both relative to `base_dir`). /// Rename a link or directory from `from` to `to` (both relative to `link_dir`).
pub(crate) fn rename(&self, from: &Path, to: &Path) -> Result<(), Error> { pub(crate) fn rename(&self, from: &Path, to: &Path) -> Result<(), Error> {
let mut abs_from = self.base_dir.clone(); let mut abs_from = self.link_dir.clone();
abs_from.push(from); abs_from.push(from);
let mut abs_to = self.base_dir.clone(); let mut abs_to = self.link_dir.clone();
abs_to.push(to); abs_to.push(to);
if !self.path_in_base(&abs_from) || !self.path_in_base(&abs_to) { if !self.path_in_link_dir(&abs_from) || !self.path_in_link_dir(&abs_to) {
bail!("Can only rename within pool.."); bail!("Can only rename within pool..");
} }