use crate::fscore::structs::{Blkptr, DnodePhys};
use crate::integrity::checksum::Checksum;
use crate::lunaos::kernel::BlockDevice;
use crate::mgmt::mount::LcpfsMount;
use crate::{BLOCK_DEVICES, FsError, FsResult};
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
const MAX_INDIRECT_DEPTH: usize = 128;
#[derive(Debug, Clone)]
pub struct BlockMetadata {
pub object_id: u64,
pub offset: u64,
pub txg: u64,
pub blkptr_index: u8,
pub blkptr: Blkptr,
}
pub struct ReverseIndex {
map: BTreeMap<u64, BlockMetadata>,
}
impl Default for ReverseIndex {
fn default() -> Self {
Self::new()
}
}
impl ReverseIndex {
pub fn new() -> Self {
Self {
map: BTreeMap::new(),
}
}
pub fn build_from_pool(mount: &LcpfsMount) -> FsResult<Self> {
let mut index = Self::new();
if let Some(ref root_dnode) = mount.root_dnode {
index.traverse_dnode(root_dnode, 0, 0)?;
}
crate::lcpfs_println!("[ SCRUB] Built reverse index: {} blocks", index.map.len());
Ok(index)
}
fn traverse_dnode(&mut self, dnode: &DnodePhys, object_id: u64, depth: usize) -> FsResult<()> {
for (i, blkptr) in dnode.blkptr.iter().enumerate() {
if blkptr.is_hole() {
continue;
}
let block_id = blkptr.dva[0].offset;
let meta = BlockMetadata {
object_id,
offset: i as u64 * 4096, txg: blkptr.birth_txg,
blkptr_index: i as u8,
blkptr: *blkptr,
};
self.map.insert(block_id, meta);
}
Ok(())
}
pub fn get_metadata(&self, block_id: u64) -> Option<&BlockMetadata> {
self.map.get(&block_id)
}
pub fn scrub(&self, mount: &LcpfsMount) -> FsResult<SolutionScrubStats> {
let mut stats = SolutionScrubStats::default();
let total_blocks = {
let devices = BLOCK_DEVICES.lock();
let dev = devices.get(mount.dev_id).ok_or(FsError::NotFound)?;
let block_size = dev.block_size();
if block_size == 0 {
return Err(FsError::IoError {
vdev: mount.dev_id,
reason: "invalid block_size (0)",
});
}
let size = dev.size().map_err(|_| FsError::IoError {
vdev: mount.dev_id,
reason: "failed to get device size",
})?;
size / block_size as u64
};
for block_id in 0..total_blocks {
let meta = match self.get_metadata(block_id) {
Some(m) => m,
None => continue, };
stats.blocks_scanned += 1;
let mut buffer = alloc::vec![0u8; 4096];
{
let mut devices = BLOCK_DEVICES.lock();
let dev = devices.get_mut(mount.dev_id).ok_or(FsError::NotFound)?;
dev.read_block(block_id as usize, &mut buffer)
.map_err(|_| FsError::IoError {
vdev: mount.dev_id,
reason: "read_block failed",
})?;
}
let computed = Checksum::calculate(&buffer);
let computed_array = [
computed.first(),
computed.second(),
computed.third(),
computed.fourth(),
];
if !crate::mgmt::security::constant_time_u64_array_eq(
&computed_array,
&meta.blkptr.checksum,
) {
stats.errors_found += 1;
crate::lcpfs_println!(
"[ SCRUB] Checksum mismatch at block {}! Repairing...",
block_id
);
if let Err(e) = Self::repair_block(&meta.blkptr, &buffer, mount.dev_id) {
crate::lcpfs_println!("[ SCRUB] Failed to repair block {}: {:?}", block_id, e);
} else {
stats.repairs_made += 1;
}
}
}
Ok(stats)
}
fn repair_block(blkptr: &Blkptr, _corrupted_data: &[u8], primary_vdev: usize) -> FsResult<()> {
for i in 1..3 {
if blkptr.dva[i].is_empty() {
continue;
}
let alt_block_id = blkptr.dva[i].offset;
let alt_vdev_id = blkptr.dva[i].vdev as usize;
let mut alt_buffer = alloc::vec![0u8; 4096];
let alt_checksum = {
let mut devices = BLOCK_DEVICES.lock();
let dev = devices.get_mut(alt_vdev_id).ok_or(FsError::NotFound)?;
if dev
.read_block(alt_block_id as usize, &mut alt_buffer)
.is_ok()
{
Checksum::calculate(&alt_buffer)
} else {
continue; }
};
let alt_array = [
alt_checksum.first(),
alt_checksum.second(),
alt_checksum.third(),
alt_checksum.fourth(),
];
if crate::mgmt::security::constant_time_u64_array_eq(&alt_array, &blkptr.checksum) {
crate::lcpfs_println!("[ SCRUB] Repaired using DVA[{}]", i);
let primary_block_id = blkptr.dva[0].offset;
let primary_vdev_id = blkptr.dva[0].vdev as usize;
let mut devices = BLOCK_DEVICES.lock();
if let Some(primary_dev) = devices.get_mut(primary_vdev_id) {
primary_dev
.write_block(primary_block_id as usize, &alt_buffer)
.map_err(|_| FsError::IoError {
vdev: primary_vdev_id,
reason: "repair write failed",
})?;
} else {
return Err(FsError::NotFound);
}
return Ok(());
}
}
Err(FsError::Corruption {
block: blkptr.dva[0].offset,
details: "unrecoverable - all DVAs corrupted",
})
}
}
pub struct DmuScrubber;
impl DmuScrubber {
pub fn scrub(mount: &LcpfsMount) -> FsResult<SolutionScrubStats> {
let mut stats = SolutionScrubStats::default();
if let Some(ref root_dnode) = mount.root_dnode {
Self::scrub_dnode(root_dnode, &mut stats, 0)?;
}
crate::lcpfs_println!(
"[ SCRUB] DMU traversal complete: {} blocks, {} errors",
stats.blocks_scanned,
stats.errors_found
);
Ok(stats)
}
fn scrub_dnode(
dnode: &DnodePhys,
stats: &mut SolutionScrubStats,
depth: usize,
) -> FsResult<()> {
if depth >= MAX_INDIRECT_DEPTH {
return Err(FsError::Corruption {
block: 0,
details: "indirect block depth limit exceeded (possible cycle)",
});
}
for blkptr in &dnode.blkptr {
if blkptr.is_hole() {
continue;
}
Self::scrub_blkptr(blkptr, stats)?;
if blkptr.is_indirect() {
let indirect_dnode = Self::read_dnode_from_blkptr(blkptr)?;
Self::scrub_dnode(&indirect_dnode, stats, depth + 1)?;
}
}
Ok(())
}
fn scrub_blkptr(blkptr: &Blkptr, stats: &mut SolutionScrubStats) -> FsResult<()> {
stats.blocks_scanned += 1;
let block_id = blkptr.dva[0].offset;
let block_size = 4096;
let mut buffer = alloc::vec![0u8; block_size];
let vdev_id = blkptr.dva[0].vdev as usize;
{
let mut devices = BLOCK_DEVICES.lock();
let dev = devices.get_mut(vdev_id).ok_or(FsError::NotFound)?;
dev.read_block(block_id as usize, &mut buffer)
.map_err(|_| FsError::IoError {
vdev: vdev_id,
reason: "scrub read failed",
})?;
}
let computed = Checksum::calculate(&buffer);
let computed_array = [
computed.first(),
computed.second(),
computed.third(),
computed.fourth(),
];
if !crate::mgmt::security::constant_time_u64_array_eq(&computed_array, &blkptr.checksum) {
stats.errors_found += 1;
crate::lcpfs_println!(
"[ SCRUB] Checksum mismatch at block {}! Repairing...",
block_id
);
Self::repair_from_blkptr(blkptr, &buffer)?;
stats.repairs_made += 1;
}
Ok(())
}
fn repair_from_blkptr(blkptr: &Blkptr, _corrupted_data: &[u8]) -> FsResult<()> {
for i in 1..3 {
if blkptr.dva[i].is_empty() {
continue;
}
let alt_block_id = blkptr.dva[i].offset;
let alt_vdev_id = blkptr.dva[i].vdev as usize;
let mut alt_buffer = alloc::vec![0u8; 4096];
let alt_checksum = {
let mut devices = BLOCK_DEVICES.lock();
let dev = devices.get_mut(alt_vdev_id).ok_or(FsError::NotFound)?;
if dev
.read_block(alt_block_id as usize, &mut alt_buffer)
.is_ok()
{
Checksum::calculate(&alt_buffer)
} else {
continue; }
};
let alt_array = [
alt_checksum.first(),
alt_checksum.second(),
alt_checksum.third(),
alt_checksum.fourth(),
];
if crate::mgmt::security::constant_time_u64_array_eq(&alt_array, &blkptr.checksum) {
crate::lcpfs_println!("[ SCRUB] Repaired using DVA[{}]", i);
let primary_block_id = blkptr.dva[0].offset;
let primary_vdev_id = blkptr.dva[0].vdev as usize;
let mut devices = BLOCK_DEVICES.lock();
if let Some(primary_dev) = devices.get_mut(primary_vdev_id) {
primary_dev
.write_block(primary_block_id as usize, &alt_buffer)
.map_err(|_| FsError::IoError {
vdev: primary_vdev_id,
reason: "repair write failed",
})?;
} else {
return Err(FsError::NotFound);
}
return Ok(());
}
}
Err(FsError::Corruption {
block: blkptr.dva[0].offset,
details: "unrecoverable - all DVAs corrupted",
})
}
fn read_dnode_from_blkptr(blkptr: &Blkptr) -> FsResult<DnodePhys> {
let block_id = blkptr.dva[0].offset;
let vdev_id = blkptr.dva[0].vdev as usize;
let mut buffer = alloc::vec![0u8; 4096];
{
let mut devices = BLOCK_DEVICES.lock();
let dev = devices.get_mut(vdev_id).ok_or(FsError::NotFound)?;
dev.read_block(block_id as usize, &mut buffer)
.map_err(|_| FsError::IoError {
vdev: vdev_id,
reason: "read indirect block failed",
})?;
}
let computed = Checksum::calculate(&buffer);
if computed.first() != blkptr.checksum[0]
|| computed.second() != blkptr.checksum[1]
|| computed.third() != blkptr.checksum[2]
|| computed.fourth() != blkptr.checksum[3]
{
return Err(FsError::Corruption {
block: block_id,
details: "indirect block checksum mismatch",
});
}
unsafe {
let dnode_ptr = buffer.as_ptr() as *const DnodePhys;
Ok(core::ptr::read(dnode_ptr))
}
}
}
pub struct BloomFilter {
bits: Vec<u8>,
num_blocks: u64,
}
impl BloomFilter {
pub fn new(num_blocks: u64) -> Self {
let num_bytes = num_blocks.div_ceil(8) as usize;
Self {
bits: alloc::vec![0u8; num_bytes],
num_blocks,
}
}
pub fn insert(&mut self, block_id: u64) {
if block_id >= self.num_blocks {
return;
}
let byte_idx = (block_id / 8) as usize;
let bit_idx = (block_id % 8) as u8;
if byte_idx < self.bits.len() {
self.bits[byte_idx] |= 1 << bit_idx;
}
}
pub fn contains(&self, block_id: u64) -> bool {
if block_id >= self.num_blocks {
return false;
}
let byte_idx = (block_id / 8) as usize;
let bit_idx = (block_id % 8) as u8;
byte_idx < self.bits.len() && (self.bits[byte_idx] & (1 << bit_idx)) != 0
}
}
pub struct HybridScrubber {
visited: BloomFilter,
}
impl HybridScrubber {
pub fn new(total_blocks: u64) -> Self {
Self {
visited: BloomFilter::new(total_blocks),
}
}
pub fn scrub(mount: &LcpfsMount) -> FsResult<SolutionScrubStats> {
let total_blocks = {
let devices = BLOCK_DEVICES.lock();
let dev = devices.get(mount.dev_id).ok_or(FsError::NotFound)?;
let block_size = dev.block_size();
if block_size == 0 {
return Err(FsError::IoError {
vdev: mount.dev_id,
reason: "invalid block_size (0)",
});
}
let size = dev.size().map_err(|_| FsError::IoError {
vdev: mount.dev_id,
reason: "failed to get device size",
})?;
size / block_size as u64
};
let mut scrubber = Self::new(total_blocks);
let mut stats = SolutionScrubStats::default();
if let Some(ref root_dnode) = mount.root_dnode {
scrubber.scrub_dnode_dedup(root_dnode, &mut stats, 0)?;
}
let orphaned = scrubber.find_orphaned_blocks(total_blocks);
crate::lcpfs_println!(
"[ SCRUB] Hybrid complete: {} blocks, {} orphaned",
stats.blocks_scanned,
orphaned
);
Ok(stats)
}
fn scrub_dnode_dedup(
&mut self,
dnode: &DnodePhys,
stats: &mut SolutionScrubStats,
depth: usize,
) -> FsResult<()> {
if depth >= MAX_INDIRECT_DEPTH {
return Err(FsError::Corruption {
block: 0,
details: "indirect block depth limit exceeded (possible cycle)",
});
}
for blkptr in &dnode.blkptr {
if blkptr.is_hole() {
continue;
}
let block_id = blkptr.dva[0].offset;
if self.visited.contains(block_id) {
continue;
}
self.visited.insert(block_id);
DmuScrubber::scrub_blkptr(blkptr, stats)?;
if blkptr.is_indirect() {
let indirect_dnode = DmuScrubber::read_dnode_from_blkptr(blkptr)?;
self.scrub_dnode_dedup(&indirect_dnode, stats, depth + 1)?;
}
}
Ok(())
}
fn find_orphaned_blocks(&self, total_blocks: u64) -> u64 {
let mut orphaned = 0u64;
for block_id in 0..total_blocks {
if !self.visited.contains(block_id) {
orphaned += 1;
}
}
orphaned
}
}
#[derive(Debug, Default)]
pub struct SolutionScrubStats {
pub blocks_scanned: u64,
pub errors_found: u64,
pub repairs_made: u64,
}
trait BlkptrExt {
fn is_hole(&self) -> bool;
fn is_indirect(&self) -> bool;
}
impl BlkptrExt for Blkptr {
fn is_hole(&self) -> bool {
self.dva[0].is_empty() && self.dva[1].is_empty() && self.dva[2].is_empty()
}
fn is_indirect(&self) -> bool {
self.fill_count > 1
}
}
use crate::fscore::structs::Dva;
trait DvaExt {
fn is_empty(&self) -> bool;
}
impl DvaExt for Dva {
fn is_empty(&self) -> bool {
self.vdev == 0 && self.offset == 0
}
}
#[cfg(test)]
mod usage_examples {
use super::*;
fn example_solution_1_reverse_index(mount: &LcpfsMount) -> FsResult<()> {
crate::lcpfs_println!("Building reverse index (may take time)...");
let index = ReverseIndex::build_from_pool(mount)?;
crate::lcpfs_println!("Scrubbing with O(1) lookup...");
let stats = index.scrub(mount)?;
crate::lcpfs_println!(
"Scanned: {}, Errors: {}",
stats.blocks_scanned,
stats.errors_found
);
Ok(())
}
fn example_solution_2_dmu_traversal(mount: &LcpfsMount) -> FsResult<()> {
crate::lcpfs_println!("DMU traversal scrub (RAM-efficient)...");
let stats = DmuScrubber::scrub(mount)?;
crate::lcpfs_println!(
"Scanned: {}, Repaired: {}",
stats.blocks_scanned,
stats.repairs_made
);
Ok(())
}
fn example_solution_3_hybrid(mount: &LcpfsMount) -> FsResult<()> {
crate::lcpfs_println!("Hybrid scrub with bloom filter...");
let stats = HybridScrubber::scrub(mount)?;
crate::lcpfs_println!("Complete! Errors found: {}", stats.errors_found);
Ok(())
}
}