use crate::Ext4;
use crate::block_index::{FileBlockIndex, FsBlockIndex};
use crate::error::{CorruptKind, Ext4Error};
use crate::extent::Extent;
use crate::inode::{Inode, InodeIndex};
use crate::iters::extents::Extents;
pub(super) struct ExtentsBlocks {
extents: Extents,
extent: Option<Extent>,
block_within_extent: u16,
blocks_remaining_in_hole: u32,
block_within_file: FileBlockIndex,
num_blocks_total: u32,
is_done: bool,
inode: InodeIndex,
}
impl ExtentsBlocks {
pub(super) fn new(fs: Ext4, inode: &Inode) -> Result<Self, Ext4Error> {
let num_blocks_total = inode.file_size_in_blocks();
Ok(Self {
extents: Extents::new(fs, inode)?,
extent: None,
blocks_remaining_in_hole: 0,
block_within_file: 0,
num_blocks_total,
block_within_extent: 0,
is_done: false,
inode: inode.index,
})
}
fn next_impl(&mut self) -> Result<Option<FsBlockIndex>, Ext4Error> {
if self.block_within_file >= self.num_blocks_total {
self.is_done = true;
return Ok(None);
}
if self.blocks_remaining_in_hole > 0 {
self.blocks_remaining_in_hole =
self.blocks_remaining_in_hole.checked_sub(1).unwrap();
self.block_within_file =
self.block_within_file.checked_add(1).unwrap();
return Ok(Some(0));
}
let extent = if let Some(extent) = &self.extent {
extent
} else {
match self.extents.next() {
Some(Ok(extent)) => {
if extent.block_within_file > self.block_within_file {
self.blocks_remaining_in_hole = extent
.block_within_file
.checked_sub(self.block_within_file)
.unwrap();
}
self.extent = Some(extent);
self.block_within_extent = 0;
if self.blocks_remaining_in_hole > 0 {
return Ok(None);
}
self.extent.as_ref().unwrap()
}
Some(Err(err)) => return Err(err),
None => {
let blocks_remaining = self
.num_blocks_total
.checked_sub(self.block_within_file)
.unwrap();
if blocks_remaining > 0 {
self.blocks_remaining_in_hole = blocks_remaining;
return Ok(None);
}
self.is_done = true;
return Ok(None);
}
}
};
if self.block_within_extent == extent.num_blocks {
self.extent = None;
return Ok(None);
}
let block = extent
.start_block
.checked_add(u64::from(self.block_within_extent))
.ok_or(CorruptKind::ExtentBlock(self.inode))?;
self.block_within_extent =
self.block_within_extent.checked_add(1).unwrap();
self.block_within_file = self.block_within_file.checked_add(1).unwrap();
Ok(Some(block))
}
}
impl_result_iter!(ExtentsBlocks, FsBlockIndex);
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::load_test_disk1;
use crate::{FollowSymlinks, Path};
#[test]
fn test_extents_blocks_with_hole() {
let fs = load_test_disk1();
let inode = fs
.path_to_inode(Path::new("/holes"), FollowSymlinks::All)
.unwrap();
let is_hole: Vec<_> = ExtentsBlocks::new(fs, &inode)
.unwrap()
.map(|block_index| {
let block_index = block_index.unwrap();
block_index == 0
})
.collect();
let expected_is_hole = [
true, true, false, false, true, true, false, false, true, true,
];
assert_eq!(is_hole, expected_is_hole);
}
}