1 use std
::os
::unix
::fs
::OpenOptionsExt
;
2 use std
::io
::{Seek, SeekFrom}
;
5 use anyhow
::{bail, format_err, Error}
;
8 use proxmox
::api
::{api, cli::*}
;
10 use proxmox_backup
::tools
;
12 use proxmox_backup
::client
::*;
17 extract_repository_from_value
,
20 key
::get_encryption_key_password
,
22 api_datastore_latest_snapshot
,
24 complete_backup_snapshot
,
25 complete_group_or_snapshot
,
26 complete_pxar_archive_name
,
30 BufferedDynamicReader
,
31 BufferedDynamicReadAt
,
44 schema
: REPO_URL_SCHEMA
,
49 description
: "Snapshot path.",
54 description
: "Path to encryption key.",
64 async
fn dump_catalog(param
: Value
) -> Result
<Value
, Error
> {
66 let repo
= extract_repository_from_value(¶m
)?
;
68 let path
= tools
::required_string_param(¶m
, "snapshot")?
;
69 let snapshot
: BackupDir
= path
.parse()?
;
71 let (keydata
, _
) = keyfile_parameters(¶m
)?
;
73 let crypt_config
= match keydata
{
76 let (key
, _created
) = decrypt_key(&key
, &get_encryption_key_password
)?
;
77 let crypt_config
= CryptConfig
::new(key
)?
;
78 Some(Arc
::new(crypt_config
))
82 let client
= connect(&repo
)?
;
84 let client
= BackupReader
::start(
88 &snapshot
.group().backup_type(),
89 &snapshot
.group().backup_id(),
90 snapshot
.backup_time(),
94 let (manifest
, _
) = client
.download_manifest().await?
;
96 let index
= client
.download_dynamic_index(&manifest
, CATALOG_NAME
).await?
;
98 let most_used
= index
.find_most_used_chunks(8);
100 let file_info
= manifest
.lookup_file_info(&CATALOG_NAME
)?
;
102 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, file_info
.chunk_crypt_mode(), most_used
);
104 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
106 let mut catalogfile
= std
::fs
::OpenOptions
::new()
109 .custom_flags(libc
::O_TMPFILE
)
112 std
::io
::copy(&mut reader
, &mut catalogfile
)
113 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
115 catalogfile
.seek(SeekFrom
::Start(0))?
;
117 let mut catalog_reader
= CatalogReader
::new(catalogfile
);
119 catalog_reader
.dump()?
;
121 record_repository(&repo
);
131 description
: "Group/Snapshot path.",
135 description
: "Backup archive name.",
139 schema
: REPO_URL_SCHEMA
,
144 description
: "Path to encryption key.",
147 schema
: KEYFD_SCHEMA
,
153 /// Shell to interactively inspect and restore snapshots.
154 async
fn catalog_shell(param
: Value
) -> Result
<(), Error
> {
155 let repo
= extract_repository_from_value(¶m
)?
;
156 let client
= connect(&repo
)?
;
157 let path
= tools
::required_string_param(¶m
, "snapshot")?
;
158 let archive_name
= tools
::required_string_param(¶m
, "archive-name")?
;
160 let (backup_type
, backup_id
, backup_time
) = if path
.matches('
/'
).count() == 1 {
161 let group
: BackupGroup
= path
.parse()?
;
162 api_datastore_latest_snapshot(&client
, repo
.store(), group
).await?
164 let snapshot
: BackupDir
= path
.parse()?
;
165 (snapshot
.group().backup_type().to_owned(), snapshot
.group().backup_id().to_owned(), snapshot
.backup_time())
168 let (keydata
, _
) = keyfile_parameters(¶m
)?
;
170 let crypt_config
= match keydata
{
173 let (key
, _created
) = decrypt_key(&key
, &get_encryption_key_password
)?
;
174 let crypt_config
= CryptConfig
::new(key
)?
;
175 Some(Arc
::new(crypt_config
))
179 let server_archive_name
= if archive_name
.ends_with(".pxar") {
180 format
!("{}.didx", archive_name
)
182 bail
!("Can only mount pxar archives.");
185 let client
= BackupReader
::start(
187 crypt_config
.clone(),
195 let mut tmpfile
= std
::fs
::OpenOptions
::new()
198 .custom_flags(libc
::O_TMPFILE
)
201 let (manifest
, _
) = client
.download_manifest().await?
;
203 let index
= client
.download_dynamic_index(&manifest
, &server_archive_name
).await?
;
204 let most_used
= index
.find_most_used_chunks(8);
206 let file_info
= manifest
.lookup_file_info(&server_archive_name
)?
;
207 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
.clone(), file_info
.chunk_crypt_mode(), most_used
);
208 let reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
209 let archive_size
= reader
.archive_size();
210 let reader
: proxmox_backup
::pxar
::fuse
::Reader
=
211 Arc
::new(BufferedDynamicReadAt
::new(reader
));
212 let decoder
= proxmox_backup
::pxar
::fuse
::Accessor
::new(reader
, archive_size
).await?
;
214 client
.download(CATALOG_NAME
, &mut tmpfile
).await?
;
215 let index
= DynamicIndexReader
::new(tmpfile
)
216 .map_err(|err
| format_err
!("unable to read catalog index - {}", err
))?
;
218 // Note: do not use values stored in index (not trusted) - instead, computed them again
219 let (csum
, size
) = index
.compute_csum();
220 manifest
.verify_file(CATALOG_NAME
, &csum
, size
)?
;
222 let most_used
= index
.find_most_used_chunks(8);
224 let file_info
= manifest
.lookup_file_info(&CATALOG_NAME
)?
;
225 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, file_info
.chunk_crypt_mode(), most_used
);
226 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
227 let mut catalogfile
= std
::fs
::OpenOptions
::new()
230 .custom_flags(libc
::O_TMPFILE
)
233 std
::io
::copy(&mut reader
, &mut catalogfile
)
234 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
236 catalogfile
.seek(SeekFrom
::Start(0))?
;
237 let catalog_reader
= CatalogReader
::new(catalogfile
);
238 let state
= Shell
::new(
240 &server_archive_name
,
244 println
!("Starting interactive shell");
245 state
.shell().await?
;
247 record_repository(&repo
);
252 pub fn catalog_mgmt_cli() -> CliCommandMap
{
253 let catalog_shell_cmd_def
= CliCommand
::new(&API_METHOD_CATALOG_SHELL
)
254 .arg_param(&["snapshot", "archive-name"])
255 .completion_cb("repository", complete_repository
)
256 .completion_cb("archive-name", complete_pxar_archive_name
)
257 .completion_cb("snapshot", complete_group_or_snapshot
);
259 let catalog_dump_cmd_def
= CliCommand
::new(&API_METHOD_DUMP_CATALOG
)
260 .arg_param(&["snapshot"])
261 .completion_cb("repository", complete_repository
)
262 .completion_cb("snapshot", complete_backup_snapshot
);
265 .insert("dump", catalog_dump_cmd_def
)
266 .insert("shell", catalog_shell_cmd_def
)