]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/verify.rs
debian/control: fix versions
[proxmox-backup.git] / src / backup / verify.rs
CommitLineData
2aaae970
DM
1use std::collections::HashSet;
2
3b2046d2 3use anyhow::{bail, format_err, Error};
c2009e53
DM
4
5use crate::server::WorkerTask;
3b2046d2 6use crate::api2::types::*;
c2009e53
DM
7
8use super::{
9 DataStore, BackupGroup, BackupDir, BackupInfo, IndexFile,
8819d1f2 10 CryptMode,
c2009e53
DM
11 FileInfo, ArchiveType, archive_type,
12};
13
14fn verify_blob(datastore: &DataStore, backup_dir: &BackupDir, info: &FileInfo) -> Result<(), Error> {
15
39f18b30 16 let blob = datastore.load_blob(backup_dir, &info.filename)?;
c2009e53 17
2aaae970 18 let raw_size = blob.raw_size();
c2009e53
DM
19 if raw_size != info.size {
20 bail!("wrong size ({} != {})", info.size, raw_size);
21 }
22
39f18b30 23 let csum = openssl::sha::sha256(blob.raw_data());
c2009e53
DM
24 if csum != info.csum {
25 bail!("wrong index checksum");
26 }
27
8819d1f2
FG
28 match blob.crypt_mode()? {
29 CryptMode::Encrypt => Ok(()),
30 CryptMode::None => {
31 // digest already verified above
32 blob.decode(None, None)?;
33 Ok(())
34 },
35 CryptMode::SignOnly => bail!("Invalid CryptMode for blob"),
c2009e53 36 }
c2009e53
DM
37}
38
fdaab0df
DM
39fn verify_index_chunks(
40 datastore: &DataStore,
41 index: Box<dyn IndexFile>,
2aaae970 42 verified_chunks: &mut HashSet<[u8;32]>,
d8594d87 43 corrupt_chunks: &mut HashSet<[u8; 32]>,
9a38fa29 44 crypt_mode: CryptMode,
fdaab0df
DM
45 worker: &WorkerTask,
46) -> Result<(), Error> {
47
f66f537d 48 let mut errors = 0;
fdaab0df
DM
49 for pos in 0..index.index_count() {
50
51 worker.fail_on_abort()?;
fdaab0df
DM
52
53 let info = index.chunk_info(pos).unwrap();
7ae571e7
DM
54
55 if verified_chunks.contains(&info.digest) {
56 continue; // already verified
57 }
58
59 if corrupt_chunks.contains(&info.digest) {
60 let digest_str = proxmox::tools::digest_to_hex(&info.digest);
61 worker.log(format!("chunk {} was marked as corrupt", digest_str));
62 errors += 1;
63 continue;
64 }
2aaae970 65
9a38fa29
FG
66 let chunk = match datastore.load_chunk(&info.digest) {
67 Err(err) => {
68 corrupt_chunks.insert(info.digest);
69 worker.log(format!("can't verify chunk, load failed - {}", err));
70 errors += 1;
71 continue;
72 },
73 Ok(chunk) => chunk,
74 };
75
76 let chunk_crypt_mode = match chunk.crypt_mode() {
77 Err(err) => {
78 corrupt_chunks.insert(info.digest);
79 worker.log(format!("can't verify chunk, unknown CryptMode - {}", err));
80 errors += 1;
81 continue;
82 },
83 Ok(mode) => mode,
84 };
85
86 if chunk_crypt_mode != crypt_mode {
87 worker.log(format!(
88 "chunk CryptMode {:?} does not match index CryptMode {:?}",
89 chunk_crypt_mode,
90 crypt_mode
91 ));
92 errors += 1;
93 }
94
7ae571e7
DM
95 let size = info.range.end - info.range.start;
96
97 if let Err(err) = chunk.verify_unencrypted(size as usize, &info.digest) {
98 corrupt_chunks.insert(info.digest);
99 worker.log(format!("{}", err));
100 errors += 1;
101 } else {
102 verified_chunks.insert(info.digest);
2aaae970 103 }
fdaab0df
DM
104 }
105
f66f537d
DC
106 if errors > 0 {
107 bail!("chunks could not be verified");
108 }
109
fdaab0df
DM
110 Ok(())
111}
112
2aaae970
DM
113fn verify_fixed_index(
114 datastore: &DataStore,
115 backup_dir: &BackupDir,
116 info: &FileInfo,
117 verified_chunks: &mut HashSet<[u8;32]>,
d8594d87 118 corrupt_chunks: &mut HashSet<[u8;32]>,
2aaae970
DM
119 worker: &WorkerTask,
120) -> Result<(), Error> {
c2009e53
DM
121
122 let mut path = backup_dir.relative_path();
123 path.push(&info.filename);
124
125 let index = datastore.open_fixed_reader(&path)?;
126
127 let (csum, size) = index.compute_csum();
128 if size != info.size {
129 bail!("wrong size ({} != {})", info.size, size);
130 }
131
132 if csum != info.csum {
133 bail!("wrong index checksum");
134 }
135
9a38fa29 136 verify_index_chunks(datastore, Box::new(index), verified_chunks, corrupt_chunks, info.chunk_crypt_mode(), worker)
c2009e53
DM
137}
138
2aaae970
DM
139fn verify_dynamic_index(
140 datastore: &DataStore,
141 backup_dir: &BackupDir,
142 info: &FileInfo,
143 verified_chunks: &mut HashSet<[u8;32]>,
d8594d87 144 corrupt_chunks: &mut HashSet<[u8;32]>,
2aaae970
DM
145 worker: &WorkerTask,
146) -> Result<(), Error> {
147
c2009e53
DM
148 let mut path = backup_dir.relative_path();
149 path.push(&info.filename);
150
151 let index = datastore.open_dynamic_reader(&path)?;
152
153 let (csum, size) = index.compute_csum();
154 if size != info.size {
155 bail!("wrong size ({} != {})", info.size, size);
156 }
157
158 if csum != info.csum {
159 bail!("wrong index checksum");
160 }
161
9a38fa29 162 verify_index_chunks(datastore, Box::new(index), verified_chunks, corrupt_chunks, info.chunk_crypt_mode(), worker)
c2009e53
DM
163}
164
165/// Verify a single backup snapshot
166///
167/// This checks all archives inside a backup snapshot.
168/// Errors are logged to the worker log.
169///
8ea00f6e
DM
170/// Returns
171/// - Ok(true) if verify is successful
172/// - Ok(false) if there were verification errors
173/// - Err(_) if task was aborted
2aaae970
DM
174pub fn verify_backup_dir(
175 datastore: &DataStore,
176 backup_dir: &BackupDir,
177 verified_chunks: &mut HashSet<[u8;32]>,
d8594d87 178 corrupt_chunks: &mut HashSet<[u8;32]>,
2aaae970
DM
179 worker: &WorkerTask
180) -> Result<bool, Error> {
c2009e53 181
3b2046d2 182 let mut manifest = match datastore.load_manifest(&backup_dir) {
ff86ef00 183 Ok((manifest, _)) => manifest,
c2009e53
DM
184 Err(err) => {
185 worker.log(format!("verify {}:{} - manifest load error: {}", datastore.name(), backup_dir, err));
8ea00f6e 186 return Ok(false);
c2009e53
DM
187 }
188 };
189
190 worker.log(format!("verify {}:{}", datastore.name(), backup_dir));
191
192 let mut error_count = 0;
193
3b2046d2 194 let mut verify_result = "ok";
c2009e53
DM
195 for info in manifest.files() {
196 let result = proxmox::try_block!({
197 worker.log(format!(" check {}", info.filename));
198 match archive_type(&info.filename)? {
d8594d87
DC
199 ArchiveType::FixedIndex =>
200 verify_fixed_index(
201 &datastore,
202 &backup_dir,
203 info,
204 verified_chunks,
205 corrupt_chunks,
206 worker
207 ),
208 ArchiveType::DynamicIndex =>
209 verify_dynamic_index(
210 &datastore,
211 &backup_dir,
212 info,
213 verified_chunks,
214 corrupt_chunks,
215 worker
216 ),
c2009e53
DM
217 ArchiveType::Blob => verify_blob(&datastore, &backup_dir, info),
218 }
219 });
8ea00f6e
DM
220
221 worker.fail_on_abort()?;
8ea00f6e 222
c2009e53
DM
223 if let Err(err) = result {
224 worker.log(format!("verify {}:{}/{} failed: {}", datastore.name(), backup_dir, info.filename, err));
225 error_count += 1;
3b2046d2 226 verify_result = "failed";
c2009e53 227 }
3b2046d2 228
c2009e53
DM
229 }
230
3b2046d2
TL
231 let verify_state = SnapshotVerifyState {
232 state: verify_result.to_string(),
233 upid: worker.upid().clone(),
234 };
235 manifest.unprotected["verify_state"] = serde_json::to_value(verify_state)?;
236 datastore.store_manifest(&backup_dir, serde_json::to_value(manifest)?)
237 .map_err(|err| format_err!("unable to store manifest blob - {}", err))?;
238
239
8ea00f6e 240 Ok(error_count == 0)
c2009e53
DM
241}
242
8ea00f6e
DM
243/// Verify all backups inside a backup group
244///
245/// Errors are logged to the worker log.
246///
247/// Returns
adfdc369 248/// - Ok(failed_dirs) where failed_dirs had verification errors
8ea00f6e 249/// - Err(_) if task was aborted
adfdc369 250pub fn verify_backup_group(datastore: &DataStore, group: &BackupGroup, worker: &WorkerTask) -> Result<Vec<String>, Error> {
c2009e53 251
adfdc369 252 let mut errors = Vec::new();
c2009e53
DM
253 let mut list = match group.list_backups(&datastore.base_path()) {
254 Ok(list) => list,
255 Err(err) => {
256 worker.log(format!("verify group {}:{} - unable to list backups: {}", datastore.name(), group, err));
adfdc369 257 return Ok(errors);
c2009e53
DM
258 }
259 };
260
261 worker.log(format!("verify group {}:{}", datastore.name(), group));
262
2aaae970 263 let mut verified_chunks = HashSet::with_capacity(1024*16); // start with 16384 chunks (up to 65GB)
d8594d87 264 let mut corrupt_chunks = HashSet::with_capacity(64); // start with 64 chunks since we assume there are few corrupt ones
2aaae970 265
c2009e53
DM
266 BackupInfo::sort_list(&mut list, false); // newest first
267 for info in list {
d8594d87 268 if !verify_backup_dir(datastore, &info.backup_dir, &mut verified_chunks, &mut corrupt_chunks, worker)?{
adfdc369 269 errors.push(info.backup_dir.to_string());
c2009e53
DM
270 }
271 }
272
adfdc369 273 Ok(errors)
c2009e53
DM
274}
275
8ea00f6e
DM
276/// Verify all backups inside a datastore
277///
278/// Errors are logged to the worker log.
279///
280/// Returns
adfdc369 281/// - Ok(failed_dirs) where failed_dirs had verification errors
8ea00f6e 282/// - Err(_) if task was aborted
adfdc369
DC
283pub fn verify_all_backups(datastore: &DataStore, worker: &WorkerTask) -> Result<Vec<String>, Error> {
284
285 let mut errors = Vec::new();
c2009e53 286
4264c502 287 let mut list = match BackupGroup::list_groups(&datastore.base_path()) {
c2009e53
DM
288 Ok(list) => list,
289 Err(err) => {
290 worker.log(format!("verify datastore {} - unable to list backups: {}", datastore.name(), err));
adfdc369 291 return Ok(errors);
c2009e53
DM
292 }
293 };
294
4264c502
DM
295 list.sort_unstable();
296
c2009e53
DM
297 worker.log(format!("verify datastore {}", datastore.name()));
298
c2009e53 299 for group in list {
adfdc369
DC
300 let mut group_errors = verify_backup_group(datastore, &group, worker)?;
301 errors.append(&mut group_errors);
c2009e53
DM
302 }
303
adfdc369 304 Ok(errors)
c2009e53 305}