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