From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id DB65D95E04 for ; Wed, 28 Feb 2024 15:03:22 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 56034D093 for ; Wed, 28 Feb 2024 15:02:59 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 28 Feb 2024 15:02:55 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id E834847BFE for ; Wed, 28 Feb 2024 15:02:53 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Date: Wed, 28 Feb 2024 15:02:19 +0100 Message-Id: <20240228140226.1251979-30-c.ebner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240228140226.1251979-1-c.ebner@proxmox.com> References: <20240228140226.1251979-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [RFC proxmox-backup 29/36] client: pxar: add previous reference to archiver 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: , X-List-Received-Date: Wed, 28 Feb 2024 14:03:22 -0000 Read the previous snaphosts manifest and check if a split archive with the same name is given. If so, create the accessor instance to read the previous archive entries to be able to lookup and compare the metata for the entries, allowing to make a decision if the entry is reusable or not. Signed-off-by: Christian Ebner --- pbs-client/src/pxar/create.rs | 45 ++++++++++++--- proxmox-backup-client/src/main.rs | 57 +++++++++++++++++-- .../src/proxmox_restore_daemon/api.rs | 1 + pxar-bin/src/main.rs | 1 + 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/pbs-client/src/pxar/create.rs b/pbs-client/src/pxar/create.rs index 981bde42..6713daf3 100644 --- a/pbs-client/src/pxar/create.rs +++ b/pbs-client/src/pxar/create.rs @@ -138,7 +138,7 @@ impl ReusedChunks { } /// Pxar options for creating a pxar archive/stream -#[derive(Default, Clone)] +#[derive(Default)] pub struct PxarCreateOptions { /// Device/mountpoint st_dev numbers that should be included. None for no limitation. pub device_set: Option>, @@ -150,6 +150,8 @@ pub struct PxarCreateOptions { pub skip_lost_and_found: bool, /// Skip xattrs of files that return E2BIG error pub skip_e2big_xattr: bool, + /// Reference state for partial backups + pub previous_ref: Option, } /// Statefull information of previous backups snapshots for partial backups @@ -249,6 +251,7 @@ struct Archiver { file_copy_buffer: Vec, skip_e2big_xattr: bool, reused_chunks: ReusedChunks, + previous_payload_index: Option, forced_boundaries: Arc>>, } @@ -305,6 +308,14 @@ where MatchType::Exclude, )?); } + let (previous_payload_index, accessor) = if let Some(refs) = options.previous_ref { + ( + Some(refs.payload_index), + refs.accessor.open_root().await.ok(), + ) + } else { + (None, None) + }; let mut archiver = Archiver { feature_flags, @@ -322,11 +333,12 @@ where file_copy_buffer: vec::undefined(4 * 1024 * 1024), skip_e2big_xattr: options.skip_e2big_xattr, reused_chunks: ReusedChunks::new(), + previous_payload_index, forced_boundaries, }; archiver - .archive_dir_contents(&mut encoder, source_dir, true) + .archive_dir_contents(&mut encoder, accessor, source_dir, true) .await?; encoder.finish().await?; Ok(()) @@ -356,6 +368,7 @@ impl Archiver { fn archive_dir_contents<'a, T: SeqWrite + Send>( &'a mut self, encoder: &'a mut Encoder<'_, T>, + mut accessor: Option>>, mut dir: Dir, is_root: bool, ) -> BoxFuture<'a, Result<(), Error>> { @@ -390,9 +403,15 @@ impl Archiver { (self.callback)(&file_entry.path)?; self.path = file_entry.path; - self.add_entry(encoder, dir_fd, &file_entry.name, &file_entry.stat) - .await - .map_err(|err| self.wrap_err(err))?; + self.add_entry( + encoder, + &mut accessor, + dir_fd, + &file_entry.name, + &file_entry.stat, + ) + .await + .map_err(|err| self.wrap_err(err))?; } self.path = old_path; self.entry_counter = entry_counter; @@ -640,6 +659,7 @@ impl Archiver { async fn add_entry( &mut self, encoder: &mut Encoder<'_, T>, + accessor: &mut Option>>, parent: RawFd, c_file_name: &CStr, stat: &FileStat, @@ -729,7 +749,7 @@ impl Archiver { catalog.lock().unwrap().start_directory(c_file_name)?; } let result = self - .add_directory(encoder, dir, c_file_name, &metadata, stat) + .add_directory(encoder, accessor, dir, c_file_name, &metadata, stat) .await; if let Some(ref catalog) = self.catalog { catalog.lock().unwrap().end_directory()?; @@ -782,6 +802,7 @@ impl Archiver { async fn add_directory( &mut self, encoder: &mut Encoder<'_, T>, + accessor: &mut Option>>, dir: Dir, dir_name: &CStr, metadata: &Metadata, @@ -812,7 +833,17 @@ impl Archiver { log::info!("skipping mount point: {:?}", self.path); Ok(()) } else { - self.archive_dir_contents(encoder, dir, false).await + let mut dir_accessor = None; + if let Some(accessor) = accessor.as_mut() { + if let Some(file_entry) = accessor.lookup(dir_name).await? { + if file_entry.entry().is_dir() { + let dir = file_entry.enter_directory().await?; + dir_accessor = Some(dir); + } + } + } + self.archive_dir_contents(encoder, dir_accessor, dir, false) + .await }; self.fs_magic = old_fs_magic; diff --git a/proxmox-backup-client/src/main.rs b/proxmox-backup-client/src/main.rs index 2d48ca85..00608eee 100644 --- a/proxmox-backup-client/src/main.rs +++ b/proxmox-backup-client/src/main.rs @@ -44,10 +44,10 @@ use pbs_client::tools::{ CHUNK_SIZE_SCHEMA, REPO_URL_SCHEMA, }; use pbs_client::{ - delete_ticket_info, parse_backup_specification, view_task_result, BackupReader, - BackupRepository, BackupSpecificationType, BackupStats, BackupWriter, ChunkStream, - FixedChunkStream, HttpClient, PxarBackupStream, RemoteChunkReader, UploadOptions, - BACKUP_SOURCE_SCHEMA, + delete_ticket_info, parse_backup_detection_mode_specification, parse_backup_specification, + view_task_result, BackupReader, BackupRepository, BackupSpecificationType, BackupStats, + BackupWriter, ChunkStream, FixedChunkStream, HttpClient, PxarBackupStream, RemoteChunkReader, + UploadOptions, BACKUP_DETECTION_MODE_SPEC, BACKUP_SOURCE_SCHEMA, }; use pbs_datastore::catalog::{BackupCatalogWriter, CatalogReader, CatalogWriter}; use pbs_datastore::chunk_store::verify_chunk_size; @@ -702,6 +702,10 @@ fn spawn_catalog_upload( schema: TRAFFIC_CONTROL_BURST_SCHEMA, optional: true, }, + "change-detection-mode": { + schema: BACKUP_DETECTION_MODE_SPEC, + optional: true, + }, "exclude": { type: Array, description: "List of paths or patterns for matching files to exclude.", @@ -896,6 +900,9 @@ async fn create_backup( let backup_time = backup_time_opt.unwrap_or_else(epoch_i64); + let detection_mode = param["change-detection-mode"].as_str().unwrap_or("data"); + let detection_mode = parse_backup_detection_mode_specification(detection_mode)?; + let client = connect_rate_limited(&repo, rate_limit)?; record_repository(&repo); @@ -947,6 +954,28 @@ async fn create_backup( } }; + let backup_reader = if detection_mode.is_metadata() { + if let Ok(backup_dir) = + api_datastore_latest_snapshot(&client, repo.store(), &backup_ns, snapshot.group.clone()) + .await + { + BackupReader::start( + &client, + crypt_config.clone(), + repo.store(), + &backup_ns, + &backup_dir, + true, + ) + .await + .ok() + } else { + None + } + } else { + None + }; + let client = BackupWriter::start( client, crypt_config.clone(), @@ -1043,7 +1072,10 @@ async fn create_backup( manifest.add_file(target, stats.size, stats.csum, crypto.mode)?; } (BackupSpecificationType::PXAR, false) => { - let metadata_mode = false; // Until enabled via param + let archives = detection_mode.metadata_archive_names(); + let metadata_mode = detection_mode.is_metadata() + && (archives.contains(&target_base) || archives.is_empty()); + let target = if metadata_mode { format!("{target_base}.meta.didx") } else { @@ -1065,12 +1097,27 @@ async fn create_backup( .unwrap() .start_directory(std::ffi::CString::new(target.as_str())?.as_c_str())?; + let previous_ref = if metadata_mode { + prepare_reference( + &target_base, + extension, + previous_manifest.clone(), + &client, + backup_reader.clone(), + crypt_config.clone(), + ) + .await? + } else { + None + }; + let pxar_options = pbs_client::pxar::PxarCreateOptions { device_set: devices.clone(), patterns: pattern_list.clone(), entries_max: entries_max as usize, skip_lost_and_found, skip_e2big_xattr, + previous_ref, }; let upload_options = UploadOptions { diff --git a/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs b/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs index d912734c..449a7e4c 100644 --- a/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs +++ b/proxmox-restore-daemon/src/proxmox_restore_daemon/api.rs @@ -355,6 +355,7 @@ fn extract( patterns, skip_lost_and_found: false, skip_e2big_xattr: false, + previous_ref: None, }; let pxar_writer = TokioWriter::new(writer); diff --git a/pxar-bin/src/main.rs b/pxar-bin/src/main.rs index 74ee04f7..f3945801 100644 --- a/pxar-bin/src/main.rs +++ b/pxar-bin/src/main.rs @@ -336,6 +336,7 @@ async fn create_archive( patterns, skip_lost_and_found: false, skip_e2big_xattr: false, + previous_ref: None, }; let source = PathBuf::from(source); -- 2.39.2