//! Abstraction layer over different methods of accessing a block backup use anyhow::{bail, Error}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::collections::HashMap; use std::future::Future; use std::hash::BuildHasher; use std::pin::Pin; use proxmox_backup::backup::{BackupDir, BackupManifest}; use proxmox_backup::api2::types::ArchiveEntry; use proxmox_backup::client::BackupRepository; use proxmox::api::{api, cli::*}; use super::block_driver_qemu::QemuBlockDriver; /// Contains details about a snapshot that is to be accessed by block file restore pub struct SnapRestoreDetails { pub repo: BackupRepository, pub snapshot: BackupDir, pub manifest: BackupManifest, } /// Return value of a BlockRestoreDriver.status() call, 'id' must be valid for .stop(id) pub struct DriverStatus { pub id: String, pub data: Value, } pub type Async = Pin + Send>>; /// An abstract implementation for retrieving data out of a block file backup pub trait BlockRestoreDriver { /// List ArchiveEntrys for the given image file and path fn data_list( &self, details: SnapRestoreDetails, img_file: String, path: Vec, ) -> Async, Error>>; /// pxar=true: /// Attempt to create a pxar archive of the given file path and return a reader instance for it /// pxar=false: /// Attempt to read the file or folder at the given path and return the file content or a zip /// file as a stream fn data_extract( &self, details: SnapRestoreDetails, img_file: String, path: Vec, pxar: bool, ) -> Async, Error>>; /// Return status of all running/mapped images, result value is (id, extra data), where id must /// match with the ones returned from list() fn status(&self) -> Async, Error>>; /// Stop/Close a running restore method fn stop(&self, id: String) -> Async>; /// Returned ids must be prefixed with driver type so that they cannot collide between drivers, /// the returned values must be passable to stop() fn list(&self) -> Vec; } #[api()] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] pub enum BlockDriverType { /// Uses a small QEMU/KVM virtual machine to map images securely. Requires PVE-patched QEMU. Qemu, } impl BlockDriverType { fn resolve(&self) -> impl BlockRestoreDriver { match self { BlockDriverType::Qemu => QemuBlockDriver {}, } } } const DEFAULT_DRIVER: BlockDriverType = BlockDriverType::Qemu; const ALL_DRIVERS: &[BlockDriverType] = &[BlockDriverType::Qemu]; pub async fn data_list( driver: Option, details: SnapRestoreDetails, img_file: String, path: Vec, ) -> Result, Error> { let driver = driver.unwrap_or(DEFAULT_DRIVER).resolve(); driver.data_list(details, img_file, path).await } pub async fn data_extract( driver: Option, details: SnapRestoreDetails, img_file: String, path: Vec, pxar: bool, ) -> Result, Error> { let driver = driver.unwrap_or(DEFAULT_DRIVER).resolve(); driver.data_extract(details, img_file, path, pxar).await } #[api( input: { properties: { "driver": { type: BlockDriverType, optional: true, }, "output-format": { schema: OUTPUT_FORMAT, optional: true, }, }, }, )] /// Retrieve status information about currently running/mapped restore images pub async fn status(driver: Option, param: Value) -> Result<(), Error> { let output_format = get_output_format(¶m); let text = output_format == "text"; let mut ret = json!({}); for dt in ALL_DRIVERS { if driver.is_some() && &driver.unwrap() != dt { continue; } let drv_name = format!("{:?}", dt); let drv = dt.resolve(); match drv.status().await { Ok(data) if data.is_empty() => { if text { println!("{}: no mappings", drv_name); } else { ret[drv_name] = json!({}); } } Ok(data) => { if text { println!("{}:", &drv_name); } ret[&drv_name]["ids"] = json!({}); for status in data { if text { println!("{} \t({})", status.id, status.data); } else { ret[&drv_name]["ids"][status.id] = status.data; } } } Err(err) => { if text { eprintln!("error getting status from driver '{}' - {}", drv_name, err); } else { ret[drv_name] = json!({ "error": format!("{}", err) }); } } } } if !text { format_and_print_result(&ret, &output_format); } Ok(()) } #[api( input: { properties: { "name": { type: String, description: "The name of the VM to stop.", }, }, }, )] /// Immediately stop/unmap a given image. Not typically necessary, as VMs will stop themselves /// after a timer anyway. pub async fn stop(name: String) -> Result<(), Error> { for drv in ALL_DRIVERS.iter().map(BlockDriverType::resolve) { if drv.list().contains(&name) { return drv.stop(name).await; } } bail!("no mapping with name '{}' found", name); } /// Autocompletion handler for block mappings pub fn complete_block_driver_ids( _arg: &str, _param: &HashMap, ) -> Vec { ALL_DRIVERS .iter() .map(BlockDriverType::resolve) .map(|d| d.list()) .flatten() .collect() }