1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Opt-in RGD recovery — resolving reads through the redundant grain directory
//! when the primary grain directory is damaged. The read path (`grain_location`,
//! `iter_allocated_grains`) calls into these resolvers; they are no-ops unless
//! `enable_rgd_fallback()` was called.
use std::io::{self, Read, Seek, SeekFrom};
use crate::header::{GD_AT_END, SECTOR_SIZE};
use crate::VmdkReader;
impl<R: Read + Seek> VmdkReader<R> {
/// Enable opt-in RGD fallback: a read whose primary grain-table pointer is out of
/// bounds is resolved through the redundant grain directory instead, recovering
/// data from a damaged primary GD that `qemu-img` would simply fail on.
pub fn enable_rgd_fallback(&mut self) {
self.rgd_fallback = true;
}
/// Number of grains resolved via the redundant grain directory so far (pointer- or
/// entry-level recovery). Zero on a healthy image; non-zero quantifies how much of a
/// damaged image was reconstructed from the RGD.
#[must_use]
pub fn rgd_recovery_count(&self) -> u64 {
self.rgd_recovery_count
}
/// Resolve the grain-table sector for `gd_idx`, preferring the primary pointer and
/// falling back to the redundant grain directory when the primary is unusable.
/// Returns the primary pointer unchanged when no better candidate exists, so the
/// non-fallback error/sparse behaviour is preserved.
pub(crate) fn resilient_gt_sector(
&mut self,
gd_idx: usize,
primary: u32,
num_gtes_per_gt: u64,
) -> io::Result<u32> {
let file_len = self.inner.seek(SeekFrom::End(0))?;
let gt_byte_len = num_gtes_per_gt * 4;
let usable = |sec: u32| {
sec != 0
&& u64::from(sec)
.saturating_mul(SECTOR_SIZE)
.saturating_add(gt_byte_len)
<= file_len
};
if usable(primary) {
return Ok(primary);
}
let rgd = self.rgd_dir_entry(gd_idx, file_len)?;
if usable(rgd) {
crate::diag::pointer_recovered(gd_idx, primary, rgd);
return Ok(rgd);
}
Ok(primary)
}
/// Read entry `gd_idx` from the redundant grain directory, or 0 if the RGD is
/// absent or the entry itself would fall outside the file.
pub(crate) fn rgd_dir_entry(&mut self, gd_idx: usize, file_len: u64) -> io::Result<u32> {
if self.rgd_offset == 0 || self.rgd_offset == GD_AT_END {
return Ok(0);
}
if gd_idx >= self.gd_entry_count {
return Ok(0);
}
let entry_byte = self
.rgd_offset
.saturating_mul(SECTOR_SIZE)
.saturating_add(gd_idx as u64 * 4);
if entry_byte.saturating_add(4) > file_len {
return Ok(0);
}
let mut b = [0u8; 4];
self.read_exact_at(entry_byte, &mut b)?;
Ok(u32::from_le_bytes(b))
}
/// Read the full redundant grain table referenced by RGD entry `gd_idx`, or `None`
/// if the RGD entry is absent or the grain table would fall outside the file.
pub(crate) fn read_redundant_gt(
&mut self,
gd_idx: usize,
num_gtes_per_gt: u64,
) -> io::Result<Option<Vec<u8>>> {
let file_len = self.inner.seek(SeekFrom::End(0))?;
let sector = self.rgd_dir_entry(gd_idx, file_len)?;
if sector == 0 {
return Ok(None);
}
let gt_byte = u64::from(sector) * SECTOR_SIZE;
let gt_byte_len = num_gtes_per_gt * 4;
if gt_byte.saturating_add(gt_byte_len) > file_len {
return Ok(None);
}
let mut b = vec![0u8; gt_byte_len as usize];
self.read_exact_at(gt_byte, &mut b)?;
Ok(Some(b))
}
/// Read grain-table entry `gte_idx` from the redundant grain table referenced by
/// RGD entry `gd_idx`, or 0 if the RGD entry or the target entry is out of bounds.
/// Used for content-level recovery when a primary GT entry has been lost.
pub(crate) fn rgd_gte(
&mut self,
gd_idx: usize,
gte_idx: u64,
num_gtes_per_gt: u64,
) -> io::Result<u32> {
let file_len = self.inner.seek(SeekFrom::End(0))?;
let rgd_gt_sector = self.rgd_dir_entry(gd_idx, file_len)?;
if rgd_gt_sector == 0 {
return Ok(0);
}
let gt_byte = u64::from(rgd_gt_sector) * SECTOR_SIZE;
let gt_byte_len = num_gtes_per_gt * 4;
if gt_byte.saturating_add(gt_byte_len) > file_len {
return Ok(0);
}
let entry_byte = gt_byte + gte_idx * 4;
if entry_byte.saturating_add(4) > file_len {
return Ok(0);
}
let mut b = [0u8; 4];
self.read_exact_at(entry_byte, &mut b)?;
Ok(u32::from_le_bytes(b))
}
}