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