datastore: add helpers to destroy whole namespaces

The behavior on "any snapshot was protected" isn't yet ideal, as we
then do not cleanup any (sub) namespace, even if some of them where
cleaned from groups & snapshots completely. But that isn't easy to do
with our current depth-first pre-order iterator behavior, and it's
also not completely wrong either, the user can re-do the removal on
the sub-namespaces, so leave that for later.

Should get moved to a datastore::BackupNamespace type once/if we get
one

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-04-25 11:37:57 +02:00
parent 90e3869690
commit 4c7cc5b39e
1 changed files with 80 additions and 0 deletions

View File

@ -1,12 +1,14 @@
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Write};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use anyhow::{bail, format_err, Error};
use lazy_static::lazy_static;
use nix::unistd::{unlinkat, UnlinkatFlags};
use proxmox_schema::ApiType;
@ -428,6 +430,84 @@ impl DataStore {
full_path
}
/// Remove all backup groups of a single namespace level but not the namespace itself.
///
/// Does *not* descends into child-namespaces and doesn't remoes the namespace itself either.
///
/// Returns true if all the groups were removed, and false if some were protected.
pub fn remove_namespace_groups(self: &Arc<Self>, ns: &BackupNamespace) -> Result<bool, Error> {
// FIXME: locking? The single groups/snapshots are already protected, so may not be
// necesarry (depends on what we all allow to do with namespaces)
log::info!("removing all groups in namespace {}:/{ns}", self.name());
let mut removed_all_groups = true;
for group in self.iter_backup_groups(ns.to_owned())? {
let removed_group = group?.destroy()?;
removed_all_groups = removed_all_groups && removed_group;
}
let base_file = std::fs::File::open(self.base_path())?;
let base_fd = base_file.as_raw_fd();
for ty in BackupType::iter() {
let mut ty_dir = ns.path();
ty_dir.push(ty.to_string());
// best effort only, but we probably should log the error
if let Err(err) = unlinkat(Some(base_fd), &ty_dir, UnlinkatFlags::RemoveDir) {
if err.as_errno() != Some(nix::errno::Errno::ENOENT) {
log::error!("failed to remove backup type {ty} in {ns} - {err}");
}
}
}
Ok(removed_all_groups)
}
/// Remove a complete backup namespace including all it's, and child namespaces', groups.
///
/// Returns true if all groups were removed, and false if some were protected
pub fn remove_namespace_recursive(
self: &Arc<Self>,
ns: &BackupNamespace,
) -> Result<bool, Error> {
// FIXME: locking? The single groups/snapshots are already protected, so may not be
// necesarry (depends on what we all allow to do with namespaces)
log::info!("removing whole namespace recursively {}:/{ns}", self.name());
let mut removed_all_groups = true;
for ns in self.recursive_iter_backup_ns(ns.to_owned())? {
let removed_ns_groups = self.remove_namespace_groups(&ns?)?;
removed_all_groups = removed_all_groups && removed_ns_groups;
}
// now try to delete the actual namespaces, bottom up so that we can use safe rmdir that
// will choke if a new backup/group appeared in the meantime (but not on an new empty NS)
let mut children = self
.recursive_iter_backup_ns(ns.to_owned())?
.collect::<Result<Vec<BackupNamespace>, Error>>()?;
children.sort_by(|a, b| b.depth().cmp(&a.depth()));
let base_file = std::fs::File::open(self.base_path())?;
let base_fd = base_file.as_raw_fd();
for ns in children.iter() {
let mut ns_dir = ns.path();
ns_dir.push("ns");
let _ = unlinkat(Some(base_fd), &ns_dir, UnlinkatFlags::RemoveDir);
if !ns.is_root() {
match unlinkat(Some(base_fd), &ns.path(), UnlinkatFlags::RemoveDir) {
Ok(()) => log::info!("removed namespace {ns}"),
Err(err) => log::error!("failed to remove namespace {ns} - {err}"),
}
}
}
Ok(removed_all_groups)
}
/// Remove a complete backup group including all snapshots.
///
/// Returns true if all snapshots were removed, and false if some were protected