From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 0451D1FF0B7 for ; Tue, 25 Aug 2026 11:18:25 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id E55FF216D1; Tue, 25 Aug 2026 11:18:07 +0200 (CEST) From: Christian Ebner To: pbs-devel@lists.proxmox.com, pve-devel@lists.proxmox.com Subject: [PATCH proxmox-backup-qemu 5/8] restore: implement `bw-limit` parameter imposing download rate limits Date: Tue, 25 Aug 2026 11:17:37 +0200 Message-ID: <20260825091741.162883-6-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260825091741.162883-1-c.ebner@proxmox.com> References: <20260825091741.162883-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1787649452192 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.716 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: LB4FVXVI72C5PWD56OHJPH44DOLHGK56 X-Message-ID-Hash: LB4FVXVI72C5PWD56OHJPH44DOLHGK56 X-MailFrom: c.ebner@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: Add an optional `bw-limit` parameter to allow setting a download bandwidth limit for pbs-restore. Since this is a breaking api change, expose this in the public api as new function and refactor to keep common code deduplicated. Subsequently this new API function will be extend further by a additional parameter. Signed-off-by: Christian Ebner --- Cargo.toml | 1 + current-api.h | 13 +++++++++++++ src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- src/restore.rs | 9 +++++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 81c46b3..af7a84a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ once_cell = "1.5" openssl = "0.10" proxmox-async = "0.5" +proxmox-human-byte = "1" proxmox-lang = "1" proxmox-schema = { version = "5", features = [ "api-macro" ] } proxmox-sortable-macro = "1" diff --git a/current-api.h b/current-api.h index 60d7558..b9a3a44 100644 --- a/current-api.h +++ b/current-api.h @@ -319,6 +319,19 @@ struct ProxmoxRestoreHandle *proxmox_restore_new_ns(const char *repo, const char *fingerprint, char **error); +/** + * Connect to the backup server for restore (sync) + */ +struct ProxmoxRestoreHandle *proxmox_restore_new_ns_rate_limited(const char *repo, + const char *snapshot, + const char *namespace_, + const char *password, + const char *keyfile, + const char *key_password, + const char *fingerprint, + const char *bw_limit, + char **error); + /** * Open connection to the backup server (sync) * diff --git a/src/lib.rs b/src/lib.rs index 1d4ea21..4d4c28b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,16 @@ use anyhow::{format_err, Error}; use std::ffi::CString; use std::os::raw::{c_char, c_int, c_long, c_uchar, c_void}; -use std::ptr; +use std::ptr::{self, null}; use std::sync::{Arc, Condvar, Mutex}; +use proxmox_human_byte::HumanByte; use proxmox_lang::try_block; -use pbs_api_types::{Authid, BackupArchiveName, BackupDir, BackupNamespace, BackupType, CryptMode}; +use pbs_api_types::{ + Authid, BackupArchiveName, BackupDir, BackupNamespace, BackupType, ClientRateLimitConfig, + CryptMode, +}; use pbs_client::BackupRepository; pub mod capi_types; @@ -140,6 +144,7 @@ pub(crate) struct BackupSetup { pub key_password: Option, pub master_keyfile: Option, pub fingerprint: Option, + pub inbound_rate_limit: Option, } // helper class to implement synchronous interface @@ -304,6 +309,7 @@ pub extern "C" fn proxmox_backup_new_ns( key_password, master_keyfile, fingerprint, + inbound_rate_limit: None, }; BackupTask::new(setup, compress, crypt_mode) @@ -816,6 +822,7 @@ pub extern "C" fn proxmox_restore_new( key_password, master_keyfile: None, fingerprint, + inbound_rate_limit: None, }; RestoreTask::new(setup) @@ -842,6 +849,33 @@ pub extern "C" fn proxmox_restore_new_ns( key_password: *const c_char, fingerprint: *const c_char, error: *mut *mut c_char, +) -> *mut ProxmoxRestoreHandle { + proxmox_restore_new_ns_rate_limited( + repo, + snapshot, + namespace, + password, + keyfile, + key_password, + fingerprint, + null(), + error, + ) +} + +/// Connect to the backup server for restore (sync) +#[no_mangle] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn proxmox_restore_new_ns_rate_limited( + repo: *const c_char, + snapshot: *const c_char, + namespace: *const c_char, + password: *const c_char, + keyfile: *const c_char, + key_password: *const c_char, + fingerprint: *const c_char, + bw_limit: *const c_char, + error: *mut *mut c_char, ) -> *mut ProxmoxRestoreHandle { let result: Result<_, Error> = try_block!({ let repo: BackupRepository = tools::utf8_c_string(repo)? @@ -862,6 +896,16 @@ pub extern "C" fn proxmox_restore_new_ns( let keyfile = tools::utf8_c_string(keyfile)?.map(std::path::PathBuf::from); let key_password = tools::utf8_c_string(key_password)?; let fingerprint = tools::utf8_c_string(fingerprint)?; + let inbound_rate_limit = match tools::utf8_c_string(bw_limit)? { + Some(rate_limit) => { + let rate_limit: HumanByte = rate_limit.parse()?; + Some(ClientRateLimitConfig { + rate: Some(rate_limit), + burst: None, + }) + } + None => None, + }; let setup = BackupSetup { host: repo.host().to_owned(), @@ -876,6 +920,7 @@ pub extern "C" fn proxmox_restore_new_ns( key_password, master_keyfile: None, fingerprint, + inbound_rate_limit, }; RestoreTask::new(setup) diff --git a/src/restore.rs b/src/restore.rs index bbe0016..fff0465 100644 --- a/src/restore.rs +++ b/src/restore.rs @@ -8,7 +8,7 @@ use tokio::runtime::Runtime; use proxmox_async::runtime::get_runtime_with_builder; -use pbs_api_types::BackupArchiveName; +use pbs_api_types::{BackupArchiveName, RateLimitConfig}; use pbs_client::{BackupReader, HttpClient, HttpClientOptions, RemoteChunkReader}; use pbs_datastore::cached_chunk_reader::CachedChunkReader; use pbs_datastore::data_blob::DataChunkBuilder; @@ -91,11 +91,16 @@ impl RestoreTask { } pub async fn connect(&self) -> Result { - let options = HttpClientOptions::new_non_interactive( + let mut options = HttpClientOptions::new_non_interactive( self.setup.password.clone(), self.setup.fingerprint.clone(), ); + if let Some(limit) = self.setup.inbound_rate_limit.clone() { + let limit = RateLimitConfig::from_client_config(limit); + options = options.rate_limit(limit); + } + let http = HttpClient::new( &self.setup.host, self.setup.port, -- 2.47.3