]> git.proxmox.com Git - proxmox-backup.git/blame - proxmox-backup-client/src/catalog.rs
split the namespace out of BackupGroup/Dir api types
[proxmox-backup.git] / proxmox-backup-client / src / catalog.rs
CommitLineData
9de69cdb 1use std::io::{Seek, SeekFrom};
f9a5beaa 2use std::os::unix::fs::OpenOptionsExt;
9de69cdb
DM
3use std::sync::Arc;
4
5use anyhow::{bail, format_err, Error};
6use serde_json::Value;
7
6ef1b649 8use proxmox_router::cli::*;
f9a5beaa 9use proxmox_schema::api;
9de69cdb 10
133d718f 11use pbs_api_types::BackupNamespace;
2b7f8dd5
WB
12use pbs_client::tools::key_source::get_encryption_key_password;
13use pbs_client::{BackupReader, RemoteChunkReader};
bbdda58b 14use pbs_tools::crypt_config::CryptConfig;
f9a5beaa 15use pbs_tools::json::required_string_param;
9de69cdb
DM
16
17use 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.
53async fn dump_catalog(param: Value) -> Result<Value, Error> {
9de69cdb
DM
54 let repo = extract_repository_from_value(&param)?;
55
133d718f 56 let backup_ns = optional_ns_param(&param)?;
3c8c2827 57 let path = required_string_param(&param, "snapshot")?;
9de69cdb
DM
58 let snapshot: BackupDir = path.parse()?;
59
c6a7ea0a 60 let crypto = crypto_parameters(&param)?;
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.
159async fn catalog_shell(param: Value) -> Result<(), Error> {
160 let repo = extract_repository_from_value(&param)?;
f3fde36b 161 let client = connect(&repo)?;
133d718f 162 let backup_ns = optional_ns_param(&param)?;
3c8c2827
WB
163 let path = required_string_param(&param, "snapshot")?;
164 let archive_name = required_string_param(&param, "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(&param)?;
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
264pub 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}