]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-backup-client/src/catalog.rs
accept a ref to a HttpClient
[proxmox-backup.git] / proxmox-backup-client / src / catalog.rs
1 use std::io::{Seek, SeekFrom};
2 use std::os::unix::fs::OpenOptionsExt;
3 use std::sync::Arc;
4
5 use anyhow::{bail, format_err, Error};
6 use serde_json::Value;
7
8 use proxmox_router::cli::*;
9 use proxmox_schema::api;
10
11 use pbs_api_types::BackupNamespace;
12 use pbs_client::tools::key_source::get_encryption_key_password;
13 use pbs_client::{BackupReader, RemoteChunkReader};
14 use pbs_tools::crypt_config::CryptConfig;
15 use pbs_tools::json::required_string_param;
16
17 use crate::{
18 complete_backup_snapshot, complete_group_or_snapshot, complete_namespace,
19 complete_pxar_archive_name, complete_repository, connect, crypto_parameters, decrypt_key,
20 dir_or_last_from_group, extract_repository_from_value, format_key_source, optional_ns_param,
21 record_repository, BackupDir, BufferedDynamicReadAt, BufferedDynamicReader, CatalogReader,
22 DynamicIndexReader, IndexFile, Shell, CATALOG_NAME, KEYFD_SCHEMA, REPO_URL_SCHEMA,
23 };
24
25 #[api(
26 input: {
27 properties: {
28 repository: {
29 schema: REPO_URL_SCHEMA,
30 optional: true,
31 },
32 ns: {
33 type: BackupNamespace,
34 optional: true,
35 },
36 snapshot: {
37 type: String,
38 description: "Snapshot path.",
39 },
40 "keyfile": {
41 optional: true,
42 type: String,
43 description: "Path to encryption key.",
44 },
45 "keyfd": {
46 schema: KEYFD_SCHEMA,
47 optional: true,
48 },
49 }
50 }
51 )]
52 /// Dump catalog.
53 async fn dump_catalog(param: Value) -> Result<Value, Error> {
54 let repo = extract_repository_from_value(&param)?;
55
56 let backup_ns = optional_ns_param(&param)?;
57 let path = required_string_param(&param, "snapshot")?;
58 let snapshot: BackupDir = path.parse()?;
59
60 let crypto = crypto_parameters(&param)?;
61
62 let crypt_config = match crypto.enc_key {
63 None => None,
64 Some(key) => {
65 let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
66 .map_err(|err| {
67 log::error!("{}", format_key_source(&key.source, "encryption"));
68 err
69 })?;
70 let crypt_config = CryptConfig::new(key)?;
71 Some(Arc::new(crypt_config))
72 }
73 };
74
75 let client = connect(&repo)?;
76
77 let client = BackupReader::start(
78 &client,
79 crypt_config.clone(),
80 repo.store(),
81 &backup_ns,
82 &snapshot,
83 true,
84 )
85 .await?;
86
87 let (manifest, _) = client.download_manifest().await?;
88 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
89
90 let index = client
91 .download_dynamic_index(&manifest, CATALOG_NAME)
92 .await?;
93
94 let most_used = index.find_most_used_chunks(8);
95
96 let file_info = manifest.lookup_file_info(CATALOG_NAME)?;
97
98 let chunk_reader = RemoteChunkReader::new(
99 client.clone(),
100 crypt_config,
101 file_info.chunk_crypt_mode(),
102 most_used,
103 );
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 ns: {
131 type: BackupNamespace,
132 optional: true,
133 },
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 },
151 "keyfd": {
152 schema: KEYFD_SCHEMA,
153 optional: true,
154 },
155 },
156 },
157 )]
158 /// Shell to interactively inspect and restore snapshots.
159 async fn catalog_shell(param: Value) -> Result<(), Error> {
160 let repo = extract_repository_from_value(&param)?;
161 let client = connect(&repo)?;
162 let backup_ns = optional_ns_param(&param)?;
163 let path = required_string_param(&param, "snapshot")?;
164 let archive_name = required_string_param(&param, "archive-name")?;
165
166 let backup_dir = dir_or_last_from_group(&client, &repo, &backup_ns, path).await?;
167
168 let crypto = crypto_parameters(&param)?;
169
170 let crypt_config = match crypto.enc_key {
171 None => None,
172 Some(key) => {
173 let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
174 .map_err(|err| {
175 log::error!("{}", format_key_source(&key.source, "encryption"));
176 err
177 })?;
178 let crypt_config = CryptConfig::new(key)?;
179 Some(Arc::new(crypt_config))
180 }
181 };
182
183 let server_archive_name = if archive_name.ends_with(".pxar") {
184 format!("{}.didx", archive_name)
185 } else {
186 bail!("Can only mount pxar archives.");
187 };
188
189 let client = BackupReader::start(
190 &client,
191 crypt_config.clone(),
192 repo.store(),
193 &backup_ns,
194 &backup_dir,
195 true,
196 )
197 .await?;
198
199 let mut tmpfile = std::fs::OpenOptions::new()
200 .write(true)
201 .read(true)
202 .custom_flags(libc::O_TMPFILE)
203 .open("/tmp")?;
204
205 let (manifest, _) = client.download_manifest().await?;
206 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
207
208 let index = client
209 .download_dynamic_index(&manifest, &server_archive_name)
210 .await?;
211 let most_used = index.find_most_used_chunks(8);
212
213 let file_info = manifest.lookup_file_info(&server_archive_name)?;
214 let chunk_reader = RemoteChunkReader::new(
215 client.clone(),
216 crypt_config.clone(),
217 file_info.chunk_crypt_mode(),
218 most_used,
219 );
220 let reader = BufferedDynamicReader::new(index, chunk_reader);
221 let archive_size = reader.archive_size();
222 let reader: pbs_pxar_fuse::Reader = Arc::new(BufferedDynamicReadAt::new(reader));
223 let decoder = pbs_pxar_fuse::Accessor::new(reader, archive_size).await?;
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);
234
235 let file_info = manifest.lookup_file_info(CATALOG_NAME)?;
236 let chunk_reader = RemoteChunkReader::new(
237 client.clone(),
238 crypt_config,
239 file_info.chunk_crypt_mode(),
240 most_used,
241 );
242 let mut reader = BufferedDynamicReader::new(index, chunk_reader);
243 let mut catalogfile = std::fs::OpenOptions::new()
244 .write(true)
245 .read(true)
246 .custom_flags(libc::O_TMPFILE)
247 .open("/tmp")?;
248
249 std::io::copy(&mut reader, &mut catalogfile)
250 .map_err(|err| format_err!("unable to download catalog - {}", err))?;
251
252 catalogfile.seek(SeekFrom::Start(0))?;
253 let catalog_reader = CatalogReader::new(catalogfile);
254 let state = Shell::new(catalog_reader, &server_archive_name, decoder).await?;
255
256 log::info!("Starting interactive shell");
257 state.shell().await?;
258
259 record_repository(&repo);
260
261 Ok(())
262 }
263
264 pub fn catalog_mgmt_cli() -> CliCommandMap {
265 let catalog_shell_cmd_def = CliCommand::new(&API_METHOD_CATALOG_SHELL)
266 .arg_param(&["snapshot", "archive-name"])
267 .completion_cb("repository", complete_repository)
268 .completion_cb("ns", complete_namespace)
269 .completion_cb("archive-name", complete_pxar_archive_name)
270 .completion_cb("snapshot", complete_group_or_snapshot);
271
272 let catalog_dump_cmd_def = CliCommand::new(&API_METHOD_DUMP_CATALOG)
273 .arg_param(&["snapshot"])
274 .completion_cb("repository", complete_repository)
275 .completion_cb("ns", complete_namespace)
276 .completion_cb("snapshot", complete_backup_snapshot);
277
278 CliCommandMap::new()
279 .insert("dump", catalog_dump_cmd_def)
280 .insert("shell", catalog_shell_cmd_def)
281 }