use crate::error::{Error, Result};
use crate::extent::{Extent, ExtentHeader};
use crate::extent_mut::{plan_free_extent, ExtentMutation};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockWrite {
pub logical_block: u64,
pub offset_in_block: u32,
pub payload: Vec<u8>,
}
pub fn split_into_block_writes(offset: u64, data: &[u8], block_size: u32) -> Vec<BlockWrite> {
if data.is_empty() {
return Vec::new();
}
let bs = block_size as u64;
let mut out = Vec::new();
let mut cur = offset;
let end = offset + data.len() as u64;
let mut src = 0usize;
while cur < end {
let logical_block = cur / bs;
let off_in_block = (cur % bs) as u32;
let block_remaining = (bs - off_in_block as u64) as usize;
let take = block_remaining.min(end.saturating_sub(cur) as usize);
out.push(BlockWrite {
logical_block,
offset_in_block: off_in_block,
payload: data[src..src + take].to_vec(),
});
src += take;
cur += take as u64;
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteBlockRange {
pub first: u64,
pub last: u64,
pub full_blocks: bool,
}
pub fn compute_write_range(offset: u64, length: u64, block_size: u32) -> Option<WriteBlockRange> {
if length == 0 {
return None;
}
let bs = block_size as u64;
let first = offset / bs;
let last = (offset + length - 1) / bs;
let full_blocks = offset.is_multiple_of(bs) && (offset + length).is_multiple_of(bs);
Some(WriteBlockRange {
first,
last,
full_blocks,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeChange {
pub old_size: u64,
pub new_size: u64,
}
pub fn plan_truncate_grow(old_size: u64, new_size: u64) -> Result<SizeChange> {
if new_size < old_size {
return Err(Error::Corrupt("plan_truncate_grow: new_size < old_size"));
}
Ok(SizeChange { old_size, new_size })
}
pub fn plan_truncate_shrink(
old_size: u64,
new_size: u64,
root_bytes: &[u8],
block_size: u32,
) -> Result<(SizeChange, Vec<ExtentMutation>)> {
if new_size > old_size {
return Err(Error::Corrupt("plan_truncate_shrink: new_size > old_size"));
}
let header = ExtentHeader::parse(root_bytes)?;
if !header.is_leaf() {
return Err(Error::CorruptExtentTree(
"plan_truncate_shrink: multi-level tree not yet supported",
));
}
let bs = block_size as u64;
let new_logical_end = new_size.div_ceil(bs);
let mut entries: Vec<Extent> = Vec::new();
for i in 0..header.entries {
let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i as usize);
entries.push(Extent::parse(
&root_bytes[off..off + crate::extent::EXT4_EXT_NODE_SIZE],
)?);
}
let mut muts: Vec<ExtentMutation> = Vec::new();
let mut kept: Vec<Extent> = Vec::new();
for e in entries.into_iter() {
let e_start = e.logical_block as u64;
let e_end = e_start + e.length as u64;
if e_end <= new_logical_end {
kept.push(e);
} else if e_start >= new_logical_end {
muts.push(ExtentMutation::FreePhysicalRun {
start: e.physical_block,
len: e.length as u32,
});
} else {
let keep_len = (new_logical_end - e_start) as u16;
let free_len = e.length - keep_len;
let free_phys = e.physical_block + keep_len as u64;
kept.push(Extent {
logical_block: e.logical_block,
length: keep_len,
physical_block: e.physical_block,
uninitialized: e.uninitialized,
});
muts.push(ExtentMutation::FreePhysicalRun {
start: free_phys,
len: free_len as u32,
});
}
}
let mut new_root = vec![0u8; root_bytes.len()];
new_root[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
new_root[2..4].copy_from_slice(&(kept.len() as u16).to_le_bytes());
new_root[4..6].copy_from_slice(&header.max.to_le_bytes());
new_root[6..8].copy_from_slice(&header.depth.to_le_bytes());
new_root[8..12].copy_from_slice(&header.generation.to_le_bytes());
for (i, e) in kept.iter().enumerate() {
let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i);
new_root[off..off + 4].copy_from_slice(&e.logical_block.to_le_bytes());
let ee_len = if e.uninitialized {
e.length + crate::extent::EXT_INIT_MAX_LEN
} else {
e.length
};
new_root[off + 4..off + 6].copy_from_slice(&ee_len.to_le_bytes());
let phys_hi = ((e.physical_block >> 32) & 0xFFFF) as u16;
let phys_lo = (e.physical_block & 0xFFFF_FFFF) as u32;
new_root[off + 6..off + 8].copy_from_slice(&phys_hi.to_le_bytes());
new_root[off + 8..off + 12].copy_from_slice(&phys_lo.to_le_bytes());
}
muts.insert(0, ExtentMutation::WriteRoot { bytes: new_root });
let _ = plan_free_extent;
Ok((SizeChange { old_size, new_size }, muts))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_aligned_single_block() {
let data = vec![7u8; 4096];
let out = split_into_block_writes(0, &data, 4096);
assert_eq!(out.len(), 1);
assert_eq!(out[0].logical_block, 0);
assert_eq!(out[0].offset_in_block, 0);
assert_eq!(out[0].payload.len(), 4096);
}
#[test]
fn split_crosses_block_boundary() {
let data = vec![1u8; 6000]; let out = split_into_block_writes(3000, &data, 4096);
assert_eq!(out.len(), 3);
assert_eq!(out[0].logical_block, 0);
assert_eq!(out[0].offset_in_block, 3000);
assert_eq!(out[0].payload.len(), 1096);
assert_eq!(out[1].logical_block, 1);
assert_eq!(out[1].offset_in_block, 0);
assert_eq!(out[1].payload.len(), 4096);
assert_eq!(out[2].logical_block, 2);
assert_eq!(out[2].offset_in_block, 0);
assert_eq!(out[2].payload.len(), 808);
}
#[test]
fn split_empty_data_returns_empty() {
assert!(split_into_block_writes(100, &[], 4096).is_empty());
}
#[test]
fn split_sub_block_write() {
let data = vec![2u8; 100];
let out = split_into_block_writes(1000, &data, 4096);
assert_eq!(out.len(), 1);
assert_eq!(out[0].offset_in_block, 1000);
assert_eq!(out[0].payload.len(), 100);
}
#[test]
fn write_range_full_blocks() {
let r = compute_write_range(0, 8192, 4096).unwrap();
assert_eq!(r.first, 0);
assert_eq!(r.last, 1);
assert!(r.full_blocks);
}
#[test]
fn write_range_partial_head_and_tail() {
let r = compute_write_range(100, 5000, 4096).unwrap();
assert_eq!(r.first, 0);
assert_eq!(r.last, 1);
assert!(!r.full_blocks);
}
#[test]
fn write_range_zero_length_returns_none() {
assert!(compute_write_range(100, 0, 4096).is_none());
}
#[test]
fn grow_returns_delta() {
let s = plan_truncate_grow(1000, 5000).unwrap();
assert_eq!(s.old_size, 1000);
assert_eq!(s.new_size, 5000);
}
#[test]
fn grow_rejects_shrink() {
assert!(plan_truncate_grow(5000, 1000).is_err());
}
fn mk_root(extents: &[Extent]) -> Vec<u8> {
let mut buf = vec![0u8; 60];
buf[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
buf[2..4].copy_from_slice(&(extents.len() as u16).to_le_bytes());
buf[4..6].copy_from_slice(&4u16.to_le_bytes());
buf[6..8].copy_from_slice(&0u16.to_le_bytes());
for (i, e) in extents.iter().enumerate() {
let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i);
buf[off..off + 4].copy_from_slice(&e.logical_block.to_le_bytes());
let ee_len = if e.uninitialized {
e.length + crate::extent::EXT_INIT_MAX_LEN
} else {
e.length
};
buf[off + 4..off + 6].copy_from_slice(&ee_len.to_le_bytes());
let phys_hi = ((e.physical_block >> 32) & 0xFFFF) as u16;
let phys_lo = (e.physical_block & 0xFFFF_FFFF) as u32;
buf[off + 6..off + 8].copy_from_slice(&phys_hi.to_le_bytes());
buf[off + 8..off + 12].copy_from_slice(&phys_lo.to_le_bytes());
}
buf
}
fn ext(log: u32, len: u16, phys: u64) -> Extent {
Extent {
logical_block: log,
length: len,
physical_block: phys,
uninitialized: false,
}
}
#[test]
fn shrink_drops_whole_tail_extent() {
let root = mk_root(&[ext(0, 10, 1000), ext(10, 10, 2000)]);
let (sc, muts) = plan_truncate_shrink(20 * 4096, 10 * 4096, &root, 4096).unwrap();
assert_eq!(sc.new_size, 10 * 4096);
assert_eq!(muts.len(), 2);
match &muts[1] {
ExtentMutation::FreePhysicalRun { start, len } => {
assert_eq!(*start, 2000);
assert_eq!(*len, 10);
}
_ => panic!("expected FreePhysicalRun"),
}
}
#[test]
fn shrink_splits_straddling_extent() {
let root = mk_root(&[ext(0, 10, 1000)]);
let (_, muts) = plan_truncate_shrink(10 * 4096, 4 * 4096, &root, 4096).unwrap();
assert_eq!(muts.len(), 2);
match &muts[1] {
ExtentMutation::FreePhysicalRun { start, len } => {
assert_eq!(*start, 1004);
assert_eq!(*len, 6);
}
_ => panic!("expected FreePhysicalRun for tail"),
}
}
#[test]
fn shrink_to_zero_frees_all() {
let root = mk_root(&[ext(0, 5, 500), ext(5, 5, 1000)]);
let (_, muts) = plan_truncate_shrink(10 * 4096, 0, &root, 4096).unwrap();
assert_eq!(muts.len(), 3);
let freed: Vec<_> = muts
.iter()
.filter_map(|m| match m {
ExtentMutation::FreePhysicalRun { start, len } => Some((*start, *len)),
_ => None,
})
.collect();
assert_eq!(freed, vec![(500, 5), (1000, 5)]);
}
#[test]
fn shrink_rejects_grow_direction() {
let root = mk_root(&[ext(0, 10, 1000)]);
assert!(plan_truncate_shrink(4096, 8192, &root, 4096).is_err());
}
}