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