1 use std
::os
::unix
::fs
::OpenOptionsExt
;
2 use std
::io
::{Seek, SeekFrom}
;
5 use anyhow
::{bail, format_err, Error}
;
8 use proxmox_schema
::api
;
9 use proxmox_router
::cli
::*;
11 use pbs_client
::tools
::key_source
::get_encryption_key_password
;
12 use pbs_client
::{BackupReader, RemoteChunkReader}
;
13 use pbs_tools
::json
::required_string_param
;
14 use pbs_tools
::crypt_config
::CryptConfig
;
19 extract_repository_from_value
,
23 api_datastore_latest_snapshot
,
25 complete_backup_snapshot
,
26 complete_group_or_snapshot
,
27 complete_pxar_archive_name
,
32 BufferedDynamicReader
,
33 BufferedDynamicReadAt
,
45 schema
: REPO_URL_SCHEMA
,
50 description
: "Snapshot path.",
55 description
: "Path to encryption key.",
65 async
fn dump_catalog(param
: Value
) -> Result
<Value
, Error
> {
67 let repo
= extract_repository_from_value(¶m
)?
;
69 let path
= required_string_param(¶m
, "snapshot")?
;
70 let snapshot
: BackupDir
= path
.parse()?
;
72 let crypto
= crypto_parameters(¶m
)?
;
74 let crypt_config
= match crypto
.enc_key
{
77 let (key
, _created
, _fingerprint
) = decrypt_key(&key
.key
, &get_encryption_key_password
)
79 eprintln
!("{}", format_key_source(&key
.source
, "encryption"));
82 let crypt_config
= CryptConfig
::new(key
)?
;
83 Some(Arc
::new(crypt_config
))
87 let client
= connect(&repo
)?
;
89 let client
= BackupReader
::start(
93 &snapshot
.group().backup_type(),
94 &snapshot
.group().backup_id(),
95 snapshot
.backup_time(),
99 let (manifest
, _
) = client
.download_manifest().await?
;
100 manifest
.check_fingerprint(crypt_config
.as_ref().map(Arc
::as_ref
))?
;
102 let index
= client
.download_dynamic_index(&manifest
, CATALOG_NAME
).await?
;
104 let most_used
= index
.find_most_used_chunks(8);
106 let file_info
= manifest
.lookup_file_info(&CATALOG_NAME
)?
;
108 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, file_info
.chunk_crypt_mode(), most_used
);
110 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
112 let mut catalogfile
= std
::fs
::OpenOptions
::new()
115 .custom_flags(libc
::O_TMPFILE
)
118 std
::io
::copy(&mut reader
, &mut catalogfile
)
119 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
121 catalogfile
.seek(SeekFrom
::Start(0))?
;
123 let mut catalog_reader
= CatalogReader
::new(catalogfile
);
125 catalog_reader
.dump()?
;
127 record_repository(&repo
);
137 description
: "Group/Snapshot path.",
141 description
: "Backup archive name.",
145 schema
: REPO_URL_SCHEMA
,
150 description
: "Path to encryption key.",
153 schema
: KEYFD_SCHEMA
,
159 /// Shell to interactively inspect and restore snapshots.
160 async
fn catalog_shell(param
: Value
) -> Result
<(), Error
> {
161 let repo
= extract_repository_from_value(¶m
)?
;
162 let client
= connect(&repo
)?
;
163 let path
= required_string_param(¶m
, "snapshot")?
;
164 let archive_name
= required_string_param(¶m
, "archive-name")?
;
166 let (backup_type
, backup_id
, backup_time
) = if path
.matches('
/'
).count() == 1 {
167 let group
: BackupGroup
= path
.parse()?
;
168 api_datastore_latest_snapshot(&client
, repo
.store(), group
).await?
170 let snapshot
: BackupDir
= path
.parse()?
;
171 (snapshot
.group().backup_type().to_owned(), snapshot
.group().backup_id().to_owned(), snapshot
.backup_time())
174 let crypto
= crypto_parameters(¶m
)?
;
176 let crypt_config
= match crypto
.enc_key
{
179 let (key
, _created
, _fingerprint
) = decrypt_key(&key
.key
, &get_encryption_key_password
)
181 eprintln
!("{}", format_key_source(&key
.source
, "encryption"));
184 let crypt_config
= CryptConfig
::new(key
)?
;
185 Some(Arc
::new(crypt_config
))
189 let server_archive_name
= if archive_name
.ends_with(".pxar") {
190 format
!("{}.didx", archive_name
)
192 bail
!("Can only mount pxar archives.");
195 let client
= BackupReader
::start(
197 crypt_config
.clone(),
205 let mut tmpfile
= std
::fs
::OpenOptions
::new()
208 .custom_flags(libc
::O_TMPFILE
)
211 let (manifest
, _
) = client
.download_manifest().await?
;
212 manifest
.check_fingerprint(crypt_config
.as_ref().map(Arc
::as_ref
))?
;
214 let index
= client
.download_dynamic_index(&manifest
, &server_archive_name
).await?
;
215 let most_used
= index
.find_most_used_chunks(8);
217 let file_info
= manifest
.lookup_file_info(&server_archive_name
)?
;
218 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
.clone(), file_info
.chunk_crypt_mode(), most_used
);
219 let reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
220 let archive_size
= reader
.archive_size();
221 let reader
: pbs_client
::pxar
::fuse
::Reader
=
222 Arc
::new(BufferedDynamicReadAt
::new(reader
));
223 let decoder
= pbs_client
::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(client
.clone(), crypt_config
, file_info
.chunk_crypt_mode(), most_used
);
237 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
238 let mut catalogfile
= std
::fs
::OpenOptions
::new()
241 .custom_flags(libc
::O_TMPFILE
)
244 std
::io
::copy(&mut reader
, &mut catalogfile
)
245 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
247 catalogfile
.seek(SeekFrom
::Start(0))?
;
248 let catalog_reader
= CatalogReader
::new(catalogfile
);
249 let state
= Shell
::new(
251 &server_archive_name
,
255 println
!("Starting interactive shell");
256 state
.shell().await?
;
258 record_repository(&repo
);
263 pub fn catalog_mgmt_cli() -> CliCommandMap
{
264 let catalog_shell_cmd_def
= CliCommand
::new(&API_METHOD_CATALOG_SHELL
)
265 .arg_param(&["snapshot", "archive-name"])
266 .completion_cb("repository", complete_repository
)
267 .completion_cb("archive-name", complete_pxar_archive_name
)
268 .completion_cb("snapshot", complete_group_or_snapshot
);
270 let catalog_dump_cmd_def
= CliCommand
::new(&API_METHOD_DUMP_CATALOG
)
271 .arg_param(&["snapshot"])
272 .completion_cb("repository", complete_repository
)
273 .completion_cb("snapshot", complete_backup_snapshot
);
276 .insert("dump", catalog_dump_cmd_def
)
277 .insert("shell", catalog_shell_cmd_def
)