]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox_backup_client/catalog.rs
src/backup/manifest.rs: include signature inside the manifest
[proxmox-backup.git] / src / bin / proxmox_backup_client / catalog.rs
CommitLineData
9de69cdb
DM
1use std::os::unix::fs::OpenOptionsExt;
2use std::io::{Seek, SeekFrom};
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use anyhow::{bail, format_err, Error};
7use serde_json::Value;
8
9use proxmox::api::{api, cli::*};
10
11use proxmox_backup::tools;
12
13use proxmox_backup::client::*;
14
15use 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
37use proxmox_backup::backup::load_and_decrypt_key;
38
9696f519 39use 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.
56async fn dump_catalog(param: Value) -> Result<Value, Error> {
57
58 let repo = extract_repository_from_value(&param)?;
59
60 let path = tools::required_string_param(&param, "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.
139async fn catalog_shell(param: Value) -> Result<(), Error> {
140 let repo = extract_repository_from_value(&param)?;
141 let client = connect(repo.host(), repo.user())?;
142 let path = tools::required_string_param(&param, "snapshot")?;
143 let archive_name = tools::required_string_param(&param, "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
231pub 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}