1 use anyhow
::{format_err, Error}
;
2 use std
::io
::{Write, Seek, SeekFrom}
;
5 use std
::os
::unix
::fs
::OpenOptionsExt
;
7 use futures
::future
::AbortHandle
;
8 use serde_json
::{json, Value}
;
10 use proxmox
::tools
::digest_to_hex
;
12 use pbs_datastore
::{PROXMOX_BACKUP_READER_PROTOCOL_ID_V1, CryptConfig, BackupManifest}
;
13 use pbs_datastore
::data_blob
::DataBlob
;
14 use pbs_datastore
::data_blob_reader
::DataBlobReader
;
15 use pbs_datastore
::dynamic_index
::DynamicIndexReader
;
16 use pbs_datastore
::fixed_index
::FixedIndexReader
;
17 use pbs_datastore
::index
::IndexFile
;
18 use pbs_datastore
::manifest
::MANIFEST_BLOB_NAME
;
19 use pbs_tools
::sha
::sha256
;
21 use super::{HttpClient, H2Client}
;
24 pub struct BackupReader
{
27 crypt_config
: Option
<Arc
<CryptConfig
>>,
30 impl Drop
for BackupReader
{
39 fn new(h2
: H2Client
, abort
: AbortHandle
, crypt_config
: Option
<Arc
<CryptConfig
>>) -> Arc
<Self> {
40 Arc
::new(Self { h2, abort, crypt_config}
)
43 /// Create a new instance by upgrading the connection at '/api2/json/reader'
46 crypt_config
: Option
<Arc
<CryptConfig
>>,
52 ) -> Result
<Arc
<BackupReader
>, Error
> {
55 "backup-type": backup_type
,
56 "backup-id": backup_id
,
57 "backup-time": backup_time
,
61 let req
= HttpClient
::request_builder(client
.server(), client
.port(), "GET", "/api2/json/reader", Some(param
)).unwrap();
63 let (h2
, abort
) = client
.start_h2_connection(req
, String
::from(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1
!())).await?
;
65 Ok(BackupReader
::new(h2
, abort
, crypt_config
))
68 /// Execute a GET request
73 ) -> Result
<Value
, Error
> {
74 self.h2
.get(path
, param
).await
77 /// Execute a PUT request
82 ) -> Result
<Value
, Error
> {
83 self.h2
.put(path
, param
).await
86 /// Execute a POST request
91 ) -> Result
<Value
, Error
> {
92 self.h2
.post(path
, param
).await
95 /// Execute a GET request and send output to a writer
96 pub async
fn download
<W
: Write
+ Send
>(
100 ) -> Result
<(), Error
> {
101 let path
= "download";
102 let param
= json
!({ "file-name": file_name }
);
103 self.h2
.download(path
, Some(param
), output
).await
106 /// Execute a special GET request and send output to a writer
108 /// This writes random data, and is only useful to test download speed.
109 pub async
fn speedtest
<W
: Write
+ Send
>(
112 ) -> Result
<(), Error
> {
113 self.h2
.download("speedtest", None
, output
).await
116 /// Download a specific chunk
117 pub async
fn download_chunk
<W
: Write
+ Send
>(
121 ) -> Result
<(), Error
> {
123 let param
= json
!({ "digest": digest_to_hex(digest) }
);
124 self.h2
.download(path
, Some(param
), output
).await
127 pub fn force_close(self) {
131 /// Download backup manifest (index.json)
133 /// The manifest signature is verified if we have a crypt_config.
134 pub async
fn download_manifest(&self) -> Result
<(BackupManifest
, Vec
<u8>), Error
> {
136 let mut raw_data
= Vec
::with_capacity(64 * 1024);
137 self.download(MANIFEST_BLOB_NAME
, &mut raw_data
).await?
;
138 let blob
= DataBlob
::load_from_reader(&mut &raw_data
[..])?
;
139 // no expected digest available
140 let data
= blob
.decode(None
, None
)?
;
142 let manifest
= BackupManifest
::from_data(&data
[..], self.crypt_config
.as_ref().map(Arc
::as_ref
))?
;
147 /// Download a .blob file
149 /// This creates a temporary file in /tmp (using O_TMPFILE). The data is verified using
150 /// the provided manifest.
151 pub async
fn download_blob(
153 manifest
: &BackupManifest
,
155 ) -> Result
<DataBlobReader
<'_
, File
>, Error
> {
157 let mut tmpfile
= std
::fs
::OpenOptions
::new()
160 .custom_flags(libc
::O_TMPFILE
)
163 self.download(name
, &mut tmpfile
).await?
;
165 tmpfile
.seek(SeekFrom
::Start(0))?
;
166 let (csum
, size
) = sha256(&mut tmpfile
)?
;
167 manifest
.verify_file(name
, &csum
, size
)?
;
169 tmpfile
.seek(SeekFrom
::Start(0))?
;
171 DataBlobReader
::new(tmpfile
, self.crypt_config
.clone())
174 /// Download dynamic index file
176 /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using
177 /// the provided manifest.
178 pub async
fn download_dynamic_index(
180 manifest
: &BackupManifest
,
182 ) -> Result
<DynamicIndexReader
, Error
> {
184 let mut tmpfile
= std
::fs
::OpenOptions
::new()
187 .custom_flags(libc
::O_TMPFILE
)
190 self.download(name
, &mut tmpfile
).await?
;
192 let index
= DynamicIndexReader
::new(tmpfile
)
193 .map_err(|err
| format_err
!("unable to read dynamic index '{}' - {}", name
, err
))?
;
195 // Note: do not use values stored in index (not trusted) - instead, computed them again
196 let (csum
, size
) = index
.compute_csum();
197 manifest
.verify_file(name
, &csum
, size
)?
;
202 /// Download fixed index file
204 /// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using
205 /// the provided manifest.
206 pub async
fn download_fixed_index(
208 manifest
: &BackupManifest
,
210 ) -> Result
<FixedIndexReader
, Error
> {
212 let mut tmpfile
= std
::fs
::OpenOptions
::new()
215 .custom_flags(libc
::O_TMPFILE
)
218 self.download(name
, &mut tmpfile
).await?
;
220 let index
= FixedIndexReader
::new(tmpfile
)
221 .map_err(|err
| format_err
!("unable to read fixed index '{}' - {}", name
, err
))?
;
223 // Note: do not use values stored in index (not trusted) - instead, computed them again
224 let (csum
, size
) = index
.compute_csum();
225 manifest
.verify_file(name
, &csum
, size
)?
;