]>
Commit | Line | Data |
---|---|---|
9de69cdb | 1 | use std::io::{Seek, SeekFrom}; |
f9a5beaa | 2 | use std::os::unix::fs::OpenOptionsExt; |
9de69cdb DM |
3 | use std::sync::Arc; |
4 | ||
5 | use anyhow::{bail, format_err, Error}; | |
6 | use serde_json::Value; | |
7 | ||
6ef1b649 | 8 | use proxmox_router::cli::*; |
f9a5beaa | 9 | use proxmox_schema::api; |
9de69cdb | 10 | |
133d718f | 11 | use pbs_api_types::BackupNamespace; |
2b7f8dd5 WB |
12 | use pbs_client::tools::key_source::get_encryption_key_password; |
13 | use pbs_client::{BackupReader, RemoteChunkReader}; | |
bbdda58b | 14 | use pbs_tools::crypt_config::CryptConfig; |
f9a5beaa | 15 | use pbs_tools::json::required_string_param; |
9de69cdb DM |
16 | |
17 | use crate::{ | |
8c74349b WB |
18 | complete_backup_snapshot, complete_group_or_snapshot, complete_pxar_archive_name, |
19 | complete_repository, connect, crypto_parameters, decrypt_key, dir_or_last_from_group, | |
133d718f WB |
20 | extract_repository_from_value, format_key_source, optional_ns_param, record_repository, |
21 | BackupDir, BufferedDynamicReadAt, BufferedDynamicReader, CatalogReader, DynamicIndexReader, | |
22 | IndexFile, Shell, CATALOG_NAME, KEYFD_SCHEMA, REPO_URL_SCHEMA, | |
9de69cdb DM |
23 | }; |
24 | ||
9de69cdb DM |
25 | #[api( |
26 | input: { | |
27 | properties: { | |
28 | repository: { | |
29 | schema: REPO_URL_SCHEMA, | |
30 | optional: true, | |
31 | }, | |
133d718f WB |
32 | ns: { |
33 | type: BackupNamespace, | |
34 | optional: true, | |
35 | }, | |
9de69cdb DM |
36 | snapshot: { |
37 | type: String, | |
38 | description: "Snapshot path.", | |
39 | }, | |
d86034af DM |
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 | }, | |
9de69cdb DM |
49 | } |
50 | } | |
51 | )] | |
52 | /// Dump catalog. | |
53 | async fn dump_catalog(param: Value) -> Result<Value, Error> { | |
9de69cdb DM |
54 | let repo = extract_repository_from_value(¶m)?; |
55 | ||
133d718f | 56 | let backup_ns = optional_ns_param(¶m)?; |
3c8c2827 | 57 | let path = required_string_param(¶m, "snapshot")?; |
9de69cdb DM |
58 | let snapshot: BackupDir = path.parse()?; |
59 | ||
c6a7ea0a | 60 | let crypto = crypto_parameters(¶m)?; |
9de69cdb | 61 | |
c6a7ea0a | 62 | let crypt_config = match crypto.enc_key { |
9de69cdb | 63 | None => None, |
d86034af | 64 | Some(key) => { |
2f26b866 FG |
65 | let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password) |
66 | .map_err(|err| { | |
67 | eprintln!("{}", format_key_source(&key.source, "encryption")); | |
68 | err | |
69 | })?; | |
d86034af DM |
70 | let crypt_config = CryptConfig::new(key)?; |
71 | Some(Arc::new(crypt_config)) | |
9de69cdb DM |
72 | } |
73 | }; | |
74 | ||
f3fde36b | 75 | let client = connect(&repo)?; |
9de69cdb | 76 | |
133d718f WB |
77 | let client = BackupReader::start( |
78 | client, | |
79 | crypt_config.clone(), | |
80 | repo.store(), | |
81 | &backup_ns, | |
82 | &snapshot, | |
83 | true, | |
84 | ) | |
85 | .await?; | |
9de69cdb | 86 | |
2107a5ae | 87 | let (manifest, _) = client.download_manifest().await?; |
23f9503a | 88 | manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?; |
9de69cdb | 89 | |
f9a5beaa TL |
90 | let index = client |
91 | .download_dynamic_index(&manifest, CATALOG_NAME) | |
92 | .await?; | |
9de69cdb DM |
93 | |
94 | let most_used = index.find_most_used_chunks(8); | |
95 | ||
9a37bd6c | 96 | let file_info = manifest.lookup_file_info(CATALOG_NAME)?; |
14f6c9cb | 97 | |
f9a5beaa TL |
98 | let chunk_reader = RemoteChunkReader::new( |
99 | client.clone(), | |
100 | crypt_config, | |
101 | file_info.chunk_crypt_mode(), | |
102 | most_used, | |
103 | ); | |
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: { | |
133d718f WB |
130 | ns: { |
131 | type: BackupNamespace, | |
132 | optional: true, | |
133 | }, | |
9de69cdb DM |
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. | |
159 | async fn catalog_shell(param: Value) -> Result<(), Error> { | |
160 | let repo = extract_repository_from_value(¶m)?; | |
f3fde36b | 161 | let client = connect(&repo)?; |
133d718f | 162 | let backup_ns = optional_ns_param(¶m)?; |
3c8c2827 WB |
163 | let path = required_string_param(¶m, "snapshot")?; |
164 | let archive_name = required_string_param(¶m, "archive-name")?; | |
9de69cdb | 165 | |
133d718f | 166 | let backup_dir = dir_or_last_from_group(&client, &repo, &backup_ns, &path).await?; |
9de69cdb | 167 | |
c6a7ea0a | 168 | let crypto = crypto_parameters(¶m)?; |
d86034af | 169 | |
c6a7ea0a | 170 | let crypt_config = match crypto.enc_key { |
9de69cdb | 171 | None => None, |
d86034af | 172 | Some(key) => { |
2f26b866 FG |
173 | let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password) |
174 | .map_err(|err| { | |
175 | eprintln!("{}", format_key_source(&key.source, "encryption")); | |
176 | err | |
177 | })?; | |
d86034af DM |
178 | let crypt_config = CryptConfig::new(key)?; |
179 | Some(Arc::new(crypt_config)) | |
9de69cdb DM |
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(), | |
133d718f | 193 | &backup_ns, |
8c74349b | 194 | &backup_dir, |
9de69cdb | 195 | true, |
f9a5beaa TL |
196 | ) |
197 | .await?; | |
9de69cdb DM |
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 | ||
2107a5ae | 205 | let (manifest, _) = client.download_manifest().await?; |
23f9503a | 206 | manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?; |
9de69cdb | 207 | |
f9a5beaa TL |
208 | let index = client |
209 | .download_dynamic_index(&manifest, &server_archive_name) | |
210 | .await?; | |
9de69cdb | 211 | let most_used = index.find_most_used_chunks(8); |
14f6c9cb FG |
212 | |
213 | let file_info = manifest.lookup_file_info(&server_archive_name)?; | |
f9a5beaa TL |
214 | let chunk_reader = RemoteChunkReader::new( |
215 | client.clone(), | |
216 | crypt_config.clone(), | |
217 | file_info.chunk_crypt_mode(), | |
218 | most_used, | |
219 | ); | |
9de69cdb DM |
220 | let reader = BufferedDynamicReader::new(index, chunk_reader); |
221 | let archive_size = reader.archive_size(); | |
f9a5beaa | 222 | let reader: pbs_client::pxar::fuse::Reader = Arc::new(BufferedDynamicReadAt::new(reader)); |
2b7f8dd5 | 223 | let decoder = pbs_client::pxar::fuse::Accessor::new(reader, archive_size).await?; |
9de69cdb DM |
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); | |
14f6c9cb | 234 | |
9a37bd6c | 235 | let file_info = manifest.lookup_file_info(CATALOG_NAME)?; |
f9a5beaa TL |
236 | let chunk_reader = RemoteChunkReader::new( |
237 | client.clone(), | |
238 | crypt_config, | |
239 | file_info.chunk_crypt_mode(), | |
240 | most_used, | |
241 | ); | |
9de69cdb DM |
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); | |
f9a5beaa | 254 | let state = Shell::new(catalog_reader, &server_archive_name, decoder).await?; |
9de69cdb DM |
255 | |
256 | println!("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("archive-name", complete_pxar_archive_name) | |
269 | .completion_cb("snapshot", complete_group_or_snapshot); | |
270 | ||
271 | let catalog_dump_cmd_def = CliCommand::new(&API_METHOD_DUMP_CATALOG) | |
272 | .arg_param(&["snapshot"]) | |
273 | .completion_cb("repository", complete_repository) | |
274 | .completion_cb("snapshot", complete_backup_snapshot); | |
275 | ||
276 | CliCommandMap::new() | |
277 | .insert("dump", catalog_dump_cmd_def) | |
278 | .insert("shell", catalog_shell_cmd_def) | |
279 | } |