]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-backup-client/src/catalog.rs
update to first proxmox crate split
[proxmox-backup.git] / proxmox-backup-client / src / 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_schema::api;
9 use proxmox_router::cli::*;
10
11 use pbs_client::tools::key_source::get_encryption_key_password;
12 use pbs_client::{BackupReader, RemoteChunkReader};
13 use pbs_tools::json::required_string_param;
14 use pbs_tools::crypt_config::CryptConfig;
15
16 use crate::{
17 REPO_URL_SCHEMA,
18 KEYFD_SCHEMA,
19 extract_repository_from_value,
20 format_key_source,
21 record_repository,
22 decrypt_key,
23 api_datastore_latest_snapshot,
24 complete_repository,
25 complete_backup_snapshot,
26 complete_group_or_snapshot,
27 complete_pxar_archive_name,
28 connect,
29 crypto_parameters,
30 BackupDir,
31 BackupGroup,
32 BufferedDynamicReader,
33 BufferedDynamicReadAt,
34 CatalogReader,
35 CATALOG_NAME,
36 DynamicIndexReader,
37 IndexFile,
38 Shell,
39 };
40
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 },
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 },
61 }
62 }
63 )]
64 /// Dump catalog.
65 async fn dump_catalog(param: Value) -> Result<Value, Error> {
66
67 let repo = extract_repository_from_value(&param)?;
68
69 let path = required_string_param(&param, "snapshot")?;
70 let snapshot: BackupDir = path.parse()?;
71
72 let crypto = crypto_parameters(&param)?;
73
74 let crypt_config = match crypto.enc_key {
75 None => None,
76 Some(key) => {
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 })?;
82 let crypt_config = CryptConfig::new(key)?;
83 Some(Arc::new(crypt_config))
84 }
85 };
86
87 let client = connect(&repo)?;
88
89 let client = BackupReader::start(
90 client,
91 crypt_config.clone(),
92 repo.store(),
93 &snapshot.group().backup_type(),
94 &snapshot.group().backup_id(),
95 snapshot.backup_time(),
96 true,
97 ).await?;
98
99 let (manifest, _) = client.download_manifest().await?;
100 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
101
102 let index = client.download_dynamic_index(&manifest, CATALOG_NAME).await?;
103
104 let most_used = index.find_most_used_chunks(8);
105
106 let file_info = manifest.lookup_file_info(&CATALOG_NAME)?;
107
108 let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used);
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 },
152 "keyfd": {
153 schema: KEYFD_SCHEMA,
154 optional: true,
155 },
156 },
157 },
158 )]
159 /// Shell to interactively inspect and restore snapshots.
160 async fn catalog_shell(param: Value) -> Result<(), Error> {
161 let repo = extract_repository_from_value(&param)?;
162 let client = connect(&repo)?;
163 let path = required_string_param(&param, "snapshot")?;
164 let archive_name = required_string_param(&param, "archive-name")?;
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
174 let crypto = crypto_parameters(&param)?;
175
176 let crypt_config = match crypto.enc_key {
177 None => None,
178 Some(key) => {
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 })?;
184 let crypt_config = CryptConfig::new(key)?;
185 Some(Arc::new(crypt_config))
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
211 let (manifest, _) = client.download_manifest().await?;
212 manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
213
214 let index = client.download_dynamic_index(&manifest, &server_archive_name).await?;
215 let most_used = index.find_most_used_chunks(8);
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);
219 let reader = BufferedDynamicReader::new(index, chunk_reader);
220 let archive_size = reader.archive_size();
221 let reader: pbs_client::pxar::fuse::Reader =
222 Arc::new(BufferedDynamicReadAt::new(reader));
223 let decoder = pbs_client::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(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used);
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
263 pub 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 }