use crate::Ext4;
use crate::block_index::FsBlockIndex;
use crate::checksum::Checksum;
use crate::error::{CorruptKind, Ext4Error, IncompatibleKind};
use crate::inode::Inode;
use crate::iters::file_blocks::FileBlocks;
use crate::journal::block_header::{JournalBlockHeader, JournalBlockType};
use crate::journal::commit_block::validate_commit_block_checksum;
use crate::journal::descriptor_block::{
DescriptorBlockTagIter, validate_descriptor_block_checksum,
};
use crate::journal::revocation_block::{
read_revocation_block_table, validate_revocation_block_checksum,
};
use crate::journal::superblock::JournalSuperblock;
use crate::util::usize_from_u32;
use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use core::iter::Skip;
pub(super) type BlockMap = BTreeMap<FsBlockIndex, FsBlockIndex>;
pub(super) fn load_block_map(
fs: &Ext4,
superblock: &JournalSuperblock,
journal_inode: &Inode,
) -> Result<BlockMap, Ext4Error> {
let mut loader = BlockMapLoader::new(fs, superblock, journal_inode)?;
while let Some(block_index) = loader.journal_block_iter.next() {
loader.block_index = block_index?;
if let Err(err) = loader.process_next() {
if let Ext4Error::Corrupt(_) = err {
break;
} else {
return Err(err);
}
}
if loader.is_done {
break;
}
}
Ok(loader.block_map)
}
struct BlockMapLoader<'a> {
fs: &'a Ext4,
superblock: &'a JournalSuperblock,
block_map: BlockMap,
uncommitted_block_map: BlockMap,
revoked_blocks: Vec<FsBlockIndex>,
journal_block_iter: Skip<FileBlocks>,
block_index: FsBlockIndex,
block: Vec<u8>,
data_block: Vec<u8>,
sequence: u32,
is_done: bool,
}
impl<'a> BlockMapLoader<'a> {
fn new(
fs: &'a Ext4,
superblock: &'a JournalSuperblock,
journal_inode: &Inode,
) -> Result<Self, Ext4Error> {
let journal_block_iter = FileBlocks::new(fs.clone(), journal_inode)?;
let journal_block_iter =
journal_block_iter.skip(usize_from_u32(superblock.start_block));
Ok(Self {
fs,
superblock,
block_map: BlockMap::new(),
uncommitted_block_map: BlockMap::new(),
revoked_blocks: Vec::new(),
journal_block_iter,
block_index: 0,
block: vec![0; fs.0.superblock.block_size.to_usize()],
data_block: vec![0; fs.0.superblock.block_size.to_usize()],
sequence: superblock.sequence,
is_done: false,
})
}
fn process_next(&mut self) -> Result<(), Ext4Error> {
self.fs
.read_from_block(self.block_index, 0, &mut self.block)?;
let Some(header) = JournalBlockHeader::read_bytes(&self.block) else {
self.is_done = true;
return Ok(());
};
if header.sequence != self.sequence {
return Err(CorruptKind::JournalSequence.into());
}
if header.block_type == JournalBlockType::DESCRIPTOR {
self.process_descriptor_block()?;
} else if header.block_type == JournalBlockType::REVOCATION {
self.process_revocation_block()?;
} else if header.block_type == JournalBlockType::COMMIT {
self.process_commit_block()?;
} else {
return Err(IncompatibleKind::JournalBlockType(
header.block_type.0,
)
.into());
}
Ok(())
}
fn process_descriptor_block(&mut self) -> Result<(), Ext4Error> {
validate_descriptor_block_checksum(self.superblock, &self.block)?;
let tags = DescriptorBlockTagIter::new(
&self.block[JournalBlockHeader::SIZE..],
);
for tag in tags {
let tag = tag?;
let block_index = self
.journal_block_iter
.next()
.ok_or(CorruptKind::JournalTruncated)??;
let mut checksum = Checksum::new();
checksum.update(self.superblock.uuid.as_bytes());
checksum.update_u32_be(self.sequence);
self.fs
.read_from_block(block_index, 0, &mut self.data_block)?;
checksum.update(&self.data_block);
if checksum.finalize() != tag.checksum {
return Err(CorruptKind::JournalDescriptorTagChecksum.into());
}
self.uncommitted_block_map
.insert(tag.block_index, block_index);
}
Ok(())
}
fn process_revocation_block(&mut self) -> Result<(), Ext4Error> {
validate_revocation_block_checksum(self.superblock, &self.block)?;
read_revocation_block_table(&self.block, &mut self.revoked_blocks)
}
fn process_commit_block(&mut self) -> Result<(), Ext4Error> {
validate_commit_block_checksum(self.superblock, &self.block)?;
for block_index in &self.revoked_blocks {
self.uncommitted_block_map.remove(block_index);
}
self.revoked_blocks.clear();
self.block_map.extend(self.uncommitted_block_map.iter());
self.uncommitted_block_map.clear();
self.sequence = self
.sequence
.checked_add(1)
.ok_or(CorruptKind::JournalSequenceOverflow)?;
Ok(())
}
}