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 66D691FF13B for ; Wed, 22 Apr 2026 15:40:37 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6A8F920861; Wed, 22 Apr 2026 15:40:32 +0200 (CEST) From: Hannes Laimer To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v8 02/13] datastore: lift check_namespace_depth_limit to pbs-datastore Date: Wed, 22 Apr 2026 15:39:40 +0200 Message-ID: <20260422133951.192862-3-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260422133951.192862-1-h.laimer@proxmox.com> References: <20260422133951.192862-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1776865110805 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.081 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 Message-ID-Hash: 7LXSDQJOGJ5SV55MBBIGDFLK6YMG2X73 X-Message-ID-Hash: 7LXSDQJOGJ5SV55MBBIGDFLK6YMG2X73 X-MailFrom: h.laimer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Move-namespace needs the same depth check sync (push/pull) already uses. Move it to pbs-datastore as a free helper so both share one implementation. Signed-off-by: Hannes Laimer --- pbs-datastore/src/datastore.rs | 25 ++++++++++++++++++++++++- pbs-datastore/src/lib.rs | 6 +++--- src/server/pull.rs | 9 +++++---- src/server/push.rs | 7 +++---- src/server/sync.rs | 21 --------------------- 5 files changed, 35 insertions(+), 33 deletions(-) diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs index f1475d77..5cdf6a4a 100644 --- a/pbs-datastore/src/datastore.rs +++ b/pbs-datastore/src/datastore.rs @@ -34,7 +34,7 @@ use pbs_api_types::{ ArchiveType, Authid, BackupGroupDeleteStats, BackupNamespace, BackupType, ChunkOrder, DataStoreConfig, DatastoreBackendConfig, DatastoreBackendType, DatastoreFSyncLevel, DatastoreTuning, GarbageCollectionCacheStats, GarbageCollectionStatus, MaintenanceMode, - MaintenanceType, Operation, S3Statistics, UPID, + MaintenanceType, Operation, S3Statistics, MAX_NAMESPACE_DEPTH, UPID, }; use pbs_config::s3::S3_CFG_TYPE_ID; use pbs_config::{BackupLockGuard, ConfigVersionCache}; @@ -89,6 +89,29 @@ const S3_DELETE_BATCH_LIMIT: usize = 100; // max defer time for s3 batch deletions const S3_DELETE_DEFER_LIMIT_SECONDS: Duration = Duration::from_secs(60 * 5); +/// Check that moving/syncing `namespaces` from `source` to `target` stays within +/// `MAX_NAMESPACE_DEPTH`. +pub fn check_namespace_depth_limit( + source: &BackupNamespace, + target: &BackupNamespace, + namespaces: &[BackupNamespace], +) -> Result<(), Error> { + let target_depth = target.depth(); + let sub_depth = namespaces + .iter() + .map(BackupNamespace::depth) + .max() + .map_or(0, |v| v - source.depth()); + + if sub_depth + target_depth > MAX_NAMESPACE_DEPTH { + bail!( + "namespace depth limit exceeded: source subtree depth ({sub_depth}) + target \ + depth ({target_depth}) would exceed max ({MAX_NAMESPACE_DEPTH})", + ); + } + Ok(()) +} + /// checks if auth_id is owner, or, if owner is a token, if /// auth_id is the user of the token pub fn check_backup_owner(owner: &Authid, auth_id: &Authid) -> Result<(), Error> { diff --git a/pbs-datastore/src/lib.rs b/pbs-datastore/src/lib.rs index 9fa9dd59..4d8ac505 100644 --- a/pbs-datastore/src/lib.rs +++ b/pbs-datastore/src/lib.rs @@ -216,9 +216,9 @@ pub use store_progress::StoreProgress; mod datastore; pub use datastore::{ - check_backup_owner, ensure_datastore_is_mounted, get_datastore_mount_status, DataStore, - DataStoreLookup, DatastoreBackend, S3_CLIENT_REQUEST_COUNTER_BASE_PATH, - S3_DATASTORE_IN_USE_MARKER, + check_backup_owner, check_namespace_depth_limit, ensure_datastore_is_mounted, + get_datastore_mount_status, DataStore, DataStoreLookup, DatastoreBackend, + S3_CLIENT_REQUEST_COUNTER_BASE_PATH, S3_DATASTORE_IN_USE_MARKER, }; mod hierarchy; diff --git a/src/server/pull.rs b/src/server/pull.rs index 688f9557..16a59fee 100644 --- a/src/server/pull.rs +++ b/src/server/pull.rs @@ -25,13 +25,14 @@ use pbs_datastore::fixed_index::FixedIndexReader; use pbs_datastore::index::IndexFile; use pbs_datastore::manifest::{BackupManifest, FileInfo}; use pbs_datastore::read_chunk::AsyncReadChunk; -use pbs_datastore::{check_backup_owner, DataStore, DatastoreBackend, StoreProgress}; +use pbs_datastore::{ + check_backup_owner, check_namespace_depth_limit, DataStore, DatastoreBackend, StoreProgress, +}; use pbs_tools::sha::sha256; use super::sync::{ - check_namespace_depth_limit, exclude_not_verified_or_encrypted, - ignore_not_verified_or_encrypted, LocalSource, RemoteSource, RemovedVanishedStats, SkipInfo, - SkipReason, SyncSource, SyncSourceReader, SyncStats, + exclude_not_verified_or_encrypted, ignore_not_verified_or_encrypted, LocalSource, RemoteSource, + RemovedVanishedStats, SkipInfo, SkipReason, SyncSource, SyncSourceReader, SyncStats, }; use crate::backup::{check_ns_modification_privs, check_ns_privs}; use crate::tools::parallel_handler::ParallelHandler; diff --git a/src/server/push.rs b/src/server/push.rs index e69973b4..82319993 100644 --- a/src/server/push.rs +++ b/src/server/push.rs @@ -26,12 +26,11 @@ use pbs_datastore::dynamic_index::DynamicIndexReader; use pbs_datastore::fixed_index::FixedIndexReader; use pbs_datastore::index::IndexFile; use pbs_datastore::read_chunk::AsyncReadChunk; -use pbs_datastore::{DataStore, StoreProgress}; +use pbs_datastore::{check_namespace_depth_limit, DataStore, StoreProgress}; use super::sync::{ - check_namespace_depth_limit, exclude_not_verified_or_encrypted, - ignore_not_verified_or_encrypted, LocalSource, RemovedVanishedStats, SkipInfo, SkipReason, - SyncSource, SyncStats, + exclude_not_verified_or_encrypted, ignore_not_verified_or_encrypted, LocalSource, + RemovedVanishedStats, SkipInfo, SkipReason, SyncSource, SyncStats, }; use crate::api2::config::remote; diff --git a/src/server/sync.rs b/src/server/sync.rs index aedf4a27..47badf1f 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -570,27 +570,6 @@ impl std::fmt::Display for SkipInfo { } } -/// Check if a sync from source to target of given namespaces exceeds the global namespace depth limit -pub(crate) fn check_namespace_depth_limit( - source_namespace: &BackupNamespace, - target_namespace: &BackupNamespace, - namespaces: &[BackupNamespace], -) -> Result<(), Error> { - let target_ns_depth = target_namespace.depth(); - let sync_ns_depth = namespaces - .iter() - .map(BackupNamespace::depth) - .max() - .map_or(0, |v| v - source_namespace.depth()); - - if sync_ns_depth + target_ns_depth > MAX_NAMESPACE_DEPTH { - bail!( - "Syncing would exceed max allowed namespace depth. ({sync_ns_depth}+{target_ns_depth} > {MAX_NAMESPACE_DEPTH})", - ); - } - Ok(()) -} - /// Run a sync job in given direction pub fn do_sync_job( mut job: Job, -- 2.47.3