1 use std
::collections
::HashSet
;
3 use anyhow
::{bail, format_err, Error}
;
5 use crate::server
::WorkerTask
;
6 use crate::api2
::types
::*;
9 DataStore
, BackupGroup
, BackupDir
, BackupInfo
, IndexFile
,
11 FileInfo
, ArchiveType
, archive_type
,
14 fn verify_blob(datastore
: &DataStore
, backup_dir
: &BackupDir
, info
: &FileInfo
) -> Result
<(), Error
> {
16 let blob
= datastore
.load_blob(backup_dir
, &info
.filename
)?
;
18 let raw_size
= blob
.raw_size();
19 if raw_size
!= info
.size
{
20 bail
!("wrong size ({} != {})", info
.size
, raw_size
);
23 let csum
= openssl
::sha
::sha256(blob
.raw_data());
24 if csum
!= info
.csum
{
25 bail
!("wrong index checksum");
28 match blob
.crypt_mode()?
{
29 CryptMode
::Encrypt
=> Ok(()),
31 // digest already verified above
32 blob
.decode(None
, None
)?
;
35 CryptMode
::SignOnly
=> bail
!("Invalid CryptMode for blob"),
39 fn verify_index_chunks(
40 datastore
: &DataStore
,
41 index
: Box
<dyn IndexFile
>,
42 verified_chunks
: &mut HashSet
<[u8;32]>,
43 corrupt_chunks
: &mut HashSet
<[u8; 32]>,
44 crypt_mode
: CryptMode
,
46 ) -> Result
<(), Error
> {
49 for pos
in 0..index
.index_count() {
51 worker
.fail_on_abort()?
;
53 let info
= index
.chunk_info(pos
).unwrap();
55 if verified_chunks
.contains(&info
.digest
) {
56 continue; // already verified
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
));
66 let chunk
= match datastore
.load_chunk(&info
.digest
) {
68 corrupt_chunks
.insert(info
.digest
);
69 worker
.log(format
!("can't verify chunk, load failed - {}", err
));
76 let chunk_crypt_mode
= match chunk
.crypt_mode() {
78 corrupt_chunks
.insert(info
.digest
);
79 worker
.log(format
!("can't verify chunk, unknown CryptMode - {}", err
));
86 if chunk_crypt_mode
!= crypt_mode
{
88 "chunk CryptMode {:?} does not match index CryptMode {:?}",
95 let size
= info
.range
.end
- info
.range
.start
;
97 if let Err(err
) = chunk
.verify_unencrypted(size
as usize, &info
.digest
) {
98 corrupt_chunks
.insert(info
.digest
);
99 worker
.log(format
!("{}", err
));
102 verified_chunks
.insert(info
.digest
);
107 bail
!("chunks could not be verified");
113 fn verify_fixed_index(
114 datastore
: &DataStore
,
115 backup_dir
: &BackupDir
,
117 verified_chunks
: &mut HashSet
<[u8;32]>,
118 corrupt_chunks
: &mut HashSet
<[u8;32]>,
120 ) -> Result
<(), Error
> {
122 let mut path
= backup_dir
.relative_path();
123 path
.push(&info
.filename
);
125 let index
= datastore
.open_fixed_reader(&path
)?
;
127 let (csum
, size
) = index
.compute_csum();
128 if size
!= info
.size
{
129 bail
!("wrong size ({} != {})", info
.size
, size
);
132 if csum
!= info
.csum
{
133 bail
!("wrong index checksum");
136 verify_index_chunks(datastore
, Box
::new(index
), verified_chunks
, corrupt_chunks
, info
.chunk_crypt_mode(), worker
)
139 fn verify_dynamic_index(
140 datastore
: &DataStore
,
141 backup_dir
: &BackupDir
,
143 verified_chunks
: &mut HashSet
<[u8;32]>,
144 corrupt_chunks
: &mut HashSet
<[u8;32]>,
146 ) -> Result
<(), Error
> {
148 let mut path
= backup_dir
.relative_path();
149 path
.push(&info
.filename
);
151 let index
= datastore
.open_dynamic_reader(&path
)?
;
153 let (csum
, size
) = index
.compute_csum();
154 if size
!= info
.size
{
155 bail
!("wrong size ({} != {})", info
.size
, size
);
158 if csum
!= info
.csum
{
159 bail
!("wrong index checksum");
162 verify_index_chunks(datastore
, Box
::new(index
), verified_chunks
, corrupt_chunks
, info
.chunk_crypt_mode(), worker
)
165 /// Verify a single backup snapshot
167 /// This checks all archives inside a backup snapshot.
168 /// Errors are logged to the worker log.
171 /// - Ok(true) if verify is successful
172 /// - Ok(false) if there were verification errors
173 /// - Err(_) if task was aborted
174 pub fn verify_backup_dir(
175 datastore
: &DataStore
,
176 backup_dir
: &BackupDir
,
177 verified_chunks
: &mut HashSet
<[u8;32]>,
178 corrupt_chunks
: &mut HashSet
<[u8;32]>,
180 ) -> Result
<bool
, Error
> {
182 let mut manifest
= match datastore
.load_manifest(&backup_dir
) {
183 Ok((manifest
, _
)) => manifest
,
185 worker
.log(format
!("verify {}:{} - manifest load error: {}", datastore
.name(), backup_dir
, err
));
190 worker
.log(format
!("verify {}:{}", datastore
.name(), backup_dir
));
192 let mut error_count
= 0;
194 let mut verify_result
= "ok";
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
)?
{
199 ArchiveType
::FixedIndex
=>
208 ArchiveType
::DynamicIndex
=>
209 verify_dynamic_index(
217 ArchiveType
::Blob
=> verify_blob(&datastore
, &backup_dir
, info
),
221 worker
.fail_on_abort()?
;
223 if let Err(err
) = result
{
224 worker
.log(format
!("verify {}:{}/{} failed: {}", datastore
.name(), backup_dir
, info
.filename
, err
));
226 verify_result
= "failed";
231 let verify_state
= SnapshotVerifyState
{
232 state
: verify_result
.to_string(),
233 upid
: worker
.upid().clone(),
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
))?
;
243 /// Verify all backups inside a backup group
245 /// Errors are logged to the worker log.
248 /// - Ok(failed_dirs) where failed_dirs had verification errors
249 /// - Err(_) if task was aborted
250 pub fn verify_backup_group(datastore
: &DataStore
, group
: &BackupGroup
, worker
: &WorkerTask
) -> Result
<Vec
<String
>, Error
> {
252 let mut errors
= Vec
::new();
253 let mut list
= match group
.list_backups(&datastore
.base_path()) {
256 worker
.log(format
!("verify group {}:{} - unable to list backups: {}", datastore
.name(), group
, err
));
261 worker
.log(format
!("verify group {}:{}", datastore
.name(), group
));
263 let mut verified_chunks
= HashSet
::with_capacity(1024*16); // start with 16384 chunks (up to 65GB)
264 let mut corrupt_chunks
= HashSet
::with_capacity(64); // start with 64 chunks since we assume there are few corrupt ones
266 BackupInfo
::sort_list(&mut list
, false); // newest first
268 if !verify_backup_dir(datastore
, &info
.backup_dir
, &mut verified_chunks
, &mut corrupt_chunks
, worker
)?
{
269 errors
.push(info
.backup_dir
.to_string());
276 /// Verify all backups inside a datastore
278 /// Errors are logged to the worker log.
281 /// - Ok(failed_dirs) where failed_dirs had verification errors
282 /// - Err(_) if task was aborted
283 pub fn verify_all_backups(datastore
: &DataStore
, worker
: &WorkerTask
) -> Result
<Vec
<String
>, Error
> {
285 let mut errors
= Vec
::new();
287 let mut list
= match BackupGroup
::list_groups(&datastore
.base_path()) {
290 worker
.log(format
!("verify datastore {} - unable to list backups: {}", datastore
.name(), err
));
295 list
.sort_unstable();
297 worker
.log(format
!("verify datastore {}", datastore
.name()));
300 let mut group_errors
= verify_backup_group(datastore
, &group
, worker
)?
;
301 errors
.append(&mut group_errors
);