mirror of
https://git.proxmox.com/git/proxmox-backup-qemu
synced 2025-10-13 04:57:47 +00:00
cleanup restore api, allow to restore multiple images
This commit is contained in:
parent
9cad27bd53
commit
493e03630b
@ -12,6 +12,9 @@ unsafe impl std::marker::Send for CallbackPointers {}
|
|||||||
pub(crate) struct DataPointer (pub *const u8);
|
pub(crate) struct DataPointer (pub *const u8);
|
||||||
unsafe impl std::marker::Send for DataPointer {}
|
unsafe impl std::marker::Send for DataPointer {}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ProxmoxRestoreHandle;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ProxmoxBackupHandle;
|
pub struct ProxmoxBackupHandle;
|
||||||
|
|
||||||
|
49
src/lib.rs
49
src/lib.rs
@ -57,8 +57,6 @@ pub extern "C" fn proxmox_backup_connect(
|
|||||||
error: * mut * mut c_char,
|
error: * mut * mut c_char,
|
||||||
) -> *mut ProxmoxBackupHandle {
|
) -> *mut ProxmoxBackupHandle {
|
||||||
|
|
||||||
println!("Hello");
|
|
||||||
|
|
||||||
let repo = unsafe { CStr::from_ptr(repo).to_string_lossy().into_owned() };
|
let repo = unsafe { CStr::from_ptr(repo).to_string_lossy().into_owned() };
|
||||||
let repo: BackupRepository = match repo.parse() {
|
let repo: BackupRepository = match repo.parse() {
|
||||||
Ok(repo) => repo,
|
Ok(repo) => repo,
|
||||||
@ -298,22 +296,17 @@ pub extern "C" fn proxmox_backup_disconnect(handle: *mut ProxmoxBackupHandle) {
|
|||||||
///
|
///
|
||||||
/// Note: This implementation is not async
|
/// Note: This implementation is not async
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn proxmox_backup_restore(
|
pub extern "C" fn proxmox_restore_connect(
|
||||||
repo: *const c_char,
|
repo: *const c_char,
|
||||||
snapshot: *const c_char,
|
snapshot: *const c_char,
|
||||||
archive_name: *const c_char, // expect full name here, i.e. "name.img.fidx"
|
|
||||||
keyfile: *const c_char,
|
keyfile: *const c_char,
|
||||||
callback: extern "C" fn(*mut c_void, u64, *const c_uchar, u64) -> c_int,
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
error: * mut * mut c_char,
|
error: * mut * mut c_char,
|
||||||
verbose: bool,
|
) -> *mut ProxmoxRestoreHandle {
|
||||||
) -> c_int {
|
|
||||||
|
|
||||||
let result: Result<_, Error> = try_block!({
|
let result: Result<_, Error> = try_block!({
|
||||||
let repo = unsafe { CStr::from_ptr(repo).to_str()?.to_owned() };
|
let repo = unsafe { CStr::from_ptr(repo).to_str()?.to_owned() };
|
||||||
let repo: BackupRepository = repo.parse()?;
|
let repo: BackupRepository = repo.parse()?;
|
||||||
|
|
||||||
let archive_name = unsafe { CStr::from_ptr(archive_name).to_str()?.to_owned() };
|
|
||||||
let keyfile = if keyfile == std::ptr::null() {
|
let keyfile = if keyfile == std::ptr::null() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -323,14 +316,50 @@ pub extern "C" fn proxmox_backup_restore(
|
|||||||
let snapshot = unsafe { CStr::from_ptr(snapshot).to_string_lossy().into_owned() };
|
let snapshot = unsafe { CStr::from_ptr(snapshot).to_string_lossy().into_owned() };
|
||||||
let snapshot = BackupDir::parse(&snapshot)?;
|
let snapshot = BackupDir::parse(&snapshot)?;
|
||||||
|
|
||||||
|
ProxmoxRestore::new(repo, snapshot, keyfile)
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(conn) => {
|
||||||
|
let boxed_task = Box::new(conn);
|
||||||
|
Box::into_raw(boxed_task) as * mut ProxmoxRestoreHandle
|
||||||
|
}
|
||||||
|
Err(err) => raise_error_null!(error, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn proxmox_restore_disconnect(handle: *mut ProxmoxRestoreHandle) {
|
||||||
|
|
||||||
|
let conn = handle as * mut ProxmoxRestore;
|
||||||
|
unsafe { Box::from_raw(conn) }; //drop(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn proxmox_restore_image(
|
||||||
|
handle: *mut ProxmoxRestoreHandle,
|
||||||
|
archive_name: *const c_char, // expect full name here, i.e. "name.img.fidx"
|
||||||
|
callback: extern "C" fn(*mut c_void, u64, *const c_uchar, u64) -> c_int,
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
error: * mut * mut c_char,
|
||||||
|
verbose: bool,
|
||||||
|
) -> c_int {
|
||||||
|
|
||||||
|
let conn = unsafe { &mut *(handle as * mut ProxmoxRestore) };
|
||||||
|
|
||||||
|
let result: Result<_, Error> = try_block!({
|
||||||
|
|
||||||
|
let archive_name = unsafe { CStr::from_ptr(archive_name).to_str()?.to_owned() };
|
||||||
|
|
||||||
let write_data_callback = move |offset: u64, data: &[u8]| {
|
let write_data_callback = move |offset: u64, data: &[u8]| {
|
||||||
callback(callback_data, offset, data.as_ptr(), data.len() as u64)
|
callback(callback_data, offset, data.as_ptr(), data.len() as u64)
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_zero_callback = move |offset: u64, len: u64| {
|
let write_zero_callback = move |offset: u64, len: u64| {
|
||||||
callback(callback_data, offset, std::ptr::null(), len)
|
callback(callback_data, offset, std::ptr::null(), len)
|
||||||
};
|
};
|
||||||
|
|
||||||
restore(repo, snapshot, archive_name, keyfile, write_data_callback, write_zero_callback, verbose)?;
|
conn.restore(archive_name, write_data_callback, write_zero_callback, verbose)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
171
src/restore.rs
171
src/restore.rs
@ -5,43 +5,106 @@ use std::os::unix::fs::OpenOptionsExt;
|
|||||||
use proxmox_backup::backup::*;
|
use proxmox_backup::backup::*;
|
||||||
use proxmox_backup::client::{HttpClient, BackupReader, BackupRepository, RemoteChunkReader};
|
use proxmox_backup::client::{HttpClient, BackupReader, BackupRepository, RemoteChunkReader};
|
||||||
|
|
||||||
fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
|
pub(crate) struct ProxmoxRestore {
|
||||||
use std::env::VarError::*;
|
pub runtime: tokio::runtime::Runtime,
|
||||||
match std::env::var("PBS_ENCRYPTION_PASSWORD") {
|
pub client: Arc<BackupReader>,
|
||||||
Ok(p) => return Ok(p.as_bytes().to_vec()),
|
pub crypt_config: Option<Arc<CryptConfig>>,
|
||||||
Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
|
pub manifest: BackupManifest,
|
||||||
Err(NotPresent) => {
|
|
||||||
bail!("env PBS_ENCRYPTION_PASSWORD not set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_async(
|
impl ProxmoxRestore {
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
repo: BackupRepository,
|
repo: BackupRepository,
|
||||||
snapshot: BackupDir,
|
snapshot: BackupDir,
|
||||||
|
keyfile: Option<std::path::PathBuf>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
|
||||||
|
let host = repo.host().to_owned();
|
||||||
|
let user = repo.user().to_owned();
|
||||||
|
let store = repo.store().to_owned();
|
||||||
|
let backup_type = snapshot.group().backup_type();
|
||||||
|
let backup_id = snapshot.group().backup_id().to_owned();
|
||||||
|
let backup_time = snapshot.backup_time();
|
||||||
|
|
||||||
|
if backup_type != "vm" {
|
||||||
|
bail!("wrong backup type ({} != vm)", backup_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let crypt_config = match keyfile {
|
||||||
|
None => None,
|
||||||
|
Some(path) => {
|
||||||
|
let (key, _) = load_and_decrtypt_key(&path, get_encryption_key_password)?;
|
||||||
|
Some(Arc::new(CryptConfig::new(key)?))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = tokio::runtime::Builder::new();
|
||||||
|
builder.core_threads(4);
|
||||||
|
builder.name_prefix("pbs-restore-");
|
||||||
|
|
||||||
|
let runtime = builder
|
||||||
|
.build()
|
||||||
|
.map_err(|err| format_err!("create runtime failed - {}", err))?;
|
||||||
|
|
||||||
|
|
||||||
|
let result: Result<_, Error> = runtime.block_on(async {
|
||||||
|
|
||||||
|
let client = HttpClient::new(&host, &user, None)?;
|
||||||
|
let client = BackupReader::start(
|
||||||
|
client,
|
||||||
|
crypt_config.clone(),
|
||||||
|
&store,
|
||||||
|
&backup_type,
|
||||||
|
&backup_id,
|
||||||
|
backup_time,
|
||||||
|
true
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
let manifest = client.download_manifest().await?;
|
||||||
|
|
||||||
|
Ok((client, manifest))
|
||||||
|
});
|
||||||
|
|
||||||
|
let (client, manifest) = result?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
runtime,
|
||||||
|
manifest,
|
||||||
|
client,
|
||||||
|
crypt_config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(
|
||||||
|
&self,
|
||||||
archive_name: String,
|
archive_name: String,
|
||||||
crypt_config: Option<Arc<CryptConfig>>,
|
|
||||||
write_data_callback: impl Fn(u64, &[u8]) -> i32,
|
write_data_callback: impl Fn(u64, &[u8]) -> i32,
|
||||||
write_zero_callback: impl Fn(u64, u64) -> i32,
|
write_zero_callback: impl Fn(u64, u64) -> i32,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
if !archive_name.ends_with(".img.fidx") {
|
if !archive_name.ends_with(".img.fidx") {
|
||||||
bail!("wrong archive type {:?}", archive_name);
|
bail!("wrong archive type {:?}", archive_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = HttpClient::new(repo.host(), repo.user(), None)?;
|
self.runtime.block_on(
|
||||||
let client = BackupReader::start(
|
self.restore_async(
|
||||||
client,
|
archive_name,
|
||||||
crypt_config.clone(),
|
write_data_callback,
|
||||||
repo.store(),
|
write_zero_callback,
|
||||||
snapshot.group().backup_type(),
|
verbose,
|
||||||
snapshot.group().backup_id(),
|
)
|
||||||
snapshot.backup_time(),
|
)
|
||||||
true
|
}
|
||||||
).await?;
|
|
||||||
|
|
||||||
let manifest = client.download_manifest().await?;
|
async fn restore_async(
|
||||||
|
&self,
|
||||||
|
archive_name: String,
|
||||||
|
write_data_callback: impl Fn(u64, &[u8]) -> i32,
|
||||||
|
write_zero_callback: impl Fn(u64, u64) -> i32,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
let tmpfile = std::fs::OpenOptions::new()
|
let tmpfile = std::fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
@ -49,24 +112,28 @@ pub async fn restore_async(
|
|||||||
.custom_flags(libc::O_TMPFILE)
|
.custom_flags(libc::O_TMPFILE)
|
||||||
.open("/tmp")?;
|
.open("/tmp")?;
|
||||||
|
|
||||||
let tmpfile = client.download(&archive_name, tmpfile).await?;
|
let tmpfile = self.client.download(&archive_name, tmpfile).await?;
|
||||||
|
|
||||||
let index = FixedIndexReader::new(tmpfile)
|
let index = FixedIndexReader::new(tmpfile)
|
||||||
.map_err(|err| format_err!("unable to read fixed index '{}' - {}", archive_name, err))?;
|
.map_err(|err| format_err!("unable to read fixed index '{}' - {}", archive_name, err))?;
|
||||||
|
|
||||||
// Note: do not use values stored in index (not trusted) - instead, computed them again
|
// Note: do not use values stored in index (not trusted) - instead, computed them again
|
||||||
let (csum, size) = index.compute_csum();
|
let (csum, size) = index.compute_csum();
|
||||||
manifest.verify_file(&archive_name, &csum, size)?;
|
self.manifest.verify_file(&archive_name, &csum, size)?;
|
||||||
|
|
||||||
let (_, zero_chunk_digest) = DataChunkBuilder::build_zero_chunk(
|
let (_, zero_chunk_digest) = DataChunkBuilder::build_zero_chunk(
|
||||||
crypt_config.as_ref().map(Arc::as_ref),
|
self.crypt_config.as_ref().map(Arc::as_ref),
|
||||||
index.chunk_size,
|
index.chunk_size,
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let most_used = index.find_most_used_chunks(8);
|
let most_used = index.find_most_used_chunks(8);
|
||||||
|
|
||||||
let mut chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, most_used);
|
let mut chunk_reader = RemoteChunkReader::new(
|
||||||
|
self.client.clone(),
|
||||||
|
self.crypt_config.clone(),
|
||||||
|
most_used,
|
||||||
|
);
|
||||||
|
|
||||||
let mut per = 0;
|
let mut per = 0;
|
||||||
let mut bytes = 0;
|
let mut bytes = 0;
|
||||||
@ -113,45 +180,17 @@ pub async fn restore_async(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore(
|
|
||||||
repo: BackupRepository,
|
|
||||||
snapshot: BackupDir,
|
|
||||||
archive_name: String,
|
|
||||||
keyfile: Option<std::path::PathBuf>,
|
|
||||||
write_data_callback: impl Fn(u64, &[u8]) -> i32,
|
|
||||||
write_zero_callback: impl Fn(u64, u64) -> i32,
|
|
||||||
verbose: bool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
|
|
||||||
let crypt_config = match keyfile {
|
|
||||||
None => None,
|
|
||||||
Some(path) => {
|
|
||||||
let (key, _) = load_and_decrtypt_key(&path, get_encryption_key_password)?;
|
|
||||||
Some(Arc::new(CryptConfig::new(key)?))
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut builder = tokio::runtime::Builder::new();
|
}
|
||||||
builder.core_threads(4);
|
|
||||||
builder.name_prefix("pbs-restore-");
|
fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
|
||||||
|
use std::env::VarError::*;
|
||||||
let runtime = builder
|
match std::env::var("PBS_ENCRYPTION_PASSWORD") {
|
||||||
.build()
|
Ok(p) => return Ok(p.as_bytes().to_vec()),
|
||||||
.map_err(|err| format_err!("create runtime failed - {}", err))?;
|
Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
|
||||||
|
Err(NotPresent) => {
|
||||||
let result = runtime.block_on(
|
bail!("env PBS_ENCRYPTION_PASSWORD not set");
|
||||||
restore_async(
|
}
|
||||||
repo,
|
}
|
||||||
snapshot,
|
|
||||||
archive_name,
|
|
||||||
crypt_config,
|
|
||||||
write_data_callback,
|
|
||||||
write_zero_callback,
|
|
||||||
verbose,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user