]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-file-restore/src/block_driver.rs
update to first proxmox crate split
[proxmox-backup.git] / proxmox-file-restore / src / block_driver.rs
1 //! Abstraction layer over different methods of accessing a block backup
2 use std::collections::HashMap;
3 use std::future::Future;
4 use std::hash::BuildHasher;
5 use std::pin::Pin;
6
7 use anyhow::{bail, Error};
8 use serde::{Deserialize, Serialize};
9 use serde_json::{json, Value};
10
11 use proxmox_router::cli::*;
12 use proxmox_schema::api;
13
14 use pbs_client::BackupRepository;
15 use pbs_datastore::backup_info::BackupDir;
16 use pbs_datastore::catalog::ArchiveEntry;
17 use pbs_datastore::manifest::BackupManifest;
18
19 use super::block_driver_qemu::QemuBlockDriver;
20
21 /// Contains details about a snapshot that is to be accessed by block file restore
22 pub struct SnapRestoreDetails {
23 pub repo: BackupRepository,
24 pub snapshot: BackupDir,
25 pub manifest: BackupManifest,
26 pub keyfile: Option<String>,
27 }
28
29 /// Return value of a BlockRestoreDriver.status() call, 'id' must be valid for .stop(id)
30 pub struct DriverStatus {
31 pub id: String,
32 pub data: Value,
33 }
34
35 pub type Async<R> = Pin<Box<dyn Future<Output = R> + Send>>;
36
37 /// An abstract implementation for retrieving data out of a block file backup
38 pub trait BlockRestoreDriver {
39 /// List ArchiveEntrys for the given image file and path
40 fn data_list(
41 &self,
42 details: SnapRestoreDetails,
43 img_file: String,
44 path: Vec<u8>,
45 ) -> Async<Result<Vec<ArchiveEntry>, Error>>;
46
47 /// pxar=true:
48 /// Attempt to create a pxar archive of the given file path and return a reader instance for it
49 /// pxar=false:
50 /// Attempt to read the file or folder at the given path and return the file content or a zip
51 /// file as a stream
52 fn data_extract(
53 &self,
54 details: SnapRestoreDetails,
55 img_file: String,
56 path: Vec<u8>,
57 pxar: bool,
58 ) -> Async<Result<Box<dyn tokio::io::AsyncRead + Unpin + Send>, Error>>;
59
60 /// Return status of all running/mapped images, result value is (id, extra data), where id must
61 /// match with the ones returned from list()
62 fn status(&self) -> Async<Result<Vec<DriverStatus>, Error>>;
63 /// Stop/Close a running restore method
64 fn stop(&self, id: String) -> Async<Result<(), Error>>;
65 /// Returned ids must be prefixed with driver type so that they cannot collide between drivers,
66 /// the returned values must be passable to stop()
67 fn list(&self) -> Vec<String>;
68 }
69
70 #[api()]
71 #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
72 pub enum BlockDriverType {
73 /// Uses a small QEMU/KVM virtual machine to map images securely. Requires PVE-patched QEMU.
74 Qemu,
75 }
76
77 impl BlockDriverType {
78 fn resolve(&self) -> impl BlockRestoreDriver {
79 match self {
80 BlockDriverType::Qemu => QemuBlockDriver {},
81 }
82 }
83 }
84
85 const DEFAULT_DRIVER: BlockDriverType = BlockDriverType::Qemu;
86 const ALL_DRIVERS: &[BlockDriverType] = &[BlockDriverType::Qemu];
87
88 pub async fn data_list(
89 driver: Option<BlockDriverType>,
90 details: SnapRestoreDetails,
91 img_file: String,
92 path: Vec<u8>,
93 ) -> Result<Vec<ArchiveEntry>, Error> {
94 let driver = driver.unwrap_or(DEFAULT_DRIVER).resolve();
95 driver.data_list(details, img_file, path).await
96 }
97
98 pub async fn data_extract(
99 driver: Option<BlockDriverType>,
100 details: SnapRestoreDetails,
101 img_file: String,
102 path: Vec<u8>,
103 pxar: bool,
104 ) -> Result<Box<dyn tokio::io::AsyncRead + Send + Unpin>, Error> {
105 let driver = driver.unwrap_or(DEFAULT_DRIVER).resolve();
106 driver.data_extract(details, img_file, path, pxar).await
107 }
108
109 #[api(
110 input: {
111 properties: {
112 "driver": {
113 type: BlockDriverType,
114 optional: true,
115 },
116 "output-format": {
117 schema: OUTPUT_FORMAT,
118 optional: true,
119 },
120 },
121 },
122 )]
123 /// Retrieve status information about currently running/mapped restore images
124 pub async fn status(driver: Option<BlockDriverType>, param: Value) -> Result<(), Error> {
125 let output_format = get_output_format(&param);
126 let text = output_format == "text";
127
128 let mut ret = json!({});
129
130 for dt in ALL_DRIVERS {
131 if driver.is_some() && &driver.unwrap() != dt {
132 continue;
133 }
134
135 let drv_name = format!("{:?}", dt);
136 let drv = dt.resolve();
137 match drv.status().await {
138 Ok(data) if data.is_empty() => {
139 if text {
140 println!("{}: no mappings", drv_name);
141 } else {
142 ret[drv_name] = json!({});
143 }
144 }
145 Ok(data) => {
146 if text {
147 println!("{}:", &drv_name);
148 }
149
150 ret[&drv_name]["ids"] = json!({});
151 for status in data {
152 if text {
153 println!("{} \t({})", status.id, status.data);
154 } else {
155 ret[&drv_name]["ids"][status.id] = status.data;
156 }
157 }
158 }
159 Err(err) => {
160 if text {
161 eprintln!("error getting status from driver '{}' - {}", drv_name, err);
162 } else {
163 ret[drv_name] = json!({ "error": format!("{}", err) });
164 }
165 }
166 }
167 }
168
169 if !text {
170 format_and_print_result(&ret, &output_format);
171 }
172
173 Ok(())
174 }
175
176 #[api(
177 input: {
178 properties: {
179 "name": {
180 type: String,
181 description: "The name of the VM to stop.",
182 },
183 },
184 },
185 )]
186 /// Immediately stop/unmap a given image. Not typically necessary, as VMs will stop themselves
187 /// after a timer anyway.
188 pub async fn stop(name: String) -> Result<(), Error> {
189 for drv in ALL_DRIVERS.iter().map(BlockDriverType::resolve) {
190 if drv.list().contains(&name) {
191 return drv.stop(name).await;
192 }
193 }
194
195 bail!("no mapping with name '{}' found", name);
196 }
197
198 /// Autocompletion handler for block mappings
199 pub fn complete_block_driver_ids<S: BuildHasher>(
200 _arg: &str,
201 _param: &HashMap<String, String, S>,
202 ) -> Vec<String> {
203 ALL_DRIVERS
204 .iter()
205 .map(BlockDriverType::resolve)
206 .map(|d| d.list())
207 .flatten()
208 .collect()
209 }