From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id BF5C81FF187 for ; Mon, 3 Nov 2025 12:31:10 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B2D8A1A759; Mon, 3 Nov 2025 12:31:45 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Date: Mon, 3 Nov 2025 12:31:11 +0100 Message-ID: <20251103113120.239455-9-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251103113120.239455-1-c.ebner@proxmox.com> References: <20251103113120.239455-1-c.ebner@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1762169484300 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.047 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup 08/17] verify/datastore: make rename corrupt chunk a datastore helper method X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" By making the rename a helper of the datastore, within this method it will become possible to access the inner chunk store for locking. That will be required to correctly lock the store to avoid concurrent chunk inserts and garbage collection operations during the rename, to guarantee consistency on datastores with s3 backend. This is a preparatory patch, no functional changes intended. Signed-off-by: Christian Ebner --- pbs-datastore/src/datastore.rs | 70 +++++++++++++++++++++++++++++++ src/backup/verify.rs | 75 +--------------------------------- 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs index 9008d8fc6..ebfbf5229 100644 --- a/pbs-datastore/src/datastore.rs +++ b/pbs-datastore/src/datastore.rs @@ -2567,4 +2567,74 @@ impl DataStore { .map_err(|err| format_err!("unable to update manifest blob - {err}"))?; Ok(()) } + + pub fn rename_corrupt_chunk(&self, digest: &[u8; 32]) { + let (path, digest_str) = self.chunk_path(digest); + + let mut counter = 0; + let mut new_path = path.clone(); + loop { + new_path.set_file_name(format!("{}.{}.bad", digest_str, counter)); + if new_path.exists() && counter < 9 { + counter += 1; + } else { + break; + } + } + + let backend = match self.backend() { + Ok(backend) => backend, + Err(err) => { + info!( + "failed to get backend while trying to rename bad chunk: {digest_str} - {err}" + ); + return; + } + }; + + if let DatastoreBackend::S3(s3_client) = backend { + let suffix = format!(".{}.bad", counter); + let target_key = match crate::s3::object_key_from_digest_with_suffix(digest, &suffix) { + Ok(target_key) => target_key, + Err(err) => { + info!("could not generate target key for corrupt chunk {path:?} - {err}"); + return; + } + }; + let object_key = match crate::s3::object_key_from_digest(digest) { + Ok(object_key) => object_key, + Err(err) => { + info!("could not generate object key for corrupt chunk {path:?} - {err}"); + return; + } + }; + if proxmox_async::runtime::block_on( + s3_client.copy_object(object_key.clone(), target_key), + ) + .is_ok() + { + if proxmox_async::runtime::block_on(s3_client.delete_object(object_key)).is_err() { + info!("failed to delete corrupt chunk on s3 backend: {digest_str}"); + } + } else { + info!("failed to copy corrupt chunk on s3 backend: {digest_str}"); + // Early return to leave the potentially locally cached chunk in the same state as + // on the object store. Verification might have failed because of connection issue + // after all. + return; + } + } + + match std::fs::rename(&path, &new_path) { + Ok(_) => { + info!("corrupt chunk renamed to {:?}", &new_path); + } + Err(err) => { + match err.kind() { + std::io::ErrorKind::NotFound => { /* ignored */ } + _ => info!("could not rename corrupt chunk {:?} - {err}", &path), + } + } + }; + } } diff --git a/src/backup/verify.rs b/src/backup/verify.rs index df77d177b..7fac46e18 100644 --- a/src/backup/verify.rs +++ b/src/backup/verify.rs @@ -76,77 +76,6 @@ impl VerifyWorker { } } - fn rename_corrupt_chunk(datastore: Arc, digest: &[u8; 32]) { - let (path, digest_str) = datastore.chunk_path(digest); - - let mut counter = 0; - let mut new_path = path.clone(); - loop { - new_path.set_file_name(format!("{digest_str}.{counter}.bad")); - if new_path.exists() && counter < 9 { - counter += 1; - } else { - break; - } - } - - let backend = match datastore.backend() { - Ok(backend) => backend, - Err(err) => { - info!( - "failed to get backend while trying to rename bad chunk: {digest_str} - {err}" - ); - return; - } - }; - - if let DatastoreBackend::S3(s3_client) = backend { - let suffix = format!(".{counter}.bad"); - let target_key = - match pbs_datastore::s3::object_key_from_digest_with_suffix(digest, &suffix) { - Ok(target_key) => target_key, - Err(err) => { - info!("could not generate target key for corrupt chunk {path:?} - {err}"); - return; - } - }; - let object_key = match pbs_datastore::s3::object_key_from_digest(digest) { - Ok(object_key) => object_key, - Err(err) => { - info!("could not generate object key for corrupt chunk {path:?} - {err}"); - return; - } - }; - if proxmox_async::runtime::block_on( - s3_client.copy_object(object_key.clone(), target_key), - ) - .is_ok() - { - if proxmox_async::runtime::block_on(s3_client.delete_object(object_key)).is_err() { - info!("failed to delete corrupt chunk on s3 backend: {digest_str}"); - } - } else { - info!("failed to copy corrupt chunk on s3 backend: {digest_str}"); - // Early return to leave the potentially locally cached chunk in the same state as - // on the object store. Verification might have failed because of connection issue - // after all. - return; - } - } - - match std::fs::rename(&path, &new_path) { - Ok(_) => { - info!("corrupt chunk renamed to {:?}", &new_path); - } - Err(err) => { - match err.kind() { - std::io::ErrorKind::NotFound => { /* ignored */ } - _ => info!("could not rename corrupt chunk {:?} - {err}", &path), - } - } - }; - } - fn verify_index_chunks( &self, index: Box, @@ -189,7 +118,7 @@ impl VerifyWorker { corrupt_chunks2.lock().unwrap().insert(digest); info!("{err}"); errors2.fetch_add(1, Ordering::SeqCst); - Self::rename_corrupt_chunk(datastore2.clone(), &digest); + datastore2.rename_corrupt_chunk(&digest); } else { verified_chunks2.lock().unwrap().insert(digest); } @@ -336,7 +265,7 @@ impl VerifyWorker { corrupt_chunks.insert(digest); error!(message); errors.fetch_add(1, Ordering::SeqCst); - Self::rename_corrupt_chunk(self.datastore.clone(), &digest); + self.datastore.rename_corrupt_chunk(&digest); } fn verify_fixed_index(&self, backup_dir: &BackupDir, info: &FileInfo) -> Result<(), Error> { -- 2.47.3 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel