1 use anyhow
::{format_err, Error}
;
3 use std
::io
::{Seek, SeekFrom, Write}
;
4 use std
::os
::unix
::fs
::OpenOptionsExt
;
7 use futures
::future
::AbortHandle
;
8 use serde_json
::{json, Value}
;
10 use pbs_api_types
::{BackupDir, BackupNamespace}
;
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
;
17 use pbs_datastore
::{BackupManifest, PROXMOX_BACKUP_READER_PROTOCOL_ID_V1}
;
18 use pbs_tools
::crypt_config
::CryptConfig
;
19 use pbs_tools
::sha
::sha256
;
21 use super::{H2Client, HttpClient}
;
24 pub struct BackupReader
{
27 crypt_config
: Option
<Arc
<CryptConfig
>>,
30 impl Drop
for BackupReader
{
37 fn new(h2
: H2Client
, abort
: AbortHandle
, crypt_config
: Option
<Arc
<CryptConfig
>>) -> Arc
<Self> {
45 /// Create a new instance by upgrading the connection at '/api2/json/reader'
48 crypt_config
: Option
<Arc
<CryptConfig
>>,
53 ) -> Result
<Arc
<BackupReader
>, Error
> {
54 let mut param
= json
!({
55 "backup-type": backup
.ty(),
56 "backup-id": backup
.id(),
57 "backup-time": backup
.time
,
63 param
["ns"] = serde_json
::to_value(ns
)?
;
66 let req
= HttpClient
::request_builder(
75 let (h2
, abort
) = client
76 .start_h2_connection(req
, String
::from(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1
!()))
79 Ok(BackupReader
::new(h2
, abort
, crypt_config
))
82 /// Execute a GET request
83 pub async
fn get(&self, path
: &str, param
: Option
<Value
>) -> Result
<Value
, Error
> {
84 self.h2
.get(path
, param
).await
87 /// Execute a PUT request
88 pub async
fn put(&self, path
: &str, param
: Option
<Value
>) -> Result
<Value
, Error
> {
89 self.h2
.put(path
, param
).await
92 /// Execute a POST request
93 pub async
fn post(&self, path
: &str, param
: Option
<Value
>) -> Result
<Value
, Error
> {
94 self.h2
.post(path
, param
).await
97 /// Execute a GET request and send output to a writer
98 pub async
fn download
<W
: Write
+ Send
>(&self, file_name
: &str, output
: W
) -> Result
<(), Error
> {
99 let path
= "download";
100 let param
= json
!({ "file-name": file_name }
);
101 self.h2
.download(path
, Some(param
), output
).await
104 /// Execute a special GET request and send output to a writer
106 /// This writes random data, and is only useful to test download speed.
107 pub async
fn speedtest
<W
: Write
+ Send
>(&self, output
: W
) -> Result
<(), Error
> {
108 self.h2
.download("speedtest", None
, output
).await
111 /// Download a specific chunk
112 pub async
fn download_chunk
<W
: Write
+ Send
>(
116 ) -> Result
<(), Error
> {
118 let param
= json
!({ "digest": hex::encode(digest) }
);
119 self.h2
.download(path
, Some(param
), output
).await
122 pub fn force_close(self) {
126 /// Download backup manifest (index.json)
128 /// The manifest signature is verified if we have a crypt_config.
129 pub async
fn download_manifest(&self) -> Result
<(BackupManifest
, Vec
<u8>), Error
> {
130 let mut raw_data
= Vec
::with_capacity(64 * 1024);
131 self.download(MANIFEST_BLOB_NAME
, &mut raw_data
).await?
;
132 let blob
= DataBlob
::load_from_reader(&mut &raw_data
[..])?
;
133 // no expected digest available
134 let data
= blob
.decode(None
, None
)?
;
137 BackupManifest
::from_data(&data
[..], self.crypt_config
.as_ref().map(Arc
::as_ref
))?
;
142 /// Download a .blob file
144 /// This creates a temporary file in /tmp (using O_TMPFILE). The data is verified using
145 /// the provided manifest.
146 pub async
fn download_blob(
148 manifest
: &BackupManifest
,
150 ) -> Result
<DataBlobReader
<'_
, File
>, Error
> {
151 let mut tmpfile
= std
::fs
::OpenOptions
::new()
154 .custom_flags(libc
::O_TMPFILE
)
157 self.download(name
, &mut tmpfile
).await?
;
159 tmpfile
.seek(SeekFrom
::Start(0))?
;
160 let (csum
, size
) = sha256(&mut tmpfile
)?
;
161 manifest
.verify_file(name
, &csum
, size
)?
;
163 tmpfile
.seek(SeekFrom
::Start(0))?
;
165 DataBlobReader
::new(tmpfile
, self.crypt_config
.clone())
168 /// Download dynamic index file
170 /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using
171 /// the provided manifest.
172 pub async
fn download_dynamic_index(
174 manifest
: &BackupManifest
,
176 ) -> Result
<DynamicIndexReader
, Error
> {
177 let mut tmpfile
= std
::fs
::OpenOptions
::new()
180 .custom_flags(libc
::O_TMPFILE
)
183 self.download(name
, &mut tmpfile
).await?
;
185 let index
= DynamicIndexReader
::new(tmpfile
)
186 .map_err(|err
| format_err
!("unable to read dynamic index '{}' - {}", name
, err
))?
;
188 // Note: do not use values stored in index (not trusted) - instead, computed them again
189 let (csum
, size
) = index
.compute_csum();
190 manifest
.verify_file(name
, &csum
, size
)?
;
195 /// Download fixed index file
197 /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using
198 /// the provided manifest.
199 pub async
fn download_fixed_index(
201 manifest
: &BackupManifest
,
203 ) -> Result
<FixedIndexReader
, Error
> {
204 let mut tmpfile
= std
::fs
::OpenOptions
::new()
207 .custom_flags(libc
::O_TMPFILE
)
210 self.download(name
, &mut tmpfile
).await?
;
212 let index
= FixedIndexReader
::new(tmpfile
)
213 .map_err(|err
| format_err
!("unable to read fixed index '{}' - {}", name
, err
))?
;
215 // Note: do not use values stored in index (not trusted) - instead, computed them again
216 let (csum
, size
) = index
.compute_csum();
217 manifest
.verify_file(name
, &csum
, size
)?
;