mirror of
https://git.proxmox.com/git/proxmox-backup-qemu
synced 2025-10-10 18:15:23 +00:00
939 lines
27 KiB
Rust
939 lines
27 KiB
Rust
use anyhow::{format_err, Error};
|
|
use std::ffi::CString;
|
|
use std::ptr;
|
|
use std::os::raw::{c_uchar, c_char, c_int, c_void, c_long};
|
|
use std::sync::{Arc, Mutex, Condvar};
|
|
|
|
use proxmox::try_block;
|
|
use proxmox_backup::backup::BackupDir;
|
|
use proxmox_backup::client::BackupRepository;
|
|
use chrono::{DateTime, Utc, TimeZone};
|
|
|
|
mod capi_types;
|
|
use capi_types::*;
|
|
|
|
mod registry;
|
|
mod upload_queue;
|
|
mod commands;
|
|
|
|
mod backup;
|
|
use backup::*;
|
|
|
|
mod restore;
|
|
use restore::*;
|
|
|
|
mod tools;
|
|
|
|
pub const PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE: u64 = 1024*1024*4;
|
|
|
|
/// Free returned error messages
|
|
///
|
|
/// All calls can return error messages, but they are allocated using
|
|
/// the rust standard library. This call moves ownership back to rust
|
|
/// and free the allocated memory.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_free_error(ptr: * mut c_char) {
|
|
if !ptr.is_null() {
|
|
unsafe { CString::from_raw(ptr); }
|
|
}
|
|
}
|
|
|
|
// Note: UTF8 Strings may contain 0 bytes.
|
|
fn convert_error_to_cstring(err: String) -> CString {
|
|
match CString::new(err) {
|
|
Ok(msg) => msg,
|
|
Err(err) => {
|
|
eprintln!("got error containung 0 bytes: {}", err);
|
|
CString::new("failed to convert error message containing 0 bytes").unwrap()
|
|
},
|
|
}
|
|
}
|
|
|
|
macro_rules! raise_error_null {
|
|
($error:ident, $err:expr) => {{
|
|
let errmsg = convert_error_to_cstring($err.to_string());
|
|
unsafe { *$error = errmsg.into_raw(); }
|
|
return ptr::null_mut();
|
|
}}
|
|
}
|
|
|
|
macro_rules! raise_error_int {
|
|
($error:ident, $err:expr) => {{
|
|
let errmsg = convert_error_to_cstring($err.to_string());
|
|
unsafe { *$error = errmsg.into_raw(); }
|
|
return -1;
|
|
}}
|
|
}
|
|
|
|
/// Returns the text presentation (relative path) for a backup snapshot
|
|
///
|
|
/// The resturned value is allocated with strdup(), and can be freed
|
|
/// with free().
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_snapshot_string(
|
|
backup_type: *const c_char,
|
|
backup_id: *const c_char,
|
|
backup_time: i64,
|
|
error: * mut * mut c_char,
|
|
) -> *const c_char {
|
|
|
|
let snapshot: Result<CString, Error> = try_block!({
|
|
let backup_type: String = tools::utf8_c_string_lossy(backup_type)
|
|
.ok_or_else(|| format_err!("backup_type must not be NULL"))?;
|
|
let backup_id: String = tools::utf8_c_string_lossy(backup_id)
|
|
.ok_or_else(|| format_err!("backup_id must not be NULL"))?;
|
|
|
|
let snapshot = BackupDir::new(backup_type, backup_id, backup_time);
|
|
|
|
Ok(CString::new(format!("{}", snapshot))?)
|
|
});
|
|
|
|
match snapshot {
|
|
Ok(snapshot) => {
|
|
unsafe { libc::strdup(snapshot.as_ptr()) }
|
|
}
|
|
Err(err) => raise_error_null!(error, err),
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct BackupSetup {
|
|
pub host: String,
|
|
pub store: String,
|
|
pub user: String,
|
|
pub chunk_size: u64,
|
|
pub backup_type: String,
|
|
pub backup_id: String,
|
|
pub backup_time: DateTime<Utc>,
|
|
pub password: Option<String>,
|
|
pub keyfile: Option<std::path::PathBuf>,
|
|
pub key_password: Option<String>,
|
|
pub fingerprint: Option<String>,
|
|
}
|
|
|
|
// helper class to implement synchrounous interface
|
|
struct GotResultCondition {
|
|
lock: Mutex<bool>,
|
|
cond: Condvar,
|
|
}
|
|
|
|
impl GotResultCondition {
|
|
|
|
pub fn new() -> Self {
|
|
Self {
|
|
lock: Mutex::new(false),
|
|
cond: Condvar::new(),
|
|
}
|
|
}
|
|
|
|
/// Create CallbackPointers
|
|
///
|
|
/// wait() returns If the contained callback is called.
|
|
pub fn callback_info(
|
|
&mut self,
|
|
result: *mut c_int,
|
|
error: *mut *mut c_char,
|
|
) -> CallbackPointers {
|
|
CallbackPointers {
|
|
callback: Self::wakeup_callback,
|
|
callback_data: (self) as *mut _ as *mut c_void,
|
|
error,
|
|
result: result,
|
|
}
|
|
}
|
|
|
|
/// Waits until the callback from callback_info is called.
|
|
pub fn wait(&mut self) {
|
|
let mut done = self.lock.lock().unwrap();
|
|
while !*done {
|
|
done = self.cond.wait(done).unwrap();
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
extern "C" fn wakeup_callback(
|
|
callback_data: *mut c_void,
|
|
) {
|
|
let callback_data = unsafe { &mut *( callback_data as * mut GotResultCondition) };
|
|
let mut done = callback_data.lock.lock().unwrap();
|
|
*done = true;
|
|
callback_data.cond.notify_one();
|
|
}
|
|
}
|
|
|
|
|
|
/// Create a new instance
|
|
///
|
|
/// Uses `PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE` if `chunk_size` is zero.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_new(
|
|
repo: *const c_char,
|
|
backup_id: *const c_char,
|
|
backup_time: u64,
|
|
chunk_size: u64,
|
|
password: *const c_char,
|
|
keyfile: *const c_char,
|
|
key_password: *const c_char,
|
|
fingerprint: *const c_char,
|
|
error: * mut * mut c_char,
|
|
) -> *mut ProxmoxBackupHandle {
|
|
|
|
let task: Result<_, Error> = try_block!({
|
|
let repo: BackupRepository = tools::utf8_c_string(repo)?
|
|
.ok_or_else(|| format_err!("repo must not be NULL"))?
|
|
.parse()?;
|
|
|
|
let backup_id = tools::utf8_c_string(backup_id)?
|
|
.ok_or_else(|| format_err!("backup_id must not be NULL"))?;
|
|
|
|
let backup_time = Utc.timestamp(backup_time as i64, 0);
|
|
|
|
let password = tools::utf8_c_string(password)?;
|
|
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 setup = BackupSetup {
|
|
host: repo.host().to_owned(),
|
|
user: repo.user().to_owned(),
|
|
store: repo.store().to_owned(),
|
|
chunk_size: if chunk_size > 0 { chunk_size } else { PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE },
|
|
backup_type: String::from("vm"),
|
|
backup_id,
|
|
password,
|
|
backup_time,
|
|
keyfile,
|
|
key_password,
|
|
fingerprint,
|
|
};
|
|
|
|
BackupTask::new(setup)
|
|
});
|
|
|
|
match task {
|
|
Ok(task) => {
|
|
let boxed_task = Box::new(Arc::new(task));
|
|
Box::into_raw(boxed_task) as * mut ProxmoxBackupHandle
|
|
}
|
|
Err(err) => raise_error_null!(error, err),
|
|
}
|
|
}
|
|
|
|
fn backup_handle_to_task(handle: *mut ProxmoxBackupHandle) -> Arc<BackupTask> {
|
|
let task = unsafe { & *(handle as *const Arc<BackupTask>) };
|
|
// increase reference count while we use it inside rust
|
|
task.clone()
|
|
}
|
|
|
|
/// Open connection to the backup server (sync)
|
|
///
|
|
/// Returns:
|
|
/// 0 ... Sucecss (no prevbious backup)
|
|
/// 1 ... Success (found previous backup)
|
|
/// -1 ... Error
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_connect(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
error: *mut *mut c_char,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.connect().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Open connection to the backup server
|
|
///
|
|
/// Returns:
|
|
/// 0 ... Sucecss (no prevbious backup)
|
|
/// 1 ... Success (found previous backup)
|
|
/// -1 ... Error
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_connect_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: *mut *mut c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.connect().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Abort a running backup task
|
|
///
|
|
/// This stops the current backup task. It is still necessary to call
|
|
/// proxmox_backup_disconnect() to close the connection and free
|
|
/// allocated memory.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_abort(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
reason: *const c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
let reason = unsafe { tools::utf8_c_string_lossy_non_null(reason) };
|
|
task.abort(reason);
|
|
}
|
|
|
|
/// Check if we can do incremental backups.
|
|
///
|
|
/// This method compares the csum from last backup manifest with the
|
|
/// checksum stored locally.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_check_incremental(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
device_name: *const c_char, // expect utf8 here
|
|
size: u64,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
if device_name.is_null() { return 0; }
|
|
|
|
let device_name = unsafe { tools::utf8_c_string_lossy_non_null(device_name) };
|
|
|
|
if task.check_incremental(device_name, size) { 1 } else { 0 }
|
|
}
|
|
|
|
/// Register a backup image (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_register_image(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
device_name: *const c_char, // expect utf8 here
|
|
size: u64,
|
|
incremental: bool,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
let device_name = unsafe { tools::utf8_c_string_lossy_non_null(device_name) };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.register_image(device_name, size, incremental).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
/// Register a backup image
|
|
///
|
|
/// Create a new image archive on the backup server
|
|
/// ('<device_name>.img.fidx'). The returned integer is the dev_id
|
|
/// parameter for the proxmox_backup_write_data_async() method.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_register_image_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
device_name: *const c_char, // expect utf8 here
|
|
size: u64,
|
|
incremental: bool,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
|
|
let device_name = unsafe { tools::utf8_c_string_lossy_non_null(device_name) };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.register_image(device_name, size, incremental).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Add a configuration blob to the backup (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_add_config(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
name: *const c_char, // expect utf8 here
|
|
data: *const u8,
|
|
size: u64,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
let name = unsafe { tools::utf8_c_string_lossy_non_null(name) };
|
|
|
|
let data: Vec<u8> = unsafe { std::slice::from_raw_parts(data, size as usize).to_vec() };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.add_config(name, data).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Add a configuration blob to the backup
|
|
///
|
|
/// Create and upload a data blob "<name>.blob".
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_add_config_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
name: *const c_char, // expect utf8 here
|
|
data: *const u8,
|
|
size: u64,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
|
|
let name = unsafe { tools::utf8_c_string_lossy_non_null(name) };
|
|
let data: Vec<u8> = unsafe { std::slice::from_raw_parts(data, size as usize).to_vec() };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.add_config(name, data).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Write data to into a registered image (sync)
|
|
///
|
|
/// Upload a chunk of data for the <dev_id> image.
|
|
///
|
|
/// The data pointer may be NULL in order to write the zero chunk
|
|
/// (only allowed if size == chunk_size)
|
|
///
|
|
/// Returns:
|
|
/// -1: on error
|
|
/// 0: successful, chunk already exists on server, so it was resued
|
|
/// size: successful, chunk uploaded
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_write_data(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
dev_id: u8,
|
|
data: *const u8,
|
|
offset: u64,
|
|
size: u64,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
let data = DataPointer(data);
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.write_data(dev_id, data, offset, size).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Write data to into a registered image
|
|
///
|
|
/// Upload a chunk of data for the <dev_id> image.
|
|
///
|
|
/// The data pointer may be NULL in order to write the zero chunk
|
|
/// (only allowed if size == chunk_size)
|
|
///
|
|
/// Note: The data pointer needs to be valid until the async
|
|
/// opteration is finished.
|
|
///
|
|
/// Returns:
|
|
/// -1: on error
|
|
/// 0: successful, chunk already exists on server, so it was resued
|
|
/// size: successful, chunk uploaded
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_write_data_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
dev_id: u8,
|
|
data: *const u8,
|
|
offset: u64,
|
|
size: u64,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
let data = DataPointer(data);
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.write_data(dev_id, data, offset, size).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Close a registered image (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_close_image(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
dev_id: u8,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let task = backup_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.close_image(dev_id).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Close a registered image
|
|
///
|
|
/// Mark the image as closed. Further writes are not possible.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_close_image_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
dev_id: u8,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let task = backup_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task.close_image(dev_id).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Finish the backup (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_finish(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let task = unsafe { & *(handle as * const Arc<BackupTask>) };
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
let task2 = task.clone();
|
|
task.runtime().spawn(async move {
|
|
let result = task2.finish().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Finish the backup
|
|
///
|
|
/// Finish the backup by creating and uploading the backup manifest.
|
|
/// All registered images have to be closed before calling this.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_finish_async(
|
|
handle: *mut ProxmoxBackupHandle,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let task = unsafe { & *(handle as * const Arc<BackupTask>) };
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
let task2 = task.clone();
|
|
|
|
task.runtime().spawn(async move {
|
|
let result = task2.finish().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Disconnect and free allocated memory
|
|
///
|
|
/// The handle becomes invalid after this call.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_backup_disconnect(handle: *mut ProxmoxBackupHandle) {
|
|
|
|
let task = handle as * mut Arc<BackupTask>;
|
|
|
|
unsafe { Box::from_raw(task) }; // take ownership, drop(task)
|
|
}
|
|
|
|
// Simple interface to restore data
|
|
//
|
|
// currently only implemented for images...
|
|
|
|
fn restore_handle_to_task(handle: *mut ProxmoxRestoreHandle) -> Arc<RestoreTask> {
|
|
let restore_task = unsafe { & *(handle as *const Arc<RestoreTask>) };
|
|
// increase reference count while we use it inside rust
|
|
restore_task.clone()
|
|
}
|
|
|
|
/// Connect the the backup server for restore (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_new(
|
|
repo: *const c_char,
|
|
snapshot: *const c_char,
|
|
password: *const c_char,
|
|
keyfile: *const c_char,
|
|
key_password: *const c_char,
|
|
fingerprint: *const c_char,
|
|
error: * mut * mut c_char,
|
|
) -> *mut ProxmoxRestoreHandle {
|
|
|
|
let result: Result<_, Error> = try_block!({
|
|
let repo: BackupRepository = tools::utf8_c_string(repo)?
|
|
.ok_or_else(|| format_err!("repo must not be NULL"))?
|
|
.parse()?;
|
|
|
|
let snapshot: BackupDir = tools::utf8_c_string_lossy(snapshot)
|
|
.ok_or_else(|| format_err!("snapshot must not be NULL"))?
|
|
.parse()?;
|
|
|
|
let backup_type = snapshot.group().backup_type().to_owned();
|
|
let backup_id = snapshot.group().backup_id().to_owned();
|
|
let backup_time = snapshot.backup_time();
|
|
|
|
let password = tools::utf8_c_string(password)?;
|
|
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 setup = BackupSetup {
|
|
host: repo.host().to_owned(),
|
|
user: repo.user().to_owned(),
|
|
store: repo.store().to_owned(),
|
|
chunk_size: PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE, // not used by restore
|
|
backup_type,
|
|
backup_id,
|
|
password,
|
|
backup_time,
|
|
keyfile,
|
|
key_password,
|
|
fingerprint,
|
|
};
|
|
|
|
RestoreTask::new(setup)
|
|
});
|
|
|
|
match result {
|
|
Ok(restore_task) => {
|
|
let boxed_restore_task = Box::new(Arc::new(restore_task));
|
|
Box::into_raw(boxed_restore_task) as * mut ProxmoxRestoreHandle
|
|
}
|
|
Err(err) => raise_error_null!(error, err),
|
|
}
|
|
}
|
|
|
|
/// Open connection to the backup server (sync)
|
|
///
|
|
/// Returns:
|
|
/// 0 ... Sucecss (no prevbious backup)
|
|
/// -1 ... Error
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_connect(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
error: *mut *mut c_char,
|
|
) -> c_int {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = restore_task.connect().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
/// Open connection to the backup server (async)
|
|
///
|
|
/// Returns:
|
|
/// 0 ... Sucecss (no prevbious backup)
|
|
/// -1 ... Error
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_connect_async(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: *mut *mut c_char,
|
|
) {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = restore_task.connect().await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Disconnect and free allocated memory
|
|
///
|
|
/// The handle becomes invalid after this call.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_disconnect(handle: *mut ProxmoxRestoreHandle) {
|
|
|
|
let restore_task = handle as * mut Arc<RestoreTask>;
|
|
unsafe { Box::from_raw(restore_task) }; //drop(restore_task)
|
|
}
|
|
|
|
/// Restore an image (sync)
|
|
///
|
|
/// Image data is downloaded and sequentially dumped to the callback.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
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 restore_task = restore_handle_to_task(handle);
|
|
|
|
let result: Result<_, Error> = try_block!({
|
|
|
|
let archive_name = tools::utf8_c_string(archive_name)?
|
|
.ok_or_else(|| format_err!("archive_name must not be NULL"))?;
|
|
|
|
let write_data_callback = move |offset: u64, data: &[u8]| {
|
|
callback(callback_data, offset, data.as_ptr(), data.len() as u64)
|
|
};
|
|
|
|
let write_zero_callback = move |offset: u64, len: u64| {
|
|
callback(callback_data, offset, std::ptr::null(), len)
|
|
};
|
|
|
|
proxmox_backup::tools::runtime::block_on(
|
|
restore_task.restore_image(archive_name, write_data_callback, write_zero_callback, verbose)
|
|
)?;
|
|
|
|
Ok(())
|
|
});
|
|
|
|
if let Err(err) = result {
|
|
raise_error_int!(error, err);
|
|
};
|
|
|
|
0
|
|
}
|
|
|
|
/// Retrieve the ID of a handle used to access data in the given archive (sync)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_open_image(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
archive_name: *const c_char,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
|
|
let archive_name = unsafe { tools::utf8_c_string_lossy_non_null(archive_name) };
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = match restore_task.open_image(archive_name).await {
|
|
Ok(res) => Ok(res as i32),
|
|
Err(err) => Err(err)
|
|
};
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Retrieve the ID of a handle used to access data in the given archive (async)
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_open_image_async(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
archive_name: *const c_char,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
let archive_name = unsafe { tools::utf8_c_string_lossy_non_null(archive_name) };
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = match restore_task.open_image(archive_name).await {
|
|
Ok(res) => Ok(res as i32),
|
|
Err(err) => Err(err)
|
|
};
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|
|
|
|
/// Retrieve the length of a given archive handle in bytes
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_get_image_length(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
aid: u8,
|
|
error: * mut * mut c_char,
|
|
) -> c_long {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
let result = restore_task.get_image_length(aid);
|
|
match result {
|
|
Ok(res) => res as i64,
|
|
Err(err) => raise_error_int!(error, err),
|
|
}
|
|
}
|
|
|
|
/// Read data from the backup image at the given offset (sync)
|
|
///
|
|
/// Reads up to size bytes from handle aid at offset. On success,
|
|
/// returns the number of bytes read. (a return of zero indicates end
|
|
/// of file).
|
|
///
|
|
/// Note: It is not an error for a successful call to transfer fewer
|
|
/// bytes than requested.
|
|
#[no_mangle]
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_read_image_at(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
aid: u8,
|
|
data: *mut u8,
|
|
offset: u64,
|
|
size: u64,
|
|
error: * mut * mut c_char,
|
|
) -> c_int {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
|
|
let mut result: c_int = -1;
|
|
|
|
let mut got_result_condition = GotResultCondition::new();
|
|
|
|
let callback_info = got_result_condition.callback_info(&mut result, error);
|
|
let data = DataPointer(data);
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = restore_task.read_image_at(aid, data, offset, size).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
|
|
got_result_condition.wait();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Read data from the backup image at the given offset (async)
|
|
///
|
|
/// Reads up to size bytes from handle aid at offset. On success,
|
|
/// returns the number of bytes read. (a return of zero indicates end
|
|
/// of file).
|
|
///
|
|
/// Note: The data pointer needs to be valid until the async
|
|
/// opteration is finished.
|
|
///
|
|
/// Note: It is not an error for a successful call to transfer fewer
|
|
/// bytes than requested.
|
|
#[no_mangle]
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
pub extern "C" fn proxmox_restore_read_image_at_async(
|
|
handle: *mut ProxmoxRestoreHandle,
|
|
aid: u8,
|
|
data: *mut u8,
|
|
offset: u64,
|
|
size: u64,
|
|
callback: extern "C" fn(*mut c_void),
|
|
callback_data: *mut c_void,
|
|
result: *mut c_int,
|
|
error: * mut * mut c_char,
|
|
) {
|
|
let restore_task = restore_handle_to_task(handle);
|
|
|
|
let callback_info = CallbackPointers { callback, callback_data, error, result };
|
|
let data = DataPointer(data);
|
|
|
|
restore_task.runtime().spawn(async move {
|
|
let result = restore_task.read_image_at(aid, data, offset, size).await;
|
|
callback_info.send_result(result);
|
|
});
|
|
}
|