]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-client/src/backup_reader.rs
api-types: introduce BackupType enum and Group/Dir api types
[proxmox-backup.git] / pbs-client / src / backup_reader.rs
CommitLineData
b53f6379 1use anyhow::{format_err, Error};
bb19af73 2use std::fs::File;
bdfa6370 3use std::io::{Seek, SeekFrom, Write};
c3d84a22 4use std::os::unix::fs::OpenOptionsExt;
bdfa6370 5use std::sync::Arc;
9e490a74 6
dc089345 7use futures::future::AbortHandle;
9e490a74
DM
8use serde_json::{json, Value};
9
988d575d 10use pbs_api_types::BackupType;
75f83c6a
WB
11use pbs_datastore::data_blob::DataBlob;
12use pbs_datastore::data_blob_reader::DataBlobReader;
13use pbs_datastore::dynamic_index::DynamicIndexReader;
14use pbs_datastore::fixed_index::FixedIndexReader;
15use pbs_datastore::index::IndexFile;
16use pbs_datastore::manifest::MANIFEST_BLOB_NAME;
bdfa6370
TL
17use pbs_datastore::{BackupManifest, PROXMOX_BACKUP_READER_PROTOCOL_ID_V1};
18use pbs_tools::crypt_config::CryptConfig;
19use pbs_tools::sha::sha256;
9e490a74 20
bdfa6370 21use super::{H2Client, HttpClient};
9e490a74 22
913acb41 23/// Backup Reader
9e490a74
DM
24pub struct BackupReader {
25 h2: H2Client,
dc089345 26 abort: AbortHandle,
296c50ba 27 crypt_config: Option<Arc<CryptConfig>>,
9e490a74
DM
28}
29
30impl Drop for BackupReader {
9e490a74 31 fn drop(&mut self) {
dc089345 32 self.abort.abort();
9e490a74
DM
33 }
34}
35
36impl 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}