From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [RFC v2 proxmox-backup 01/21] datastore/api: mark snapshots as trash on destroy
Date: Thu, 8 May 2025 15:05:35 +0200 [thread overview]
Message-ID: <20250508130555.494782-2-c.ebner@proxmox.com> (raw)
In-Reply-To: <20250508130555.494782-1-c.ebner@proxmox.com>
In order to implement the trash can functionality, mark snapshots
as trash instead of removing them by default. However, provide a
`skip-trash` flag to opt-out and destroy the snapshot including it's
contents immediately.
Trashed snapshots are marked by creating a `.trashed` marker file
inside the snapshot folder. Actual removal of the snapshot will be
deferred to the garbage collection task.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
pbs-datastore/src/backup_info.rs | 66 ++++++++++++++++++--------------
pbs-datastore/src/datastore.rs | 2 +-
src/api2/admin/datastore.rs | 18 ++++++++-
3 files changed, 55 insertions(+), 31 deletions(-)
diff --git a/pbs-datastore/src/backup_info.rs b/pbs-datastore/src/backup_info.rs
index d4732fdd9..76bcd15f5 100644
--- a/pbs-datastore/src/backup_info.rs
+++ b/pbs-datastore/src/backup_info.rs
@@ -21,6 +21,7 @@ use crate::manifest::{BackupManifest, MANIFEST_LOCK_NAME};
use crate::{DataBlob, DataStore};
pub const DATASTORE_LOCKS_DIR: &str = "/run/proxmox-backup/locks";
+pub const TRASH_MARKER_FILENAME: &str = ".trashed";
// TODO: Remove with PBS 5
// Note: The `expect()` call here will only happen if we can neither confirm nor deny the existence
@@ -228,7 +229,7 @@ impl BackupGroup {
delete_stats.increment_protected_snapshots();
continue;
}
- snap.destroy(false)?;
+ snap.destroy(false, false)?;
delete_stats.increment_removed_snapshots();
}
@@ -575,7 +576,8 @@ impl BackupDir {
/// 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.
- pub fn destroy(&self, force: bool) -> Result<(), Error> {
+ /// Setting `skip_trash` to true will remove the snapshot instead of marking it as trash.
+ pub fn destroy(&self, force: bool, skip_trash: bool) -> Result<(), Error> {
let (_guard, _manifest_guard);
if !force {
_guard = self
@@ -588,37 +590,45 @@ impl BackupDir {
bail!("cannot remove protected snapshot"); // use special error type?
}
- let full_path = self.full_path();
- log::info!("removing backup snapshot {:?}", full_path);
- std::fs::remove_dir_all(&full_path).map_err(|err| {
- format_err!("removing backup snapshot {:?} failed - {}", full_path, err,)
- })?;
+ let mut full_path = self.full_path();
+ log::info!("removing backup snapshot {full_path:?}");
+ if skip_trash {
+ std::fs::remove_dir_all(&full_path).map_err(|err| {
+ format_err!("removing backup snapshot {full_path:?} failed - {err}")
+ })?;
+ } else {
+ full_path.push(TRASH_MARKER_FILENAME);
+ let _trash_file =
+ std::fs::File::create(full_path).context("failed to set trash file")?;
+ }
// remove no longer needed lock files
let _ = std::fs::remove_file(self.manifest_lock_path()); // ignore errors
let _ = std::fs::remove_file(self.lock_path()); // ignore errors
- let group = BackupGroup::from(self);
- let guard = group.lock().with_context(|| {
- format!("while checking if group '{group:?}' is empty during snapshot destruction")
- });
-
- // Only remove the group if all of the following is true:
- //
- // - we can lock it: if we can't lock the group, it is still in use (either by another
- // backup process or a parent caller (who needs to take care that empty groups are
- // removed themselves).
- // - it is now empty: if the group isn't empty, removing it will fail (to avoid removing
- // backups that might still be used).
- // - the new locking mechanism is used: if the old mechanism is used, a group removal here
- // could lead to a race condition.
- //
- // Do not error out, as we have already removed the snapshot, there is nothing a user could
- // do to rectify the situation.
- if guard.is_ok() && group.list_backups()?.is_empty() && !*OLD_LOCKING {
- group.remove_group_dir()?;
- } else if let Err(err) = guard {
- log::debug!("{err:#}");
+ if skip_trash {
+ let group = BackupGroup::from(self);
+ let guard = group.lock().with_context(|| {
+ format!("while checking if group '{group:?}' is empty during snapshot destruction")
+ });
+
+ // Only remove the group if all of the following is true:
+ //
+ // - we can lock it: if we can't lock the group, it is still in use (either by another
+ // backup process or a parent caller (who needs to take care that empty groups are
+ // removed themselves).
+ // - it is now empty: if the group isn't empty, removing it will fail (to avoid removing
+ // backups that might still be used).
+ // - the new locking mechanism is used: if the old mechanism is used, a group removal here
+ // could lead to a race condition.
+ //
+ // Do not error out, as we have already removed the snapshot, there is nothing a user could
+ // do to rectify the situation.
+ if guard.is_ok() && group.list_backups()?.is_empty() && !*OLD_LOCKING {
+ group.remove_group_dir()?;
+ } else if let Err(err) = guard {
+ log::debug!("{err:#}");
+ }
}
Ok(())
diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
index cbf78ecb6..6df26e825 100644
--- a/pbs-datastore/src/datastore.rs
+++ b/pbs-datastore/src/datastore.rs
@@ -686,7 +686,7 @@ impl DataStore {
) -> Result<(), Error> {
let backup_dir = self.backup_dir(ns.clone(), backup_dir.clone())?;
- backup_dir.destroy(force)
+ backup_dir.destroy(force, true)
}
/// Returns the time of the last successful backup
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 392494488..aafd1bbd7 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -402,6 +402,12 @@ pub async fn list_snapshot_files(
type: pbs_api_types::BackupDir,
flatten: true,
},
+ "skip-trash": {
+ type: bool,
+ optional: true,
+ default: false,
+ description: "Immediately remove the snapshot, not marking it as trash.",
+ },
},
},
access: {
@@ -415,6 +421,7 @@ pub async fn delete_snapshot(
store: String,
ns: Option<BackupNamespace>,
backup_dir: pbs_api_types::BackupDir,
+ skip_trash: bool,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
@@ -435,7 +442,7 @@ pub async fn delete_snapshot(
let snapshot = datastore.backup_dir(ns, backup_dir)?;
- snapshot.destroy(false)?;
+ snapshot.destroy(false, skip_trash)?;
Ok(Value::Null)
})
@@ -979,6 +986,12 @@ pub fn verify(
optional: true,
description: "Spins up an asynchronous task that does the work.",
},
+ "skip-trash": {
+ type: bool,
+ optional: true,
+ default: false,
+ description: "Immediately remove the snapshot, not marking it as trash.",
+ },
},
},
returns: pbs_api_types::ADMIN_DATASTORE_PRUNE_RETURN_TYPE,
@@ -995,6 +1008,7 @@ pub fn prune(
keep_options: KeepOptions,
store: String,
ns: Option<BackupNamespace>,
+ skip_trash: bool,
param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
@@ -1098,7 +1112,7 @@ pub fn prune(
});
if !keep {
- if let Err(err) = backup_dir.destroy(false) {
+ if let Err(err) = backup_dir.destroy(false, skip_trash) {
warn!(
"failed to remove dir {:?}: {}",
backup_dir.relative_path(),
--
2.39.5
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
next prev parent reply other threads:[~2025-05-08 13:06 UTC|newest]
Thread overview: 51+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-08 13:05 [pbs-devel] [RFC v2 proxmox-backup 00/21] implement trash bin functionality Christian Ebner
2025-05-08 13:05 ` Christian Ebner [this message]
2025-05-09 12:27 ` [pbs-devel] [RFC v2 proxmox-backup 01/21] datastore/api: mark snapshots as trash on destroy Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 02/21] datastore: mark groups " Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 03/21] datastore: allow filtering of backups by their trash status Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 9:32 ` Christian Ebner
2025-05-12 10:08 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 04/21] datastore: ignore trashed snapshots for last successful backup Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 05/21] sync: ignore trashed snapshots when reading from local source Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 06/21] api: tape: check trash marker when trying to write snapshot Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 9:19 ` Christian Ebner
2025-05-12 9:38 ` Fabian Grünbichler
2025-05-12 9:46 ` Christian Ebner
2025-05-12 9:55 ` Christian Ebner
2025-05-12 10:09 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 07/21] sync: ignore trashed groups in local source reader Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 08/21] datastore: namespace: add filter for trash status Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 09/21] datastore: refactor recursive namespace removal Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 10/21] datastore: mark namespace as trash instead of deleting it Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 7:47 ` Christian Ebner
2025-05-12 9:46 ` Fabian Grünbichler
2025-05-12 10:35 ` Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 11/21] datastore: check for trash marker in namespace exists check Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 12/21] datastore: clear trashed snapshot dir if re-creation requested Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 8:31 ` Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 13/21] datastore: recreate trashed backup groups if requested Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 8:05 ` Christian Ebner
2025-05-12 10:02 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 14/21] datastore: GC: clean-up trashed snapshots, groups and namespaces Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 15/21] client: expose skip trash flags for cli commands Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 16/21] api: datastore: add flag to list trashed snapshots only Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-12 7:57 ` Christian Ebner
2025-05-12 10:01 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 17/21] api: namespace: add option to list all namespaces, including trashed Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 18/21] api: admin: implement endpoints to restore trashed contents Christian Ebner
2025-05-09 12:27 ` Fabian Grünbichler
2025-05-09 12:59 ` Christian Ebner
2025-05-12 10:03 ` Fabian Grünbichler
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 19/21] ui: add recover for trashed items tab to datastore panel Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 20/21] ui: drop 'permanent' in group/snapshot forget, default is to trash Christian Ebner
2025-05-08 13:05 ` [pbs-devel] [RFC v2 proxmox-backup 21/21] ui: allow to skip trash on namespace deletion Christian Ebner
2025-05-13 13:54 ` [pbs-devel] superseded: [RFC v2 proxmox-backup 00/21] implement trash bin functionality Christian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250508130555.494782-2-c.ebner@proxmox.com \
--to=c.ebner@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal