]>
Commit | Line | Data |
---|---|---|
b53f6379 | 1 | use anyhow::{format_err, Error}; |
bb19af73 | 2 | use std::fs::File; |
bdfa6370 | 3 | use std::io::{Seek, SeekFrom, Write}; |
c3d84a22 | 4 | use std::os::unix::fs::OpenOptionsExt; |
bdfa6370 | 5 | use std::sync::Arc; |
9e490a74 | 6 | |
dc089345 | 7 | use futures::future::AbortHandle; |
9e490a74 DM |
8 | use serde_json::{json, Value}; |
9 | ||
988d575d | 10 | use pbs_api_types::BackupType; |
75f83c6a WB |
11 | use pbs_datastore::data_blob::DataBlob; |
12 | use pbs_datastore::data_blob_reader::DataBlobReader; | |
13 | use pbs_datastore::dynamic_index::DynamicIndexReader; | |
14 | use pbs_datastore::fixed_index::FixedIndexReader; | |
15 | use pbs_datastore::index::IndexFile; | |
16 | use pbs_datastore::manifest::MANIFEST_BLOB_NAME; | |
bdfa6370 TL |
17 | use pbs_datastore::{BackupManifest, PROXMOX_BACKUP_READER_PROTOCOL_ID_V1}; |
18 | use pbs_tools::crypt_config::CryptConfig; | |
19 | use pbs_tools::sha::sha256; | |
9e490a74 | 20 | |
bdfa6370 | 21 | use super::{H2Client, HttpClient}; |
9e490a74 | 22 | |
913acb41 | 23 | /// Backup Reader |
9e490a74 DM |
24 | pub struct BackupReader { |
25 | h2: H2Client, | |
dc089345 | 26 | abort: AbortHandle, |
296c50ba | 27 | crypt_config: Option<Arc<CryptConfig>>, |
9e490a74 DM |
28 | } |
29 | ||
30 | impl Drop for BackupReader { | |
9e490a74 | 31 | fn drop(&mut self) { |
dc089345 | 32 | self.abort.abort(); |
9e490a74 DM |
33 | } |
34 | } | |
35 | ||
36 | impl BackupReader { | |
dc089345 | 37 | fn new(h2: H2Client, abort: AbortHandle, crypt_config: Option<Arc<CryptConfig>>) -> Arc<Self> { |
bdfa6370 TL |
38 | Arc::new(Self { |
39 | h2, | |
40 | abort, | |
41 | crypt_config, | |
42 | }) | |
9e490a74 DM |
43 | } |
44 | ||
913acb41 | 45 | /// Create a new instance by upgrading the connection at '/api2/json/reader' |
9e490a74 DM |
46 | pub async fn start( |
47 | client: HttpClient, | |
296c50ba | 48 | crypt_config: Option<Arc<CryptConfig>>, |
9e490a74 | 49 | datastore: &str, |
988d575d | 50 | backup_type: BackupType, |
9e490a74 | 51 | backup_id: &str, |
6a7be83e | 52 | backup_time: i64, |
9e490a74 DM |
53 | debug: bool, |
54 | ) -> Result<Arc<BackupReader>, Error> { | |
9e490a74 DM |
55 | let param = json!({ |
56 | "backup-type": backup_type, | |
57 | "backup-id": backup_id, | |
6a7be83e | 58 | "backup-time": backup_time, |
9e490a74 DM |
59 | "store": datastore, |
60 | "debug": debug, | |
61 | }); | |
bdfa6370 TL |
62 | let req = HttpClient::request_builder( |
63 | client.server(), | |
64 | client.port(), | |
65 | "GET", | |
66 | "/api2/json/reader", | |
67 | Some(param), | |
68 | ) | |
69 | .unwrap(); | |
70 | ||
71 | let (h2, abort) = client | |
72 | .start_h2_connection(req, String::from(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!())) | |
73 | .await?; | |
9e490a74 | 74 | |
dc089345 | 75 | Ok(BackupReader::new(h2, abort, crypt_config)) |
9e490a74 DM |
76 | } |
77 | ||
913acb41 | 78 | /// Execute a GET request |
bdfa6370 | 79 | pub async fn get(&self, path: &str, param: Option<Value>) -> Result<Value, Error> { |
9e490a74 DM |
80 | self.h2.get(path, param).await |
81 | } | |
82 | ||
913acb41 | 83 | /// Execute a PUT request |
bdfa6370 | 84 | pub async fn put(&self, path: &str, param: Option<Value>) -> Result<Value, Error> { |
9e490a74 DM |
85 | self.h2.put(path, param).await |
86 | } | |
87 | ||
913acb41 | 88 | /// Execute a POST request |
bdfa6370 | 89 | pub async fn post(&self, path: &str, param: Option<Value>) -> Result<Value, Error> { |
9e490a74 DM |
90 | self.h2.post(path, param).await |
91 | } | |
92 | ||
913acb41 | 93 | /// Execute a GET request and send output to a writer |
bdfa6370 | 94 | pub async fn download<W: Write + Send>(&self, file_name: &str, output: W) -> Result<(), Error> { |
9e490a74 DM |
95 | let path = "download"; |
96 | let param = json!({ "file-name": file_name }); | |
97 | self.h2.download(path, Some(param), output).await | |
98 | } | |
99 | ||
913acb41 DM |
100 | /// Execute a special GET request and send output to a writer |
101 | /// | |
102 | /// This writes random data, and is only useful to test download speed. | |
bdfa6370 | 103 | pub async fn speedtest<W: Write + Send>(&self, output: W) -> Result<(), Error> { |
9e490a74 DM |
104 | self.h2.download("speedtest", None, output).await |
105 | } | |
106 | ||
913acb41 | 107 | /// Download a specific chunk |
9e490a74 DM |
108 | pub async fn download_chunk<W: Write + Send>( |
109 | &self, | |
110 | digest: &[u8; 32], | |
111 | output: W, | |
3d571d55 | 112 | ) -> Result<(), Error> { |
9e490a74 | 113 | let path = "chunk"; |
25877d05 | 114 | let param = json!({ "digest": hex::encode(digest) }); |
9e490a74 DM |
115 | self.h2.download(path, Some(param), output).await |
116 | } | |
117 | ||
118 | pub fn force_close(self) { | |
dc089345 | 119 | self.abort.abort(); |
9e490a74 | 120 | } |
296c50ba DM |
121 | |
122 | /// Download backup manifest (index.json) | |
2107a5ae DM |
123 | /// |
124 | /// The manifest signature is verified if we have a crypt_config. | |
125 | pub async fn download_manifest(&self) -> Result<(BackupManifest, Vec<u8>), Error> { | |
3d571d55 WB |
126 | let mut raw_data = Vec::with_capacity(64 * 1024); |
127 | self.download(MANIFEST_BLOB_NAME, &mut raw_data).await?; | |
39f18b30 | 128 | let blob = DataBlob::load_from_reader(&mut &raw_data[..])?; |
8819d1f2 FG |
129 | // no expected digest available |
130 | let data = blob.decode(None, None)?; | |
b53f6379 | 131 | |
bdfa6370 TL |
132 | let manifest = |
133 | BackupManifest::from_data(&data[..], self.crypt_config.as_ref().map(Arc::as_ref))?; | |
2107a5ae DM |
134 | |
135 | Ok((manifest, data)) | |
296c50ba | 136 | } |
c3d84a22 | 137 | |
bb19af73 DM |
138 | /// Download a .blob file |
139 | /// | |
add5861e | 140 | /// This creates a temporary file in /tmp (using O_TMPFILE). The data is verified using |
bb19af73 DM |
141 | /// the provided manifest. |
142 | pub async fn download_blob( | |
143 | &self, | |
144 | manifest: &BackupManifest, | |
145 | name: &str, | |
90ff75f8 | 146 | ) -> Result<DataBlobReader<'_, File>, Error> { |
3d571d55 | 147 | let mut tmpfile = std::fs::OpenOptions::new() |
bb19af73 DM |
148 | .write(true) |
149 | .read(true) | |
150 | .custom_flags(libc::O_TMPFILE) | |
151 | .open("/tmp")?; | |
152 | ||
3d571d55 | 153 | self.download(name, &mut tmpfile).await?; |
bb19af73 | 154 | |
ba0ccc59 WB |
155 | tmpfile.seek(SeekFrom::Start(0))?; |
156 | let (csum, size) = sha256(&mut tmpfile)?; | |
bb19af73 DM |
157 | manifest.verify_file(name, &csum, size)?; |
158 | ||
159 | tmpfile.seek(SeekFrom::Start(0))?; | |
160 | ||
161 | DataBlobReader::new(tmpfile, self.crypt_config.clone()) | |
162 | } | |
163 | ||
c3d84a22 DM |
164 | /// Download dynamic index file |
165 | /// | |
add5861e | 166 | /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using |
c3d84a22 DM |
167 | /// the provided manifest. |
168 | pub async fn download_dynamic_index( | |
169 | &self, | |
170 | manifest: &BackupManifest, | |
171 | name: &str, | |
172 | ) -> Result<DynamicIndexReader, Error> { | |
3d571d55 | 173 | let mut tmpfile = std::fs::OpenOptions::new() |
c3d84a22 DM |
174 | .write(true) |
175 | .read(true) | |
176 | .custom_flags(libc::O_TMPFILE) | |
177 | .open("/tmp")?; | |
178 | ||
3d571d55 | 179 | self.download(name, &mut tmpfile).await?; |
c3d84a22 DM |
180 | |
181 | let index = DynamicIndexReader::new(tmpfile) | |
182 | .map_err(|err| format_err!("unable to read dynamic index '{}' - {}", name, err))?; | |
183 | ||
184 | // Note: do not use values stored in index (not trusted) - instead, computed them again | |
185 | let (csum, size) = index.compute_csum(); | |
186 | manifest.verify_file(name, &csum, size)?; | |
187 | ||
188 | Ok(index) | |
189 | } | |
72050500 DM |
190 | |
191 | /// Download fixed index file | |
192 | /// | |
add5861e | 193 | /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using |
72050500 DM |
194 | /// the provided manifest. |
195 | pub async fn download_fixed_index( | |
196 | &self, | |
197 | manifest: &BackupManifest, | |
198 | name: &str, | |
199 | ) -> Result<FixedIndexReader, Error> { | |
3d571d55 | 200 | let mut tmpfile = std::fs::OpenOptions::new() |
72050500 DM |
201 | .write(true) |
202 | .read(true) | |
203 | .custom_flags(libc::O_TMPFILE) | |
204 | .open("/tmp")?; | |
205 | ||
3d571d55 | 206 | self.download(name, &mut tmpfile).await?; |
72050500 DM |
207 | |
208 | let index = FixedIndexReader::new(tmpfile) | |
209 | .map_err(|err| format_err!("unable to read fixed index '{}' - {}", name, err))?; | |
210 | ||
211 | // Note: do not use values stored in index (not trusted) - instead, computed them again | |
212 | let (csum, size) = index.compute_csum(); | |
213 | manifest.verify_file(name, &csum, size)?; | |
214 | ||
215 | Ok(index) | |
216 | } | |
9e490a74 | 217 | } |