all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH v3 proxmox-backup 03/20] datastore/api: mark snapshots as trash on destroy
Date: Tue, 13 May 2025 15:52:30 +0200	[thread overview]
Message-ID: <20250513135247.644260-4-c.ebner@proxmox.com> (raw)
In-Reply-To: <20250513135247.644260-1-c.ebner@proxmox.com>

In order to implement the trash 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..aa9202ed5 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 group including all snapshots, 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


  parent reply	other threads:[~2025-05-13 13:53 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-13 13:52 [pbs-devel] [PATCH v3 proxmox proxmox-backup 00/20] implement trash bin functionality Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox 1/20] pbs api types: add type for snapshot list filtering based on trash state Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox 2/20] pbs api types: datastore: add trash marker to snapshot list item Christian Ebner
2025-05-13 13:52 ` Christian Ebner [this message]
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 04/20] datastore: mark groups as trash on destroy Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 05/20] datastore: add helpers to check if snapshot/group is trash Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 06/20] datastore: allow filtering of backups by their trash state Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 07/20] api: datastore: add trash state filtering for snapshot listing Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 08/20] datastore: ignore trashed snapshots for last successful backup Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 09/20] sync: ignore trashed snapshots/groups when reading from local source Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 10/20] api: tape: check trash marker when trying to write snapshot Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 11/20] datastore: clear trashed snapshot dir if re-creation requested Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 12/20] datastore: recover backup group from trash for new backups Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 13/20] datastore: garbage collection: clean-up trashed snapshots and groups Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 14/20] client: expose skip trash flags for cli commands Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 15/20] api: admin: implement endpoints to recover trashed contents Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 16/20] api: admin: move backup group list generation into helper Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 17/20] api: admin: add endpoint to clear trashed items from group Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 18/20] ui: add recover for trashed items tab to datastore panel Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 19/20] ui: drop 'permanent' in group/snapshot forget, default is to trash Christian Ebner
2025-05-13 13:52 ` [pbs-devel] [PATCH v3 proxmox-backup 20/20] ui: mention trash items will be cleared on namespace deletion 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=20250513135247.644260-4-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