1 use std
::os
::unix
::fs
::OpenOptionsExt
;
2 use std
::io
::{Seek, SeekFrom}
;
3 use std
::path
::PathBuf
;
6 use anyhow
::{bail, format_err, Error}
;
9 use proxmox
::api
::{api, cli::*}
;
11 use proxmox_backup
::tools
;
13 use proxmox_backup
::client
::*;
17 extract_repository_from_value
,
19 get_encryption_key_password
,
21 api_datastore_latest_snapshot
,
23 complete_backup_snapshot
,
24 complete_group_or_snapshot
,
25 complete_pxar_archive_name
,
29 BufferedDynamicReader
,
30 BufferedDynamicReadAt
,
45 schema
: REPO_URL_SCHEMA
,
50 description
: "Snapshot path.",
56 async
fn dump_catalog(param
: Value
) -> Result
<Value
, Error
> {
58 let repo
= extract_repository_from_value(¶m
)?
;
60 let path
= tools
::required_string_param(¶m
, "snapshot")?
;
61 let snapshot
: BackupDir
= path
.parse()?
;
63 let keyfile
= param
["keyfile"].as_str().map(PathBuf
::from
);
65 let crypt_config
= match keyfile
{
68 let (key
, _
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
69 Some(Arc
::new(CryptConfig
::new(key
)?
))
73 let client
= connect(repo
.host(), repo
.user())?
;
75 let client
= BackupReader
::start(
79 &snapshot
.group().backup_type(),
80 &snapshot
.group().backup_id(),
81 snapshot
.backup_time(),
85 let manifest
= client
.download_manifest().await?
;
87 let index
= client
.download_dynamic_index(&manifest
, CATALOG_NAME
).await?
;
89 let most_used
= index
.find_most_used_chunks(8);
91 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, most_used
);
93 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
95 let mut catalogfile
= std
::fs
::OpenOptions
::new()
98 .custom_flags(libc
::O_TMPFILE
)
101 std
::io
::copy(&mut reader
, &mut catalogfile
)
102 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
104 catalogfile
.seek(SeekFrom
::Start(0))?
;
106 let mut catalog_reader
= CatalogReader
::new(catalogfile
);
108 catalog_reader
.dump()?
;
110 record_repository(&repo
);
120 description
: "Group/Snapshot path.",
124 description
: "Backup archive name.",
128 schema
: REPO_URL_SCHEMA
,
133 description
: "Path to encryption key.",
138 /// Shell to interactively inspect and restore snapshots.
139 async
fn catalog_shell(param
: Value
) -> Result
<(), Error
> {
140 let repo
= extract_repository_from_value(¶m
)?
;
141 let client
= connect(repo
.host(), repo
.user())?
;
142 let path
= tools
::required_string_param(¶m
, "snapshot")?
;
143 let archive_name
= tools
::required_string_param(¶m
, "archive-name")?
;
145 let (backup_type
, backup_id
, backup_time
) = if path
.matches('
/'
).count() == 1 {
146 let group
: BackupGroup
= path
.parse()?
;
147 api_datastore_latest_snapshot(&client
, repo
.store(), group
).await?
149 let snapshot
: BackupDir
= path
.parse()?
;
150 (snapshot
.group().backup_type().to_owned(), snapshot
.group().backup_id().to_owned(), snapshot
.backup_time())
153 let keyfile
= param
["keyfile"].as_str().map(|p
| PathBuf
::from(p
));
154 let crypt_config
= match keyfile
{
157 let (key
, _
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
158 Some(Arc
::new(CryptConfig
::new(key
)?
))
162 let server_archive_name
= if archive_name
.ends_with(".pxar") {
163 format
!("{}.didx", archive_name
)
165 bail
!("Can only mount pxar archives.");
168 let client
= BackupReader
::start(
170 crypt_config
.clone(),
178 let mut tmpfile
= std
::fs
::OpenOptions
::new()
181 .custom_flags(libc
::O_TMPFILE
)
184 let manifest
= client
.download_manifest().await?
;
186 let index
= client
.download_dynamic_index(&manifest
, &server_archive_name
).await?
;
187 let most_used
= index
.find_most_used_chunks(8);
188 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
.clone(), most_used
);
189 let reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
190 let archive_size
= reader
.archive_size();
191 let reader
: proxmox_backup
::pxar
::fuse
::Reader
=
192 Arc
::new(BufferedDynamicReadAt
::new(reader
));
193 let decoder
= proxmox_backup
::pxar
::fuse
::Accessor
::new(reader
, archive_size
).await?
;
195 client
.download(CATALOG_NAME
, &mut tmpfile
).await?
;
196 let index
= DynamicIndexReader
::new(tmpfile
)
197 .map_err(|err
| format_err
!("unable to read catalog index - {}", err
))?
;
199 // Note: do not use values stored in index (not trusted) - instead, computed them again
200 let (csum
, size
) = index
.compute_csum();
201 manifest
.verify_file(CATALOG_NAME
, &csum
, size
)?
;
203 let most_used
= index
.find_most_used_chunks(8);
204 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, most_used
);
205 let mut reader
= BufferedDynamicReader
::new(index
, chunk_reader
);
206 let mut catalogfile
= std
::fs
::OpenOptions
::new()
209 .custom_flags(libc
::O_TMPFILE
)
212 std
::io
::copy(&mut reader
, &mut catalogfile
)
213 .map_err(|err
| format_err
!("unable to download catalog - {}", err
))?
;
215 catalogfile
.seek(SeekFrom
::Start(0))?
;
216 let catalog_reader
= CatalogReader
::new(catalogfile
);
217 let state
= Shell
::new(
219 &server_archive_name
,
223 println
!("Starting interactive shell");
224 state
.shell().await?
;
226 record_repository(&repo
);
231 pub fn catalog_mgmt_cli() -> CliCommandMap
{
232 let catalog_shell_cmd_def
= CliCommand
::new(&API_METHOD_CATALOG_SHELL
)
233 .arg_param(&["snapshot", "archive-name"])
234 .completion_cb("repository", complete_repository
)
235 .completion_cb("archive-name", complete_pxar_archive_name
)
236 .completion_cb("snapshot", complete_group_or_snapshot
);
238 let catalog_dump_cmd_def
= CliCommand
::new(&API_METHOD_DUMP_CATALOG
)
239 .arg_param(&["snapshot"])
240 .completion_cb("repository", complete_repository
)
241 .completion_cb("snapshot", complete_backup_snapshot
);
244 .insert("dump", catalog_dump_cmd_def
)
245 .insert("shell", catalog_shell_cmd_def
)