1 use std
::io
::{Seek, SeekFrom}
;
2 use std
::os
::unix
::fs
::OpenOptionsExt
;
5 use anyhow
::{bail, format_err, Error}
;
8 use proxmox_router
::cli
::*;
9 use proxmox_schema
::api
;
11 use pbs_api_types
::BackupNamespace
;
12 use pbs_client
::tools
::key_source
::get_encryption_key_password
;
13 use pbs_client
::{BackupReader, RemoteChunkReader}
;
14 use pbs_tools
::crypt_config
::CryptConfig
;
15 use pbs_tools
::json
::required_string_param
;
18 complete_backup_snapshot
, complete_group_or_snapshot
, complete_namespace
,
19 complete_pxar_archive_name
, complete_repository
, connect
, crypto_parameters
, decrypt_key
,
20 dir_or_last_from_group
, extract_repository_from_value
, format_key_source
, optional_ns_param
,
21 record_repository
, BackupDir
, BufferedDynamicReadAt
, BufferedDynamicReader
, CatalogReader
,
22 DynamicIndexReader
, IndexFile
, Shell
, CATALOG_NAME
, KEYFD_SCHEMA
, REPO_URL_SCHEMA
,
29 schema
: REPO_URL_SCHEMA
,
33 type: BackupNamespace
,
38 description
: "Snapshot path.",
43 description
: "Path to encryption key.",
53 async
fn dump_catalog(param
: Value
) -> Result
<Value
, Error
> {
54 let repo
= extract_repository_from_value(¶m
)?
;
56 let backup_ns
= optional_ns_param(¶m
)?
;
57 let path
= required_string_param(¶m
, "snapshot")?
;
58 let snapshot
: BackupDir
= path
.parse()?
;
60 let crypto
= crypto_parameters(¶m
)?
;
62 let crypt_config
= match crypto
.enc_key
{
65 let (key
, _created
, _fingerprint
) = decrypt_key(&key
.key
, &get_encryption_key_password
)
67 log
::error
!("{}", format_key_source(&key
.source
, "encryption"));
70 let crypt_config
= CryptConfig
::new(key
)?
;
71 Some(Arc
::new(crypt_config
))
75 let client
= connect(&repo
)?
;
77 let client
= BackupReader
::start(
87 let (manifest
, _
) = client
.download_manifest().await?
;
88 manifest
.check_fingerprint(crypt_config
.as_ref().map(Arc
::as_ref
))?
;
91 .download_dynamic_index(&manifest
, CATALOG_NAME
)
94 let most_used
= index
.find_most_used_chunks(8);
96 let file_info
= manifest
.lookup_file_info(CATALOG_NAME
)?
;
98 let chunk_reader
= RemoteChunkReader
::new(
101 file_info
.chunk_crypt_mode(),
105 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
107 let mut catalogfile
= std
::fs
::OpenOptions
::new()
110 .custom_flags(libc
::O_TMPFILE
)
113 std
::io
::copy(&mut reader
, &mut catalogfile
)
114 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
116 catalogfile
.seek(SeekFrom
::Start(0))?
;
118 let mut catalog_reader
= CatalogReader
::new(catalogfile
);
120 catalog_reader
.dump()?
;
122 record_repository(&repo
);
131 type: BackupNamespace
,
136 description
: "Group/Snapshot path.",
140 description
: "Backup archive name.",
144 schema
: REPO_URL_SCHEMA
,
149 description
: "Path to encryption key.",
152 schema
: KEYFD_SCHEMA
,
158 /// Shell to interactively inspect and restore snapshots.
159 async
fn catalog_shell(param
: Value
) -> Result
<(), Error
> {
160 let repo
= extract_repository_from_value(¶m
)?
;
161 let client
= connect(&repo
)?
;
162 let backup_ns
= optional_ns_param(¶m
)?
;
163 let path
= required_string_param(¶m
, "snapshot")?
;
164 let archive_name
= required_string_param(¶m
, "archive-name")?
;
166 let backup_dir
= dir_or_last_from_group(&client
, &repo
, &backup_ns
, path
).await?
;
168 let crypto
= crypto_parameters(¶m
)?
;
170 let crypt_config
= match crypto
.enc_key
{
173 let (key
, _created
, _fingerprint
) = decrypt_key(&key
.key
, &get_encryption_key_password
)
175 log
::error
!("{}", format_key_source(&key
.source
, "encryption"));
178 let crypt_config
= CryptConfig
::new(key
)?
;
179 Some(Arc
::new(crypt_config
))
183 let server_archive_name
= if archive_name
.ends_with(".pxar") {
184 format
!("{}.didx", archive_name
)
186 bail
!("Can only mount pxar archives.");
189 let client
= BackupReader
::start(
191 crypt_config
.clone(),
199 let mut tmpfile
= std
::fs
::OpenOptions
::new()
202 .custom_flags(libc
::O_TMPFILE
)
205 let (manifest
, _
) = client
.download_manifest().await?
;
206 manifest
.check_fingerprint(crypt_config
.as_ref().map(Arc
::as_ref
))?
;
209 .download_dynamic_index(&manifest
, &server_archive_name
)
211 let most_used
= index
.find_most_used_chunks(8);
213 let file_info
= manifest
.lookup_file_info(&server_archive_name
)?
;
214 let chunk_reader
= RemoteChunkReader
::new(
216 crypt_config
.clone(),
217 file_info
.chunk_crypt_mode(),
220 let reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
221 let archive_size
= reader
.archive_size();
222 let reader
: pbs_pxar_fuse
::Reader
= Arc
::new(BufferedDynamicReadAt
::new(reader
));
223 let decoder
= pbs_pxar_fuse
::Accessor
::new(reader
, archive_size
).await?
;
225 client
.download(CATALOG_NAME
, &mut tmpfile
).await?
;
226 let index
= DynamicIndexReader
::new(tmpfile
)
227 .map_err(|err
| format_err
!("unable to read catalog index - {}", err
))?
;
229 // Note: do not use values stored in index (not trusted) - instead, computed them again
230 let (csum
, size
) = index
.compute_csum();
231 manifest
.verify_file(CATALOG_NAME
, &csum
, size
)?
;
233 let most_used
= index
.find_most_used_chunks(8);
235 let file_info
= manifest
.lookup_file_info(CATALOG_NAME
)?
;
236 let chunk_reader
= RemoteChunkReader
::new(
239 file_info
.chunk_crypt_mode(),
242 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
243 let mut catalogfile
= std
::fs
::OpenOptions
::new()
246 .custom_flags(libc
::O_TMPFILE
)
249 std
::io
::copy(&mut reader
, &mut catalogfile
)
250 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
252 catalogfile
.seek(SeekFrom
::Start(0))?
;
253 let catalog_reader
= CatalogReader
::new(catalogfile
);
254 let state
= Shell
::new(catalog_reader
, &server_archive_name
, decoder
).await?
;
256 log
::info
!("Starting interactive shell");
257 state
.shell().await?
;
259 record_repository(&repo
);
264 pub fn catalog_mgmt_cli() -> CliCommandMap
{
265 let catalog_shell_cmd_def
= CliCommand
::new(&API_METHOD_CATALOG_SHELL
)
266 .arg_param(&["snapshot", "archive-name"])
267 .completion_cb("repository", complete_repository
)
268 .completion_cb("ns", complete_namespace
)
269 .completion_cb("archive-name", complete_pxar_archive_name
)
270 .completion_cb("snapshot", complete_group_or_snapshot
);
272 let catalog_dump_cmd_def
= CliCommand
::new(&API_METHOD_DUMP_CATALOG
)
273 .arg_param(&["snapshot"])
274 .completion_cb("repository", complete_repository
)
275 .completion_cb("ns", complete_namespace
)
276 .completion_cb("snapshot", complete_backup_snapshot
);
279 .insert("dump", catalog_dump_cmd_def
)
280 .insert("shell", catalog_shell_cmd_def
)