[pbs-devel] [PATCH v4 vma-to-pbs 5/6] Add support for streaming the VMA file via stdin
Filip Schauer
f.schauer at proxmox.com
Tue Mar 5 14:56:44 CET 2024
This allows the user to stream a compressed VMA file directly to a PBS
without the need to extract it to an intermediate .vma file.
Example usage:
zstd -d --stdout vzdump.vma.zstd | vma-to-pbs \
--repository <auth_id at host:port:datastore> \
--vmid 123 \
--password_file pbs_password
Signed-off-by: Filip Schauer <f.schauer at proxmox.com>
---
src/main.rs | 232 +--------------------------------
src/vma.rs | 270 ++++++++++++++------------------------
src/vma2pbs.rs | 348 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 455 insertions(+), 395 deletions(-)
create mode 100644 src/vma2pbs.rs
diff --git a/src/main.rs b/src/main.rs
index b58387b..dc8171e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,235 +1,14 @@
extern crate anyhow;
extern crate clap;
-extern crate proxmox_backup_qemu;
-extern crate proxmox_io;
extern crate proxmox_sys;
-extern crate scopeguard;
-use std::env;
-use std::ffi::{c_char, CStr, CString};
-use std::ptr;
-use std::time::{SystemTime, UNIX_EPOCH};
-
-use anyhow::{anyhow, Context, Result};
+use anyhow::Result;
use clap::{command, Arg, ArgAction};
-use proxmox_backup_qemu::*;
use proxmox_sys::linux::tty;
-use scopeguard::defer;
mod vma;
-use vma::*;
-
-fn backup_vma_to_pbs(
- vma_file_path: String,
- pbs_repository: String,
- backup_id: String,
- pbs_password: String,
- keyfile: Option<String>,
- key_password: Option<String>,
- master_keyfile: Option<String>,
- fingerprint: String,
- compress: bool,
- encrypt: bool,
-) -> Result<()> {
- println!("VMA input file: {}", vma_file_path);
- println!("PBS repository: {}", pbs_repository);
- println!("PBS fingerprint: {}", fingerprint);
- println!("compress: {}", compress);
- println!("encrypt: {}", encrypt);
-
- let backup_time = SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .unwrap()
- .as_secs();
- println!("backup time: {}", backup_time);
-
- let mut pbs_err: *mut c_char = ptr::null_mut();
-
- let pbs_repository_cstr = CString::new(pbs_repository).unwrap();
- let backup_id_cstr = CString::new(backup_id).unwrap();
- let pbs_password_cstr = CString::new(pbs_password).unwrap();
- let fingerprint_cstr = CString::new(fingerprint).unwrap();
- let keyfile_cstr = keyfile.map(|v| CString::new(v).unwrap());
- let keyfile_ptr = keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null());
- let key_password_cstr = key_password.map(|v| CString::new(v).unwrap());
- let key_password_ptr = key_password_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null());
- let master_keyfile_cstr = master_keyfile.map(|v| CString::new(v).unwrap());
- let master_keyfile_ptr = master_keyfile_cstr
- .map(|v| v.as_ptr())
- .unwrap_or(ptr::null());
-
- let pbs = proxmox_backup_new_ns(
- pbs_repository_cstr.as_ptr(),
- ptr::null(),
- backup_id_cstr.as_ptr(),
- backup_time,
- PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE,
- pbs_password_cstr.as_ptr(),
- keyfile_ptr,
- key_password_ptr,
- master_keyfile_ptr,
- true,
- false,
- fingerprint_cstr.as_ptr(),
- &mut pbs_err,
- );
-
- defer! {
- proxmox_backup_disconnect(pbs);
- }
-
- if pbs == ptr::null_mut() {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!("proxmox_backup_new_ns failed: {pbs_err_cstr:?}"));
- }
- }
-
- let connect_result = proxmox_backup_connect(pbs, &mut pbs_err);
-
- if connect_result < 0 {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!("proxmox_backup_connect failed: {pbs_err_cstr:?}"));
- }
- }
-
- let mut vma_reader = VmaReader::new(&vma_file_path)?;
-
- // Handle configs
- let configs = vma_reader.get_configs();
- for (config_name, config_data) in configs {
- println!("CFG: size: {} name: {}", config_data.len(), config_name);
-
- let config_name_cstr = CString::new(config_name).unwrap();
-
- if proxmox_backup_add_config(
- pbs,
- config_name_cstr.as_ptr(),
- config_data.as_ptr(),
- config_data.len() as u64,
- &mut pbs_err,
- ) < 0
- {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!(
- "proxmox_backup_add_config failed: {pbs_err_cstr:?}"
- ));
- }
- }
- }
-
- // Handle block devices
- for device_id in 0..255 {
- let device_name = match vma_reader.get_device_name(device_id) {
- Some(x) => x,
- None => {
- continue;
- }
- };
-
- let device_size = match vma_reader.get_device_size(device_id) {
- Some(x) => x,
- None => {
- continue;
- }
- };
-
- println!(
- "DEV: dev_id={} size: {} devname: {}",
- device_id, device_size, device_name
- );
-
- let device_name_cstr = CString::new(device_name).unwrap();
- let pbs_device_id = proxmox_backup_register_image(
- pbs,
- device_name_cstr.as_ptr(),
- device_size,
- false,
- &mut pbs_err,
- );
-
- if pbs_device_id < 0 {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!(
- "proxmox_backup_register_image failed: {pbs_err_cstr:?}"
- ));
- }
- }
-
- let mut image_chunk_buffer =
- proxmox_io::boxed::zeroed(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE as usize);
- let mut bytes_transferred = 0;
-
- while bytes_transferred < device_size {
- let bytes_left = device_size - bytes_transferred;
- let chunk_size = bytes_left.min(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE);
- println!(
- "Uploading dev_id: {} offset: {:#0X} - {:#0X}",
- device_id,
- bytes_transferred,
- bytes_transferred + chunk_size
- );
-
- let is_zero_chunk = vma_reader
- .read_device_contents(
- device_id,
- &mut image_chunk_buffer[0..chunk_size as usize],
- bytes_transferred,
- )
- .with_context(|| {
- format!(
- "read {} bytes at offset {} from disk {} from VMA file",
- chunk_size, bytes_transferred, device_id
- )
- })?;
-
- let write_data_result = proxmox_backup_write_data(
- pbs,
- pbs_device_id as u8,
- if is_zero_chunk {
- ptr::null()
- } else {
- image_chunk_buffer.as_ptr()
- },
- bytes_transferred,
- chunk_size,
- &mut pbs_err,
- );
-
- if write_data_result < 0 {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!(
- "proxmox_backup_write_data failed: {pbs_err_cstr:?}"
- ));
- }
- }
-
- bytes_transferred += chunk_size;
- }
-
- if proxmox_backup_close_image(pbs, pbs_device_id as u8, &mut pbs_err) < 0 {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!(
- "proxmox_backup_close_image failed: {pbs_err_cstr:?}"
- ));
- }
- }
- }
-
- if proxmox_backup_finish(pbs, &mut pbs_err) < 0 {
- unsafe {
- let pbs_err_cstr = CStr::from_ptr(pbs_err);
- return Err(anyhow!("proxmox_backup_finish failed: {pbs_err_cstr:?}"));
- }
- }
-
- Ok(())
-}
+mod vma2pbs;
+use vma2pbs::backup_vma_to_pbs;
fn main() -> Result<()> {
let matches = command!()
@@ -307,7 +86,8 @@ fn main() -> Result<()> {
let compress = matches.get_flag("compress");
let encrypt = matches.get_flag("encrypt");
- let vma_file_path = matches.get_one::<String>("vma_file").unwrap().to_string();
+ let vma_file_path = matches.get_one::<String>("vma_file");
+
let password_file = matches.get_one::<String>("password_file");
let pbs_password = match password_file {
@@ -331,7 +111,7 @@ fn main() -> Result<()> {
};
backup_vma_to_pbs(
- vma_file_path,
+ vma_file_path.cloned(),
pbs_repository,
vmid,
pbs_password,
diff --git a/src/vma.rs b/src/vma.rs
index 5ec3822..de3045d 100644
--- a/src/vma.rs
+++ b/src/vma.rs
@@ -2,10 +2,9 @@ extern crate anyhow;
extern crate md5;
use std::collections::HashMap;
-use std::fs::File;
-use std::io::{Read, Seek, SeekFrom};
+use std::io::Read;
use std::mem::size_of;
-use std::{cmp, str};
+use std::str;
use anyhow::{anyhow, Result};
use bincode::Options;
@@ -45,6 +44,8 @@ struct VmaHeader {
reserved1: [u8; 4],
#[serde(with = "BigArray")]
pub dev_info: [VmaDeviceInfoHeader; VMA_MAX_DEVICES],
+ #[serde(skip_deserializing, skip_serializing)]
+ blob_buffer: Vec<u8>,
}
#[repr(C)]
@@ -68,52 +69,35 @@ struct VmaExtentHeader {
pub blockinfo: [VmaBlockInfo; VMA_BLOCKS_PER_EXTENT],
}
-#[derive(Clone)]
-struct VmaBlockIndexEntry {
- pub cluster_file_offset: u64,
- pub mask: u16,
-}
-
pub struct VmaReader {
- vma_file: File,
+ vma_file: Box<dyn Read>,
vma_header: VmaHeader,
configs: HashMap<String, String>,
- block_index: Vec<Vec<VmaBlockIndexEntry>>,
- blocks_are_indexed: bool,
}
impl VmaReader {
- pub fn new(vma_file_path: &str) -> Result<Self> {
- let mut vma_file = match File::open(vma_file_path) {
- Err(why) => return Err(anyhow!("couldn't open {}: {}", vma_file_path, why)),
- Ok(file) => file,
- };
-
- let vma_header = Self::read_header(&mut vma_file).unwrap();
- let configs = Self::read_blob_buffer(&mut vma_file, &vma_header).unwrap();
- let block_index: Vec<Vec<VmaBlockIndexEntry>> = (0..256).map(|_| Vec::new()).collect();
+ pub fn new(mut vma_file: impl Read + 'static) -> Result<Self> {
+ let vma_header = Self::read_header(&mut vma_file)?;
+ let configs = Self::read_configs(&vma_header)?;
let instance = Self {
- vma_file,
+ vma_file: Box::new(vma_file),
vma_header,
configs,
- block_index,
- blocks_are_indexed: false,
};
Ok(instance)
}
- fn read_header(vma_file: &mut File) -> Result<VmaHeader> {
- let mut buffer = Vec::with_capacity(size_of::<VmaHeader>());
- buffer.resize(size_of::<VmaHeader>(), 0);
+ fn read_header(vma_file: &mut impl Read) -> Result<VmaHeader> {
+ let mut buffer = vec![0; 12288];
vma_file.read_exact(&mut buffer)?;
let bincode_options = bincode::DefaultOptions::new()
.with_fixint_encoding()
.with_big_endian();
- let vma_header: VmaHeader = bincode_options.deserialize(&buffer)?;
+ let mut vma_header: VmaHeader = bincode_options.deserialize(&buffer)?;
if vma_header.magic != [b'V', b'M', b'A', 0] {
return Err(anyhow!("Invalid magic number"));
@@ -124,7 +108,7 @@ impl VmaReader {
}
buffer.resize(vma_header.header_size as usize, 0);
- vma_file.read_exact(&mut buffer[size_of::<VmaHeader>()..])?;
+ vma_file.read_exact(&mut buffer[12288..])?;
// Fill the MD5 sum field with zeros to compute the MD5 sum
buffer[32..48].fill(0);
@@ -134,89 +118,77 @@ impl VmaReader {
return Err(anyhow!("Wrong VMA header checksum"));
}
- return Ok(vma_header);
- }
-
- fn read_string_from_file(vma_file: &mut File, file_offset: u64) -> Result<String> {
- let mut size_bytes = [0u8; 2];
- vma_file.seek(SeekFrom::Start(file_offset))?;
- vma_file.read_exact(&mut size_bytes)?;
- let size = u16::from_le_bytes(size_bytes) as usize;
- let mut string_bytes = Vec::with_capacity(size - 1);
- string_bytes.resize(size - 1, 0);
- vma_file.read_exact(&mut string_bytes)?;
- let string = str::from_utf8(&string_bytes)?;
+ let blob_buffer = &buffer[12288..vma_header.header_size as usize];
+ vma_header.blob_buffer = blob_buffer.to_vec();
- return Ok(string.to_string());
+ Ok(vma_header)
}
- fn read_blob_buffer(
- vma_file: &mut File,
- vma_header: &VmaHeader,
- ) -> Result<HashMap<String, String>> {
+ fn read_configs(vma_header: &VmaHeader) -> Result<HashMap<String, String>> {
let mut configs = HashMap::new();
for i in 0..VMA_MAX_CONFIGS {
- let config_name_offset = vma_header.config_names[i];
- let config_data_offset = vma_header.config_data[i];
+ let config_name_offset = vma_header.config_names[i] as usize;
+ let config_data_offset = vma_header.config_data[i] as usize;
if config_name_offset == 0 || config_data_offset == 0 {
continue;
}
- let config_name_file_offset =
- (vma_header.blob_buffer_offset + config_name_offset) as u64;
- let config_data_file_offset =
- (vma_header.blob_buffer_offset + config_data_offset) as u64;
- let config_name = Self::read_string_from_file(vma_file, config_name_file_offset)?;
- let config_data = Self::read_string_from_file(vma_file, config_data_file_offset)?;
-
- configs.insert(String::from(config_name), String::from(config_data));
+ let config_name_size_bytes =
+ vma_header.blob_buffer[config_name_offset..config_name_offset + 2].try_into()?;
+ let config_name_size = u16::from_le_bytes(config_name_size_bytes) as usize;
+ let config_name_bytes = &vma_header.blob_buffer
+ [config_name_offset + 2..config_name_offset + 1 + config_name_size];
+ let config_name = str::from_utf8(config_name_bytes);
+ let config_data_size_bytes =
+ vma_header.blob_buffer[config_data_offset..config_data_offset + 2].try_into()?;
+ let config_data_size = u16::from_le_bytes(config_data_size_bytes) as usize;
+ let config_data_bytes = &vma_header.blob_buffer
+ [config_data_offset + 2..config_data_offset + 1 + config_data_size];
+ let config_data = str::from_utf8(config_data_bytes);
+
+ configs.insert(String::from(config_name?), String::from(config_data?));
}
- return Ok(configs);
+ Ok(configs)
}
pub fn get_configs(&self) -> HashMap<String, String> {
- return self.configs.clone();
+ self.configs.clone()
}
- pub fn get_device_name(&mut self, device_id: usize) -> Option<String> {
- if device_id >= VMA_MAX_DEVICES {
- return None;
- }
-
- let device_name_offset = self.vma_header.dev_info[device_id].device_name_offset;
+ pub fn get_device_name(&self, device_id: u8) -> Option<String> {
+ let device_name_offset =
+ self.vma_header.dev_info[device_id as usize].device_name_offset as usize;
if device_name_offset == 0 {
return None;
}
- let device_name_file_offset =
- (self.vma_header.blob_buffer_offset + device_name_offset) as u64;
- let device_name =
- Self::read_string_from_file(&mut self.vma_file, device_name_file_offset).unwrap();
+ let size_bytes = self.vma_header.blob_buffer[device_name_offset..device_name_offset + 2]
+ .try_into()
+ .unwrap();
+ let size = u16::from_le_bytes(size_bytes) as usize;
+ let device_name_bytes =
+ &self.vma_header.blob_buffer[device_name_offset + 2..device_name_offset + 1 + size];
+ let device_name = str::from_utf8(device_name_bytes);
- return Some(device_name.to_string());
+ Some(String::from(device_name.unwrap()))
}
- pub fn get_device_size(&self, device_id: usize) -> Option<u64> {
- if device_id >= VMA_MAX_DEVICES {
- return None;
- }
-
- let dev_info = &self.vma_header.dev_info[device_id];
+ pub fn get_device_size(&self, device_id: u8) -> Option<u64> {
+ let dev_info = &self.vma_header.dev_info[device_id as usize];
if dev_info.device_name_offset == 0 {
return None;
}
- return Some(dev_info.device_size);
+ Some(dev_info.device_size)
}
- fn read_extent_header(vma_file: &mut File) -> Result<VmaExtentHeader> {
- let mut buffer = Vec::with_capacity(size_of::<VmaExtentHeader>());
- buffer.resize(size_of::<VmaExtentHeader>(), 0);
+ fn read_extent_header(mut vma_file: impl Read) -> Result<VmaExtentHeader> {
+ let mut buffer = vec![0; size_of::<VmaExtentHeader>()];
vma_file.read_exact(&mut buffer)?;
let bincode_options = bincode::DefaultOptions::new()
@@ -237,113 +209,73 @@ impl VmaReader {
return Err(anyhow!("Wrong VMA extent header checksum"));
}
- return Ok(vma_extent_header);
+ Ok(vma_extent_header)
}
- fn index_device_clusters(&mut self) -> Result<()> {
- for device_id in 0..255 {
- let device_size = match self.get_device_size(device_id) {
- Some(x) => x,
- None => {
- continue;
- }
- };
+ fn restore_extent<F>(&mut self, func: F) -> Result<()>
+ where
+ F: Fn(u8, u64, Option<Vec<u8>>) -> Result<()>,
+ {
+ let vma_extent_header = Self::read_extent_header(&mut self.vma_file)?;
- let device_cluster_count = (device_size + 4096 * 16 - 1) / (4096 * 16);
+ for i in 0..VMA_BLOCKS_PER_EXTENT {
+ let blockinfo = &vma_extent_header.blockinfo[i];
- let block_index_entry_placeholder = VmaBlockIndexEntry {
- cluster_file_offset: 0,
- mask: 0,
- };
-
- self.block_index[device_id]
- .resize(device_cluster_count as usize, block_index_entry_placeholder);
- }
+ if blockinfo.dev_id == 0 {
+ continue;
+ }
- let mut file_offset = self.vma_header.header_size as u64;
- let vma_file_size = self.vma_file.metadata()?.len();
+ let image_offset = 4096 * 16 * blockinfo.cluster_num as u64;
+ let is_zero = blockinfo.mask == 0;
- while file_offset < vma_file_size {
- self.vma_file.seek(SeekFrom::Start(file_offset))?;
- let vma_extent_header = Self::read_extent_header(&mut self.vma_file)?;
- file_offset += size_of::<VmaExtentHeader>() as u64;
+ let image_chunk_buffer = if is_zero {
+ None
+ } else {
+ let mut image_chunk_buffer = vec![0; 4096 * 16];
- for i in 0..VMA_BLOCKS_PER_EXTENT {
- let blockinfo = &vma_extent_header.blockinfo[i];
+ for j in 0..16 {
+ let block_mask = ((blockinfo.mask >> j) & 1) == 1;
- if blockinfo.dev_id == 0 {
- continue;
+ if block_mask {
+ self.vma_file.read_exact(
+ &mut image_chunk_buffer[4096 * j as usize..4096 * (j + 1) as usize],
+ )?;
+ } else {
+ image_chunk_buffer[4096 * j as usize..4096 * (j + 1) as usize].fill(0);
+ }
}
- let block_index_entry = VmaBlockIndexEntry {
- cluster_file_offset: file_offset,
- mask: blockinfo.mask,
- };
+ Some(image_chunk_buffer)
+ };
- self.block_index[blockinfo.dev_id as usize][blockinfo.cluster_num as usize] =
- block_index_entry;
- file_offset += blockinfo.mask.count_ones() as u64 * 4096;
- }
+ func(blockinfo.dev_id, image_offset, image_chunk_buffer)?;
}
- self.blocks_are_indexed = true;
-
- return Ok(());
+ Ok(())
}
- pub fn read_device_contents(
- &mut self,
- device_id: usize,
- buffer: &mut [u8],
- offset: u64,
- ) -> Result<bool> {
- if device_id >= VMA_MAX_DEVICES {
- return Err(anyhow!("invalid device id {}", device_id));
- }
-
- if offset % (4096 * 16) != 0 {
- return Err(anyhow!("offset is not aligned to 65536"));
- }
-
- // Make sure that the device clusters are already indexed
- if !self.blocks_are_indexed {
- self.index_device_clusters()?;
- }
-
- let this_device_block_index = &self.block_index[device_id];
- let length = cmp::min(
- buffer.len(),
- this_device_block_index.len() * 4096 * 16 - offset as usize,
- );
- let mut buffer_offset = 0;
- let mut buffer_is_zero = true;
-
- while buffer_offset < length {
- let block_index_entry =
- &this_device_block_index[(offset as usize + buffer_offset) / (4096 * 16)];
- self.vma_file
- .seek(SeekFrom::Start(block_index_entry.cluster_file_offset))?;
-
- for i in 0..16 {
- if buffer_offset >= length {
- break;
- }
-
- let block_buffer_end = buffer_offset + cmp::min(length - buffer_offset, 4096);
- let block_mask = ((block_index_entry.mask >> i) & 1) == 1;
-
- if block_mask {
- self.vma_file
- .read_exact(&mut buffer[buffer_offset..block_buffer_end])?;
- buffer_is_zero = false;
- } else {
- buffer[buffer_offset..block_buffer_end].fill(0);
- }
-
- buffer_offset += 4096;
+ pub fn restore<F>(&mut self, func: F) -> Result<()>
+ where
+ F: Fn(u8, u64, Option<Vec<u8>>) -> Result<()>,
+ {
+ loop {
+ match self.restore_extent(&func) {
+ Ok(()) => {}
+ Err(e) => match e.downcast_ref::<std::io::Error>() {
+ Some(ioerr) => {
+ if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
+ break;
+ } else {
+ return Err(anyhow!("Failed to read VMA file: {}", ioerr));
+ }
+ }
+ _ => {
+ return Err(anyhow!("{}", e));
+ }
+ },
}
}
- return Ok(buffer_is_zero);
+ Ok(())
}
}
diff --git a/src/vma2pbs.rs b/src/vma2pbs.rs
new file mode 100644
index 0000000..2451bff
--- /dev/null
+++ b/src/vma2pbs.rs
@@ -0,0 +1,348 @@
+extern crate anyhow;
+extern crate proxmox_backup_qemu;
+extern crate scopeguard;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::ffi::{c_char, CStr, CString};
+use std::fs::File;
+use std::io::{stdin, BufRead, BufReader};
+use std::ptr;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use anyhow::{anyhow, Result};
+use proxmox_backup_qemu::*;
+use scopeguard::defer;
+
+use crate::vma::*;
+
+const VMA_CLUSTER_SIZE: usize = 65536;
+
+pub fn backup_vma_to_pbs(
+ vma_file_path: Option<String>,
+ pbs_repository: String,
+ backup_id: String,
+ pbs_password: String,
+ keyfile: Option<String>,
+ key_password: Option<String>,
+ master_keyfile: Option<String>,
+ fingerprint: String,
+ compress: bool,
+ encrypt: bool,
+) -> Result<()> {
+ println!("PBS repository: {}", pbs_repository);
+ println!("PBS fingerprint: {}", fingerprint);
+ println!("compress: {}", compress);
+ println!("encrypt: {}", encrypt);
+
+ let backup_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
+ println!("backup time: {}", backup_time);
+
+ let mut pbs_err: *mut c_char = ptr::null_mut();
+
+ let pbs_repository_cstr = CString::new(pbs_repository)?;
+ let backup_id_cstr = CString::new(backup_id)?;
+ let pbs_password_cstr = CString::new(pbs_password)?;
+ let fingerprint_cstr = CString::new(fingerprint)?;
+ let keyfile_cstr = keyfile.map(|v| CString::new(v).unwrap());
+ let keyfile_ptr = keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null());
+ let key_password_cstr = key_password.map(|v| CString::new(v).unwrap());
+ let key_password_ptr = key_password_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null());
+ let master_keyfile_cstr = master_keyfile.map(|v| CString::new(v).unwrap());
+ let master_keyfile_ptr = master_keyfile_cstr
+ .map(|v| v.as_ptr())
+ .unwrap_or(ptr::null());
+
+ let pbs = proxmox_backup_new_ns(
+ pbs_repository_cstr.as_ptr(),
+ ptr::null(),
+ backup_id_cstr.as_ptr(),
+ backup_time,
+ PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE,
+ pbs_password_cstr.as_ptr(),
+ keyfile_ptr,
+ key_password_ptr,
+ master_keyfile_ptr,
+ true,
+ false,
+ fingerprint_cstr.as_ptr(),
+ &mut pbs_err,
+ );
+
+ defer! {
+ proxmox_backup_disconnect(pbs);
+ }
+
+ if pbs.is_null() {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!("proxmox_backup_new_ns failed: {pbs_err_cstr:?}"));
+ }
+ }
+
+ let connect_result = proxmox_backup_connect(pbs, &mut pbs_err);
+
+ if connect_result < 0 {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!("proxmox_backup_connect failed: {pbs_err_cstr:?}"));
+ }
+ }
+
+ println!("Connected to Proxmox Backup Server");
+
+ let vma_file: Box<dyn BufRead> = match vma_file_path {
+ Some(vma_file_path) => match File::open(vma_file_path) {
+ Err(why) => return Err(anyhow!("Couldn't open file: {}", why)),
+ Ok(file) => Box::new(BufReader::new(file)),
+ },
+ None => Box::new(BufReader::new(stdin())),
+ };
+ let mut vma_reader = VmaReader::new(vma_file)?;
+
+ let start_transfer_time = SystemTime::now();
+
+ // Handle configs
+ let configs = vma_reader.get_configs();
+ for (config_name, config_data) in configs {
+ println!("CFG: size: {} name: {}", config_data.len(), config_name);
+
+ let config_name_cstr = CString::new(config_name)?;
+
+ if proxmox_backup_add_config(
+ pbs,
+ config_name_cstr.as_ptr(),
+ config_data.as_ptr(),
+ config_data.len() as u64,
+ &mut pbs_err,
+ ) < 0
+ {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!(
+ "proxmox_backup_add_config failed: {pbs_err_cstr:?}"
+ ));
+ }
+ }
+ }
+
+ let mut pbs_device_id_map = [-1i32; 256];
+ let mut device_sizes = [0u64; 256];
+
+ // Handle block devices
+ for device_id in 0..255 {
+ let device_name = match vma_reader.get_device_name(device_id) {
+ Some(x) => x,
+ None => continue,
+ };
+
+ let device_size = match vma_reader.get_device_size(device_id) {
+ Some(x) => x,
+ None => continue,
+ };
+
+ device_sizes[device_id as usize] = device_size;
+
+ println!(
+ "DEV: dev_id={} size: {} devname: {}",
+ device_id, device_size, device_name
+ );
+
+ let device_name_cstr = CString::new(device_name)?;
+ let pbs_device_id = proxmox_backup_register_image(
+ pbs,
+ device_name_cstr.as_ptr(),
+ device_size,
+ false,
+ &mut pbs_err,
+ );
+
+ if pbs_device_id < 0 {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!(
+ "proxmox_backup_register_image failed: {pbs_err_cstr:?}"
+ ));
+ }
+ }
+
+ pbs_device_id_map[device_id as usize] = pbs_device_id;
+ }
+
+ struct ImageChunk {
+ sub_chunks: HashMap<u8, Option<Vec<u8>>>,
+ mask: u64,
+ non_zero_mask: u64,
+ }
+
+ let images_chunks: RefCell<HashMap<u8, HashMap<u64, ImageChunk>>> =
+ RefCell::new(HashMap::new());
+
+ vma_reader.restore(|dev_id, offset, buffer| {
+ let pbs_device_id = pbs_device_id_map[dev_id as usize];
+
+ if pbs_device_id < 0 {
+ return Err(anyhow!(
+ "Reference to unknown device id {} in VMA file",
+ dev_id
+ ));
+ }
+
+ let mut images_chunks = images_chunks.borrow_mut();
+ let image_chunks = images_chunks.get_mut(&dev_id);
+ let pbs_chunk_offset =
+ PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE * (offset / PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE);
+ let sub_chunk_index =
+ ((offset % PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE) / VMA_CLUSTER_SIZE as u64) as u8;
+
+ let pbs_chunk_size =
+ PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE.min(device_sizes[dev_id as usize] - pbs_chunk_offset);
+
+ let pbs_upload_chunk = |pbs_chunk_buffer: Option<&[u8]>| {
+ println!(
+ "Uploading dev_id: {} offset: {:#0X} - {:#0X}",
+ dev_id,
+ pbs_chunk_offset,
+ pbs_chunk_offset + pbs_chunk_size,
+ );
+
+ let mut pbs_err: *mut c_char = ptr::null_mut();
+
+ let write_data_result = proxmox_backup_write_data(
+ pbs,
+ pbs_device_id as u8,
+ match pbs_chunk_buffer {
+ Some(pbs_chunk_buffer) => pbs_chunk_buffer.as_ptr(),
+ None => ptr::null(),
+ },
+ pbs_chunk_offset,
+ pbs_chunk_size,
+ &mut pbs_err,
+ );
+
+ if write_data_result < 0 {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!(
+ "proxmox_backup_write_data failed: {pbs_err_cstr:?}"
+ ));
+ }
+ }
+
+ Ok(())
+ };
+
+ let insert_image_chunk = |image_chunks: &mut HashMap<u64, ImageChunk>,
+ buffer: Option<Vec<u8>>| {
+ let mut sub_chunks: HashMap<u8, Option<Vec<u8>>> = HashMap::new();
+ let mask = 1 << sub_chunk_index;
+ let non_zero_mask = buffer.is_some() as u64;
+ sub_chunks.insert(sub_chunk_index, buffer);
+
+ let image_chunk = ImageChunk {
+ sub_chunks,
+ mask,
+ non_zero_mask,
+ };
+
+ image_chunks.insert(pbs_chunk_offset, image_chunk);
+ };
+
+ match image_chunks {
+ Some(image_chunks) => {
+ let image_chunk = image_chunks.get_mut(&pbs_chunk_offset);
+
+ match image_chunk {
+ Some(image_chunk) => {
+ image_chunk.mask |= 1 << sub_chunk_index;
+ image_chunk.non_zero_mask |= (buffer.is_some() as u64) << sub_chunk_index;
+ image_chunk.sub_chunks.insert(sub_chunk_index, buffer);
+
+ let sub_chunk_count =
+ ((pbs_chunk_size + 65535) / VMA_CLUSTER_SIZE as u64) as u8;
+ let pbs_chunk_mask = 1_u64
+ .checked_shl(sub_chunk_count.into())
+ .unwrap_or(0)
+ .wrapping_sub(1);
+
+ if image_chunk.mask == pbs_chunk_mask {
+ if image_chunk.non_zero_mask == 0 {
+ pbs_upload_chunk(None)?;
+ } else {
+ let mut pbs_chunk_buffer =
+ proxmox_io::boxed::zeroed(pbs_chunk_size as usize);
+
+ for i in 0..sub_chunk_count {
+ let sub_chunk = image_chunk.sub_chunks.get(&i).unwrap();
+ let a = i as usize * VMA_CLUSTER_SIZE;
+ let b = (a + VMA_CLUSTER_SIZE).min(pbs_chunk_size as usize);
+
+ match sub_chunk {
+ Some(sub_chunk) => {
+ pbs_chunk_buffer[a..b]
+ .copy_from_slice(&sub_chunk[0..b - a]);
+ }
+ None => pbs_chunk_buffer[a..b].fill(0),
+ }
+ }
+
+ pbs_upload_chunk(Some(&*pbs_chunk_buffer))?;
+ }
+
+ image_chunks.remove(&pbs_chunk_offset);
+ }
+ }
+ None => {
+ if pbs_chunk_size <= VMA_CLUSTER_SIZE as u64 {
+ pbs_upload_chunk(buffer.as_deref())?;
+ } else {
+ insert_image_chunk(image_chunks, buffer);
+ }
+ }
+ }
+ }
+ None => {
+ if pbs_chunk_size <= VMA_CLUSTER_SIZE as u64 {
+ pbs_upload_chunk(buffer.as_deref())?;
+ } else {
+ let mut image_chunks: HashMap<u64, ImageChunk> = HashMap::new();
+ insert_image_chunk(&mut image_chunks, buffer);
+ images_chunks.insert(dev_id, image_chunks);
+ }
+ }
+ }
+
+ Ok(())
+ })?;
+
+ for pbs_device_id in pbs_device_id_map.iter().take(255) {
+ if *pbs_device_id < 0 {
+ continue;
+ }
+
+ if proxmox_backup_close_image(pbs, *pbs_device_id as u8, &mut pbs_err) < 0 {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!(
+ "proxmox_backup_close_image failed: {pbs_err_cstr:?}"
+ ));
+ }
+ }
+ }
+
+ if proxmox_backup_finish(pbs, &mut pbs_err) < 0 {
+ unsafe {
+ let pbs_err_cstr = CStr::from_ptr(pbs_err);
+ return Err(anyhow!("proxmox_backup_finish failed: {pbs_err_cstr:?}"));
+ }
+ }
+
+ println!(
+ "Backup finished within {}ms",
+ SystemTime::now()
+ .duration_since(start_transfer_time)?
+ .as_millis()
+ );
+
+ Ok(())
+}
--
2.39.2
More information about the pbs-devel
mailing list