]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_backup_debug/recover.rs
update to proxmox-sys 0.2 crate
[proxmox-backup.git] / src / bin / proxmox_backup_debug / recover.rs
1 use std::fs::File;
2 use std::io::{Read, Seek, SeekFrom, Write};
3 use std::path::Path;
4
5 use anyhow::{bail, format_err, Error};
6 use serde_json::Value;
7
8 use proxmox_router::cli::{CliCommand, CliCommandMap, CommandLineInterface};
9 use proxmox_schema::api;
10
11 use pbs_tools::crypt_config::CryptConfig;
12 use pbs_datastore::dynamic_index::DynamicIndexReader;
13 use pbs_datastore::file_formats::{DYNAMIC_SIZED_CHUNK_INDEX_1_0, FIXED_SIZED_CHUNK_INDEX_1_0};
14 use pbs_datastore::fixed_index::FixedIndexReader;
15 use pbs_datastore::index::IndexFile;
16 use pbs_datastore::DataBlob;
17 use pbs_config::key_config::load_and_decrypt_key;
18 use pbs_client::tools::key_source::get_encryption_key_password;
19
20 #[api(
21 input: {
22 properties: {
23 file: {
24 description: "Path to the index file, either .fidx or .didx.",
25 type: String,
26 },
27 chunks: {
28 description: "Path to the directorty that contains the chunks, usually <datastore>/.chunks.",
29 type: String,
30 },
31 "keyfile": {
32 description: "Path to a keyfile, if the data was encrypted, a keyfile is needed for decryption.",
33 type: String,
34 optional: true,
35 },
36 "skip-crc": {
37 description: "Skip the crc verification, increases the restore speed by lot.",
38 type: Boolean,
39 optional: true,
40 default: false,
41 }
42 }
43 }
44 )]
45 /// Restore the data from an index file, given the directory of where chunks
46 /// are saved, the index file and a keyfile, if needed for decryption.
47 fn recover_index(
48 file: String,
49 chunks: String,
50 keyfile: Option<String>,
51 skip_crc: bool,
52 _param: Value,
53 ) -> Result<(), Error> {
54 let file_path = Path::new(&file);
55 let chunks_path = Path::new(&chunks);
56
57 let key_file_path = keyfile.as_ref().map(Path::new);
58
59 let mut file = File::open(Path::new(&file))?;
60 let mut magic = [0; 8];
61 file.read_exact(&mut magic)?;
62 file.seek(SeekFrom::Start(0))?;
63 let index: Box<dyn IndexFile> = match magic {
64 FIXED_SIZED_CHUNK_INDEX_1_0 => Box::new(FixedIndexReader::new(file)?) as Box<dyn IndexFile>,
65 DYNAMIC_SIZED_CHUNK_INDEX_1_0 => {
66 Box::new(DynamicIndexReader::new(file)?) as Box<dyn IndexFile>
67 }
68 _ => bail!(format_err!(
69 "index file must either be a .fidx or a .didx file"
70 )),
71 };
72
73 let crypt_conf_opt = if let Some(key_file_path) = key_file_path {
74 let (key, _created, _fingerprint) =
75 load_and_decrypt_key(&key_file_path, &get_encryption_key_password)?;
76 Some(CryptConfig::new(key)?)
77 } else {
78 None
79 };
80
81 let output_filename = file_path.file_stem().unwrap().to_str().unwrap();
82 let output_path = Path::new(output_filename);
83 let mut output_file = File::create(output_path)
84 .map_err(|e| format_err!("could not create output file - {}", e))?;
85
86 let mut data = Vec::with_capacity(4 * 1024 * 1024);
87 for pos in 0..index.index_count() {
88 let chunk_digest = index.index_digest(pos).unwrap();
89 let digest_str = hex::encode(chunk_digest);
90 let digest_prefix = &digest_str[0..4];
91 let chunk_path = chunks_path.join(digest_prefix).join(digest_str);
92 let mut chunk_file = std::fs::File::open(&chunk_path)
93 .map_err(|e| format_err!("could not open chunk file - {}", e))?;
94
95 data.clear();
96 chunk_file.read_to_end(&mut data)?;
97 let chunk_blob = DataBlob::from_raw(data.clone())?;
98
99 if !skip_crc {
100 chunk_blob.verify_crc()?;
101 }
102
103 output_file.write_all(
104 chunk_blob
105 .decode(crypt_conf_opt.as_ref(), Some(chunk_digest))?
106 .as_slice(),
107 )?;
108 }
109
110 Ok(())
111 }
112
113 pub fn recover_commands() -> CommandLineInterface {
114 let cmd_def = CliCommandMap::new().insert(
115 "index",
116 CliCommand::new(&API_METHOD_RECOVER_INDEX).arg_param(&["file", "chunks"]),
117 );
118 cmd_def.into()
119 }