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