]> git.proxmox.com Git - proxmox-backup.git/blame - proxmox-backup-client/src/catalog.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / proxmox-backup-client / src / catalog.rs
CommitLineData
9de69cdb
DM
1use std::os::unix::fs::OpenOptionsExt;
2use std::io::{Seek, SeekFrom};
9de69cdb
DM
3use std::sync::Arc;
4
5use anyhow::{bail, format_err, Error};
6use serde_json::Value;
7
6ef1b649
WB
8use proxmox_schema::api;
9use proxmox_router::cli::*;
9de69cdb 10
2b7f8dd5
WB
11use pbs_client::tools::key_source::get_encryption_key_password;
12use pbs_client::{BackupReader, RemoteChunkReader};
3c8c2827 13use pbs_tools::json::required_string_param;
bbdda58b 14use pbs_tools::crypt_config::CryptConfig;
9de69cdb
DM
15
16use crate::{
17 REPO_URL_SCHEMA,
d86034af 18 KEYFD_SCHEMA,
9de69cdb 19 extract_repository_from_value,
2f26b866 20 format_key_source,
9de69cdb 21 record_repository,
d86034af 22 decrypt_key,
9de69cdb
DM
23 api_datastore_latest_snapshot,
24 complete_repository,
25 complete_backup_snapshot,
26 complete_group_or_snapshot,
27 complete_pxar_archive_name,
28 connect,
c6a7ea0a 29 crypto_parameters,
9de69cdb
DM
30 BackupDir,
31 BackupGroup,
32 BufferedDynamicReader,
33 BufferedDynamicReadAt,
34 CatalogReader,
35 CATALOG_NAME,
9de69cdb
DM
36 DynamicIndexReader,
37 IndexFile,
38 Shell,
39};
40
9de69cdb
DM
41#[api(
42 input: {
43 properties: {
44 repository: {
45 schema: REPO_URL_SCHEMA,
46 optional: true,
47 },
48 snapshot: {
49 type: String,
50 description: "Snapshot path.",
51 },
d86034af
DM
52 "keyfile": {
53 optional: true,
54 type: String,
55 description: "Path to encryption key.",
56 },
57 "keyfd": {
58 schema: KEYFD_SCHEMA,
59 optional: true,
60 },
9de69cdb
DM
61 }
62 }
63)]
64/// Dump catalog.
65async fn dump_catalog(param: Value) -> Result<Value, Error> {
66
67 let repo = extract_repository_from_value(&param)?;
68
3c8c2827 69 let path = required_string_param(&param, "snapshot")?;
9de69cdb
DM
70 let snapshot: BackupDir = path.parse()?;
71
c6a7ea0a 72 let crypto = crypto_parameters(&param)?;
9de69cdb 73
c6a7ea0a 74 let crypt_config = match crypto.enc_key {
9de69cdb 75 None => None,
d86034af 76 Some(key) => {
2f26b866
FG
77 let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
78 .map_err(|err| {
79 eprintln!("{}", format_key_source(&key.source, "encryption"));
80 err
81 })?;
d86034af
DM
82 let crypt_config = CryptConfig::new(key)?;
83 Some(Arc::new(crypt_config))
9de69cdb
DM
84 }
85 };
86
f3fde36b 87 let client = connect(&repo)?;
9de69cdb
DM
88
89 let client = BackupReader::start(
90 client,
91 crypt_config.clone(),
92 repo.store(),
9a37bd6c
FG
93 snapshot.group().backup_type(),
94 snapshot.group().backup_id(),
9de69cdb
DM
95 snapshot.backup_time(),
96 true,
97 ).await?;
98
2107a5ae 99 let (manifest, _) = client.download_manifest().await?;
23f9503a 100 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
9de69cdb
DM
101
102 let index = client.download_dynamic_index(&manifest, CATALOG_NAME).await?;
103
104 let most_used = index.find_most_used_chunks(8);
105
9a37bd6c 106 let file_info = manifest.lookup_file_info(CATALOG_NAME)?;
14f6c9cb
FG
107
108 let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used);
9de69cdb
DM
109
110 let mut reader = BufferedDynamicReader::new(index, chunk_reader);
111
112 let mut catalogfile = std::fs::OpenOptions::new()
113 .write(true)
114 .read(true)
115 .custom_flags(libc::O_TMPFILE)
116 .open("/tmp")?;
117
118 std::io::copy(&mut reader, &mut catalogfile)
119 .map_err(|err| format_err!("unable to download catalog - {}", err))?;
120
121 catalogfile.seek(SeekFrom::Start(0))?;
122
123 let mut catalog_reader = CatalogReader::new(catalogfile);
124
125 catalog_reader.dump()?;
126
127 record_repository(&repo);
128
129 Ok(Value::Null)
130}
131
132#[api(
133 input: {
134 properties: {
135 "snapshot": {
136 type: String,
137 description: "Group/Snapshot path.",
138 },
139 "archive-name": {
140 type: String,
141 description: "Backup archive name.",
142 },
143 "repository": {
144 optional: true,
145 schema: REPO_URL_SCHEMA,
146 },
147 "keyfile": {
148 optional: true,
149 type: String,
150 description: "Path to encryption key.",
151 },
d86034af
DM
152 "keyfd": {
153 schema: KEYFD_SCHEMA,
154 optional: true,
155 },
156 },
9de69cdb
DM
157 },
158)]
159/// Shell to interactively inspect and restore snapshots.
160async fn catalog_shell(param: Value) -> Result<(), Error> {
161 let repo = extract_repository_from_value(&param)?;
f3fde36b 162 let client = connect(&repo)?;
3c8c2827
WB
163 let path = required_string_param(&param, "snapshot")?;
164 let archive_name = required_string_param(&param, "archive-name")?;
9de69cdb
DM
165
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?
169 } else {
170 let snapshot: BackupDir = path.parse()?;
171 (snapshot.group().backup_type().to_owned(), snapshot.group().backup_id().to_owned(), snapshot.backup_time())
172 };
173
c6a7ea0a 174 let crypto = crypto_parameters(&param)?;
d86034af 175
c6a7ea0a 176 let crypt_config = match crypto.enc_key {
9de69cdb 177 None => None,
d86034af 178 Some(key) => {
2f26b866
FG
179 let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
180 .map_err(|err| {
181 eprintln!("{}", format_key_source(&key.source, "encryption"));
182 err
183 })?;
d86034af
DM
184 let crypt_config = CryptConfig::new(key)?;
185 Some(Arc::new(crypt_config))
9de69cdb
DM
186 }
187 };
188
189 let server_archive_name = if archive_name.ends_with(".pxar") {
190 format!("{}.didx", archive_name)
191 } else {
192 bail!("Can only mount pxar archives.");
193 };
194
195 let client = BackupReader::start(
196 client,
197 crypt_config.clone(),
198 repo.store(),
199 &backup_type,
200 &backup_id,
201 backup_time,
202 true,
203 ).await?;
204
205 let mut tmpfile = std::fs::OpenOptions::new()
206 .write(true)
207 .read(true)
208 .custom_flags(libc::O_TMPFILE)
209 .open("/tmp")?;
210
2107a5ae 211 let (manifest, _) = client.download_manifest().await?;
23f9503a 212 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
9de69cdb
DM
213
214 let index = client.download_dynamic_index(&manifest, &server_archive_name).await?;
215 let most_used = index.find_most_used_chunks(8);
14f6c9cb
FG
216
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);
9de69cdb
DM
219 let reader = BufferedDynamicReader::new(index, chunk_reader);
220 let archive_size = reader.archive_size();
2b7f8dd5 221 let reader: pbs_client::pxar::fuse::Reader =
9de69cdb 222 Arc::new(BufferedDynamicReadAt::new(reader));
2b7f8dd5 223 let decoder = pbs_client::pxar::fuse::Accessor::new(reader, archive_size).await?;
9de69cdb
DM
224
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))?;
228
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)?;
232
233 let most_used = index.find_most_used_chunks(8);
14f6c9cb 234
9a37bd6c 235 let file_info = manifest.lookup_file_info(CATALOG_NAME)?;
14f6c9cb 236 let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used);
9de69cdb
DM
237 let mut reader = BufferedDynamicReader::new(index, chunk_reader);
238 let mut catalogfile = std::fs::OpenOptions::new()
239 .write(true)
240 .read(true)
241 .custom_flags(libc::O_TMPFILE)
242 .open("/tmp")?;
243
244 std::io::copy(&mut reader, &mut catalogfile)
245 .map_err(|err| format_err!("unable to download catalog - {}", err))?;
246
247 catalogfile.seek(SeekFrom::Start(0))?;
248 let catalog_reader = CatalogReader::new(catalogfile);
249 let state = Shell::new(
250 catalog_reader,
251 &server_archive_name,
252 decoder,
253 ).await?;
254
255 println!("Starting interactive shell");
256 state.shell().await?;
257
258 record_repository(&repo);
259
260 Ok(())
261}
262
263pub 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);
269
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);
274
275 CliCommandMap::new()
276 .insert("dump", catalog_dump_cmd_def)
277 .insert("shell", catalog_shell_cmd_def)
278}