use crate::Ext4;
use crate::checksum::Checksum;
use crate::error::{CorruptKind, Ext4Error};
use crate::extent::Extent;
use crate::inode::{Inode, InodeIndex};
use crate::util::{read_u16le, read_u32le, u64_from_hilo, usize_from_u32};
use alloc::vec;
use alloc::vec::Vec;
const ENTRY_SIZE_IN_BYTES: usize = 12;
struct NodeHeader {
num_entries: u16,
max_entries: u16,
depth: u16,
}
fn add_one_mul_entry_size(n: u16) -> usize {
let n_plus_one = usize::from(n).checked_add(1).unwrap();
n_plus_one.checked_mul(ENTRY_SIZE_IN_BYTES).unwrap()
}
impl NodeHeader {
fn node_size_in_bytes(&self) -> usize {
add_one_mul_entry_size(self.num_entries)
}
fn checksum_offset(&self) -> usize {
add_one_mul_entry_size(self.max_entries)
}
}
impl NodeHeader {
fn from_bytes(data: &[u8], inode: InodeIndex) -> Result<Self, Ext4Error> {
if data.len() < ENTRY_SIZE_IN_BYTES {
return Err(CorruptKind::ExtentNotEnoughData(inode).into());
}
let eh_magic = read_u16le(data, 0);
let eh_entries = read_u16le(data, 2);
let eh_max = read_u16le(data, 4);
let eh_depth = read_u16le(data, 6);
if eh_magic != 0xf30a {
return Err(CorruptKind::ExtentMagic(inode).into());
}
if eh_depth > 5 {
return Err(CorruptKind::ExtentDepth(inode).into());
}
Ok(Self {
depth: eh_depth,
num_entries: eh_entries,
max_entries: eh_max,
})
}
}
#[derive(Clone)]
struct ToVisitItem {
node: Vec<u8>,
entry: u32,
depth: u16,
}
impl ToVisitItem {
fn new(mut node: Vec<u8>, inode: InodeIndex) -> Result<Self, Ext4Error> {
let header = NodeHeader::from_bytes(&node, inode)?;
if node.len() < header.node_size_in_bytes() {
return Err(CorruptKind::ExtentNotEnoughData(inode).into());
}
node.truncate(header.node_size_in_bytes());
Ok(Self {
node,
entry: 0,
depth: header.depth,
})
}
fn entry(&self) -> Option<&[u8]> {
let start = usize_from_u32(self.entry)
.checked_mul(ENTRY_SIZE_IN_BYTES)
.unwrap();
let end = start.checked_add(ENTRY_SIZE_IN_BYTES).unwrap();
self.node.get(start..end)
}
}
pub(crate) struct Extents {
ext4: Ext4,
inode: InodeIndex,
to_visit: Vec<ToVisitItem>,
checksum_base: Checksum,
is_done: bool,
}
impl Extents {
pub(crate) fn new(ext4: Ext4, inode: &Inode) -> Result<Self, Ext4Error> {
Ok(Self {
ext4,
inode: inode.index,
to_visit: vec![ToVisitItem::new(
inode.inline_data.to_vec(),
inode.index,
)?],
checksum_base: inode.checksum_base.clone(),
is_done: false,
})
}
fn next_impl(&mut self) -> Result<Option<Extent>, Ext4Error> {
let Some(item) = self.to_visit.last_mut() else {
self.is_done = true;
return Ok(None);
};
item.entry = item.entry.checked_add(1).unwrap();
let Some(entry) = item.entry() else {
self.to_visit.pop();
return Ok(None);
};
if item.depth == 0 {
let ee_block = read_u32le(entry, 0);
let ee_len = read_u16le(entry, 4);
let ee_start_hi = read_u16le(entry, 6);
let ee_start_low = read_u32le(entry, 8);
let start_block =
u64_from_hilo(u32::from(ee_start_hi), ee_start_low);
return Ok(Some(Extent {
block_within_file: ee_block,
start_block,
num_blocks: ee_len,
}));
} else {
let ei_leaf_lo = read_u32le(entry, 4);
let ei_leaf_hi = read_u16le(entry, 8);
let child_block = u64_from_hilo(u32::from(ei_leaf_hi), ei_leaf_lo);
let mut child_header = [0; ENTRY_SIZE_IN_BYTES];
self.ext4
.read_from_block(child_block, 0, &mut child_header)?;
let child_header =
NodeHeader::from_bytes(&child_header, self.inode)?;
let checksum_offset = child_header.checksum_offset();
let checksum_size = if self.ext4.has_metadata_checksums() {
4
} else {
0
};
let child_node_size: usize =
checksum_offset.checked_add(checksum_size).unwrap();
if child_node_size > self.ext4.0.superblock.block_size {
return Err(CorruptKind::ExtentNodeSize(self.inode).into());
}
let mut child_node = vec![0; child_node_size];
self.ext4.read_from_block(child_block, 0, &mut child_node)?;
if self.ext4.has_metadata_checksums() {
let expected_checksum =
read_u32le(&child_node, checksum_offset);
let mut checksum = self.checksum_base.clone();
checksum.update(&child_node[..checksum_offset]);
let actual_checksum = checksum.finalize();
if expected_checksum != actual_checksum {
return Err(CorruptKind::ExtentChecksum(self.inode).into());
}
}
self.to_visit
.push(ToVisitItem::new(child_node, self.inode)?);
}
Ok(None)
}
}
impl_result_iter!(Extents, Extent);