From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id E08FD1FF13F for ; Wed, 14 Jan 2026 13:31:56 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id ADFE5118BA; Wed, 14 Jan 2026 13:31:58 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Date: Wed, 14 Jan 2026 13:31:36 +0100 Message-ID: <20260114123139.505214-4-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260114123139.505214-1-c.ebner@proxmox.com> References: <20260114123139.505214-1-c.ebner@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1768393868951 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.046 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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 v3 3/6] chunk store: return chunk extension and restrict chunk filename check 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" Clearly distinguish the cases for regular, bad and chunk marker files by returning the respective extension as ChunkExt enum variant in the chunk iterator. Restrict the filenames to not only consist of 64 hexdigits and match size and ending, but rather check that each individual byte in the filename matches, including the bad chunk number and the marker file extension. Directory entries which no longer match the stricter criteria are now skipped over. Signed-off-by: Christian Ebner --- changes since version 2: - refactor to not duplicate the is_ascii_hexdigit check for better readability - stricter extension checks to truly match all expected bytes in fileaname - inline trivial extension type checks instead of introducing helpers pbs-datastore/src/chunk_store.rs | 58 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/pbs-datastore/src/chunk_store.rs b/pbs-datastore/src/chunk_store.rs index 084f62b80..5148d6c46 100644 --- a/pbs-datastore/src/chunk_store.rs +++ b/pbs-datastore/src/chunk_store.rs @@ -280,7 +280,11 @@ impl ChunkStore { &self, ) -> Result< impl std::iter::FusedIterator< - Item = (Result, usize, bool), + Item = ( + Result, + usize, + ChunkExt, + ), >, Error, > { @@ -315,21 +319,47 @@ impl ChunkStore { Some(Ok(entry)) => { // skip files if they're not a hash let bytes = entry.file_name().to_bytes(); - if bytes.len() != 64 && bytes.len() != 64 + ".0.bad".len() { + + if bytes.len() < 64 { continue; } + if !bytes.iter().take(64).all(u8::is_ascii_hexdigit) { continue; } - let bad = bytes.ends_with(b".bad"); - return Some((Ok(entry), percentage, bad)); + // regular chunk + if bytes.len() == 64 { + return Some((Ok(entry), percentage, ChunkExt::None)); + } + + // i-th bad chunk + if bytes.len() == 64 + ".i.bad".len() + && bytes[64] == b'.' + && bytes[65] >= b'0' + && bytes[65] <= b'9' + && bytes[66] == b'.' + && bytes.ends_with(b"bad") + { + return Some((Ok(entry), percentage, ChunkExt::Bad)); + } + + // chunk marker file + let marker_ext_bytes = USING_MARKER_FILENAME_EXT.as_bytes(); + if bytes.len() == 64 + 1 + marker_ext_bytes.len() + && bytes[64] == b'.' + && bytes.ends_with(marker_ext_bytes) + { + return Some((Ok(entry), percentage, ChunkExt::UsedMarker)); + } + + continue; } Some(Err(err)) => { // stop after first error done = true; // and pass the error through: - return Some((Err(err), percentage, false)); + return Some((Err(err), percentage, ChunkExt::None)); } None => (), // open next directory } @@ -362,7 +392,7 @@ impl ChunkStore { return Some(( Err(format_err!("unable to read subdir '{subdir}' - {err}")), percentage, - false, + ChunkExt::None, )); } } @@ -397,7 +427,8 @@ impl ChunkStore { let mut last_percentage = 0; let mut chunk_count = 0; - for (entry, percentage, bad) in self.get_chunk_store_iterator()? { + for (entry, percentage, chunk_ext) in self.get_chunk_store_iterator()? { + let bad = chunk_ext == ChunkExt::Bad; if last_percentage != percentage { last_percentage = percentage; info!("processed {percentage}% ({chunk_count} chunks)"); @@ -428,10 +459,7 @@ impl ChunkStore { drop(lock); continue; } - if filename - .to_bytes() - .ends_with(USING_MARKER_FILENAME_EXT.as_bytes()) - { + if chunk_ext == ChunkExt::UsedMarker { unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).map_err(|err| { format_err!("unlinking chunk using marker {filename:?} failed - {err}") })?; @@ -903,6 +931,14 @@ impl ChunkStore { } } +#[derive(PartialEq)] +/// Chunk iterator directory entry filename extension +enum ChunkExt { + None, + Bad, + UsedMarker, +} + #[test] fn test_chunk_store1() { let mut path = std::fs::canonicalize(".").unwrap(); // we need absolute path -- 2.47.3 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel