Implement block-level checksum: Phase 1-4 complete
Phase 1: VfsBlockChecksum struct + JSON storage (~240 lines) - VfsBlockChecksum: offset + SHA-256 hash - VfsChecksumFile: block_size + algorithm + blocks + file_size - compute_block_hash() + verify_block_hash() - ChecksumMode: Lazy (default) + OnRead - ScrubResult: total/verified/corrupted/repaired blocks metrics Phase 2: ChecksumFile wrapper (~180 lines) - VfsFile wrapper with transparent checksum - Lazy verification (only on scrub) - Cache of verified blocks - Update checksum on flush() - read_at/write_at support Phase 3: Scrub API (~150 lines) - scrub_file(): verify single file integrity - scrub_all(): recursive directory scrub - create_checksums_for_file(): generate checksums - repair_block(): placeholder for RAID/Dedup Phase 4: RAID repair integration (~160 lines) - repair_block_from_parity(): reconstruct from RAID parity - reconstruct_from_p(): XOR reconstruction for RaidZ1 - reconstruct_from_pq/pqr(): placeholder for RaidZ2/3 Tests: 15 checksum tests pass (465 total) Files: - markbase-core/src/vfs/checksum.rs (NEW) - markbase-core/src/vfs/checksum_file.rs (NEW) - markbase-core/src/vfs/raid.rs (MOD +160 lines) - markbase-core/src/vfs/mod.rs (MOD +2 lines)
This commit is contained in:
@@ -162,6 +162,137 @@ impl VfsRaidBackend {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Repair a corrupted block from parity
|
||||
///
|
||||
/// This reads the block from surviving disks and reconstructs using parity.
|
||||
/// Works for RAID-Z1/2/3 (requires parity disks).
|
||||
pub fn repair_block_from_parity(
|
||||
&self,
|
||||
path: &Path,
|
||||
offset: u64,
|
||||
corrupted_disk_index: usize,
|
||||
) -> Result<Vec<u8>, VfsError> {
|
||||
if self.config.level == VfsRaidLevel::Single {
|
||||
return Err(VfsError::Io("Cannot repair from single disk RAID".to_string()));
|
||||
}
|
||||
|
||||
if corrupted_disk_index >= self.backends.len() {
|
||||
return Err(VfsError::Io(format!("Invalid disk index {}", corrupted_disk_index)));
|
||||
}
|
||||
|
||||
let block_size = self.stripe_size;
|
||||
let mut data_blocks: Vec<Option<Vec<u8>>> = vec![None; self.backends.len()];
|
||||
let mut parity_blocks: Vec<Vec<u8>> = vec![];
|
||||
|
||||
for (i, backend) in self.backends.iter().enumerate() {
|
||||
if i == corrupted_disk_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut file = backend.open_file(path, &super::open_flags::OpenFlags::new().read())?;
|
||||
let mut buffer = vec![0u8; block_size];
|
||||
let bytes_read = file.read_at(&mut buffer, offset)?;
|
||||
|
||||
if bytes_read > 0 {
|
||||
if i < self.data_disks() {
|
||||
data_blocks[i] = Some(buffer[..bytes_read].to_vec());
|
||||
} else {
|
||||
parity_blocks.push(buffer[..bytes_read].to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.config.level {
|
||||
VfsRaidLevel::RaidZ1 => {
|
||||
if parity_blocks.len() < 1 {
|
||||
return Err(VfsError::Io("Not enough parity for RaidZ1 repair".to_string()));
|
||||
}
|
||||
let reconstructed = Self::reconstruct_from_p(
|
||||
&data_blocks,
|
||||
&parity_blocks[0],
|
||||
corrupted_disk_index,
|
||||
self.data_disks(),
|
||||
);
|
||||
Ok(reconstructed)
|
||||
}
|
||||
VfsRaidLevel::RaidZ2 => {
|
||||
if parity_blocks.len() < 2 {
|
||||
return Err(VfsError::Io("Not enough parity for RaidZ2 repair".to_string()));
|
||||
}
|
||||
let reconstructed = Self::reconstruct_from_pq(
|
||||
&data_blocks,
|
||||
&parity_blocks[0],
|
||||
&parity_blocks[1],
|
||||
corrupted_disk_index,
|
||||
self.data_disks(),
|
||||
);
|
||||
Ok(reconstructed)
|
||||
}
|
||||
VfsRaidLevel::RaidZ3 => {
|
||||
if parity_blocks.len() < 3 {
|
||||
return Err(VfsError::Io("Not enough parity for RaidZ3 repair".to_string()));
|
||||
}
|
||||
let reconstructed = Self::reconstruct_from_pqr(
|
||||
&data_blocks,
|
||||
&parity_blocks[0],
|
||||
&parity_blocks[1],
|
||||
&parity_blocks[2],
|
||||
corrupted_disk_index,
|
||||
self.data_disks(),
|
||||
);
|
||||
Ok(reconstructed)
|
||||
}
|
||||
_ => Err(VfsError::Io("RAID level does not support block repair".to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn reconstruct_from_p(
|
||||
data_blocks: &[Option<Vec<u8>>],
|
||||
p_block: &[u8],
|
||||
missing_index: usize,
|
||||
data_disk_count: usize,
|
||||
) -> Vec<u8> {
|
||||
let size = p_block.len();
|
||||
let mut reconstructed = vec![0u8; size];
|
||||
|
||||
for i in 0..data_disk_count {
|
||||
if i != missing_index {
|
||||
if let Some(data) = &data_blocks[i] {
|
||||
for j in 0..size {
|
||||
reconstructed[j] ^= data[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for j in 0..size {
|
||||
reconstructed[j] ^= p_block[j];
|
||||
}
|
||||
|
||||
reconstructed
|
||||
}
|
||||
|
||||
fn reconstruct_from_pq(
|
||||
data_blocks: &[Option<Vec<u8>>],
|
||||
p_block: &[u8],
|
||||
q_block: &[u8],
|
||||
missing_index: usize,
|
||||
data_disk_count: usize,
|
||||
) -> Vec<u8> {
|
||||
Self::reconstruct_from_p(data_blocks, p_block, missing_index, data_disk_count)
|
||||
}
|
||||
|
||||
fn reconstruct_from_pqr(
|
||||
data_blocks: &[Option<Vec<u8>>],
|
||||
p_block: &[u8],
|
||||
q_block: &[u8],
|
||||
r_block: &[u8],
|
||||
missing_index: usize,
|
||||
data_disk_count: usize,
|
||||
) -> Vec<u8> {
|
||||
Self::reconstruct_from_p(data_blocks, p_block, missing_index, data_disk_count)
|
||||
}
|
||||
}
|
||||
|
||||
impl VfsBackend for VfsRaidBackend {
|
||||
|
||||
Reference in New Issue
Block a user