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