use std::io::{self, Read, Seek, SeekFrom, Write};
use super::Ext;
use super::constants::{
self, EXT4_EXTENTS_FL, IDX_DOUBLE_INDIRECT, IDX_INDIRECT, IDX_TRIPLE_INDIRECT, N_DIRECT,
S_IFMT, S_IFREG,
};
use super::extent::{
self, ExtentIdx, ExtentRun, MAX_EXTENTS_IN_INODE, MAX_INDICES_IN_INODE, MAX_LEN_PER_EXTENT,
};
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::FileHandle;
struct ExtentTreeState {
runs: Vec<ExtentRun>,
leaf_blocks: Vec<u32>,
}
pub struct Ext2FileHandle<'a> {
ext: &'a mut Ext,
dev: &'a mut dyn BlockDevice,
ino: u32,
pos: u64,
len: u64,
}
impl<'a> Ext2FileHandle<'a> {
pub(crate) fn new(
ext: &'a mut Ext,
dev: &'a mut dyn BlockDevice,
ino: u32,
len: u64,
) -> Result<Self> {
ext.ensure_inode_staged(dev, ino)?;
Ok(Self {
ext,
dev,
ino,
pos: 0,
len,
})
}
fn set_inode_size(&mut self, new_len: u64) -> Result<()> {
if new_len > u32::MAX as u64 {
return Err(crate::Error::Unsupported(
"ext2: file > 4 GiB requires LARGE_FILE — not yet implemented".into(),
));
}
self.len = new_len;
let inode = self.staged_inode_mut();
inode.size = new_len as u32;
Ok(())
}
fn is_extent(&self) -> bool {
self.staged_inode().flags & EXT4_EXTENTS_FL != 0
}
fn recompute_blocks_512(&mut self) -> Result<()> {
if self.is_extent() {
return self.recompute_blocks_512_extent();
}
let bs = self.ext.layout.block_size;
let ptrs = (bs / 4) as u64;
let inode = *self.staged_inode();
let mut data = 0u64;
let mut meta = 0u64;
for b in &inode.block[..N_DIRECT] {
if *b != 0 {
data += 1;
}
}
let ind = inode.block[IDX_INDIRECT];
if ind != 0 {
meta += 1;
let buf = self.read_indirect_block(ind)?;
for i in 0..ptrs as usize {
let p = read_u32_le(&buf, i * 4);
if p != 0 {
data += 1;
}
}
}
let dind = inode.block[IDX_DOUBLE_INDIRECT];
if dind != 0 {
meta += 1;
let outer = self.read_indirect_block(dind)?;
for i in 0..ptrs as usize {
let sub = read_u32_le(&outer, i * 4);
if sub != 0 {
meta += 1;
let inner = self.read_indirect_block(sub)?;
for j in 0..ptrs as usize {
let p = read_u32_le(&inner, j * 4);
if p != 0 {
data += 1;
}
}
}
}
}
let tind = inode.block[IDX_TRIPLE_INDIRECT];
if tind != 0 {
meta += 1;
let l1 = self.read_indirect_block(tind)?;
for i in 0..ptrs as usize {
let dind2 = read_u32_le(&l1, i * 4);
if dind2 != 0 {
meta += 1;
let l2 = self.read_indirect_block(dind2)?;
for j in 0..ptrs as usize {
let sub = read_u32_le(&l2, j * 4);
if sub != 0 {
meta += 1;
let inner = self.read_indirect_block(sub)?;
for k in 0..ptrs as usize {
let p = read_u32_le(&inner, k * 4);
if p != 0 {
data += 1;
}
}
}
}
}
}
}
let total_sectors = (data + meta) * (bs as u64 / 512);
self.staged_inode_mut().blocks_512 = total_sectors as u32;
Ok(())
}
fn read_extent_tree_state(&mut self) -> Result<ExtentTreeState> {
let inode = *self.staged_inode();
let bytes = extent::iblock_to_bytes(&inode.block);
let header = extent::decode_header(&bytes[..12])?;
match header.depth {
0 => {
let (_, runs) = extent::decode_depth0_iblock(&bytes)?;
Ok(ExtentTreeState {
runs,
leaf_blocks: Vec::new(),
})
}
1 => {
let (_, indices) = extent::decode_idx_iblock(&bytes)?;
let bs = self.ext.layout.block_size;
let mut runs = Vec::new();
let mut leaf_blocks = Vec::with_capacity(indices.len());
for idx in &indices {
let leaf_buf = self.read_indirect_block(idx.leaf as u32)?;
let (_, mut leaf_runs) = extent::decode_leaf_block(&leaf_buf[..bs as usize])?;
runs.append(&mut leaf_runs);
leaf_blocks.push(idx.leaf as u32);
}
Ok(ExtentTreeState { runs, leaf_blocks })
}
d => Err(crate::Error::Unsupported(format!(
"ext4: cannot mutate extent tree of depth {d} (writer supports depth 0 and 1 only)",
))),
}
}
fn read_extent_runs(&mut self) -> Result<Vec<ExtentRun>> {
Ok(self.read_extent_tree_state()?.runs)
}
fn write_extent_runs_with_state(
&mut self,
runs: &[ExtentRun],
old_state: &ExtentTreeState,
) -> Result<()> {
let bs = self.ext.layout.block_size;
let per_leaf = extent::entries_per_leaf_block(bs);
if runs.len() <= MAX_EXTENTS_IN_INODE {
for &b in &old_state.leaf_blocks {
self.free_leaf_block(b);
}
let packed = extent::pack_into_iblock(runs)?;
let slots = extent::bytes_to_iblock(&packed);
let inode = self.staged_inode_mut();
inode.block = slots;
return Ok(());
}
let needed_leaves = runs.len().div_ceil(per_leaf);
if needed_leaves > MAX_INDICES_IN_INODE {
return Err(crate::Error::Unsupported(format!(
"ext4: file requires {} leaf blocks (4 idx slots × {} entries each = {} max); depth ≥ 2 not implemented",
needed_leaves,
per_leaf,
MAX_INDICES_IN_INODE * per_leaf,
)));
}
for &b in &old_state.leaf_blocks {
self.free_leaf_block(b);
}
let mut indices = Vec::with_capacity(needed_leaves);
for chunk in runs.chunks(per_leaf) {
let leaf_blk = self.ext.alloc_data_block()?;
let leaf_bytes = extent::encode_leaf_block(chunk, bs)?;
self.write_indirect_block(leaf_blk, &leaf_bytes)?;
indices.push(ExtentIdx {
block: chunk[0].logical,
leaf: leaf_blk as u64,
});
}
let packed = extent::pack_idx_into_iblock(&indices)?;
let slots = extent::bytes_to_iblock(&packed);
let inode = self.staged_inode_mut();
inode.block = slots;
Ok(())
}
fn free_leaf_block(&mut self, blk: u32) {
self.ext.free_block(blk);
self.ext.data_blocks.retain(|(b, _)| *b != blk);
}
fn recompute_blocks_512_extent(&mut self) -> Result<()> {
let state = self.read_extent_tree_state()?;
let bs = self.ext.layout.block_size as u64;
let mut data = 0u64;
for r in &state.runs {
let len = if r.len > MAX_LEN_PER_EXTENT {
(r.len - MAX_LEN_PER_EXTENT) as u64
} else {
r.len as u64
};
data += len;
}
let meta = state.leaf_blocks.len() as u64;
let sectors = (data + meta) * (bs / 512);
self.staged_inode_mut().blocks_512 = sectors as u32;
Ok(())
}
fn read_logical_block_extent(&mut self, n: u32) -> Result<u32> {
let runs = self.read_extent_runs()?;
for r in &runs {
let len = if r.len > MAX_LEN_PER_EXTENT {
r.len - MAX_LEN_PER_EXTENT
} else {
r.len
};
if n >= r.logical && n < r.logical + len as u32 {
let phys = r.physical + (n - r.logical) as u64;
return Ok(phys as u32);
}
}
Ok(0)
}
fn get_or_alloc_block_extent(&mut self, n: u32) -> Result<u32> {
let state = self.read_extent_tree_state()?;
let mut runs = state.runs.clone();
for r in &runs {
let len = if r.len > MAX_LEN_PER_EXTENT {
r.len - MAX_LEN_PER_EXTENT
} else {
r.len
};
if n >= r.logical && n < r.logical + len as u32 {
let phys = r.physical + (n - r.logical) as u64;
return Ok(phys as u32);
}
}
let new_phys = self.ext.alloc_data_block()? as u64;
self.zero_block_on_disk(new_phys as u32)?;
let mut merged = false;
for r in runs.iter_mut() {
if r.len >= MAX_LEN_PER_EXTENT {
continue;
}
let tail_logical = r.logical + r.len as u32;
let tail_phys = r.physical + r.len as u64;
if tail_logical == n && tail_phys == new_phys {
r.len += 1;
merged = true;
break;
}
}
if !merged {
for r in runs.iter_mut() {
if r.len >= MAX_LEN_PER_EXTENT {
continue;
}
if n + 1 == r.logical && new_phys + 1 == r.physical {
r.logical = n;
r.physical = new_phys;
r.len += 1;
merged = true;
break;
}
}
}
if !merged {
let max_runs =
MAX_INDICES_IN_INODE * extent::entries_per_leaf_block(self.ext.layout.block_size);
if runs.len() >= max_runs {
self.ext.free_block(new_phys as u32);
return Err(crate::Error::Unsupported(format!(
"ext4: file would need more than {max_runs} extents (4 leaf blocks × {} entries); depth ≥ 2 not implemented",
extent::entries_per_leaf_block(self.ext.layout.block_size),
)));
}
runs.push(ExtentRun {
logical: n,
len: 1,
physical: new_phys,
});
runs.sort_by_key(|r| r.logical);
}
let mut i = 0;
while i + 1 < runs.len() {
let (a, b) = (runs[i], runs[i + 1]);
let a_len = if a.len > MAX_LEN_PER_EXTENT {
a.len - MAX_LEN_PER_EXTENT
} else {
a.len
};
if a.len < MAX_LEN_PER_EXTENT
&& a.logical + a_len as u32 == b.logical
&& a.physical + a_len as u64 == b.physical
&& a.len.saturating_add(b.len) <= MAX_LEN_PER_EXTENT
{
runs[i].len += b.len;
runs.remove(i + 1);
} else {
i += 1;
}
}
self.write_extent_runs_with_state(&runs, &state)?;
Ok(new_phys as u32)
}
fn free_blocks_from_extent(&mut self, from: u32) -> Result<()> {
let state = self.read_extent_tree_state()?;
let mut kept: Vec<ExtentRun> = Vec::with_capacity(state.runs.len());
for r in &state.runs {
let r = *r;
let len = if r.len > MAX_LEN_PER_EXTENT {
r.len - MAX_LEN_PER_EXTENT
} else {
r.len
};
if r.logical >= from {
for off in 0..len as u32 {
self.ext.free_block((r.physical + off as u64) as u32);
}
} else if r.logical + len as u32 > from {
let new_len = from - r.logical;
for off in new_len..len as u32 {
self.ext.free_block((r.physical + off as u64) as u32);
}
let mut shortened = r;
shortened.len = new_len as u16;
kept.push(shortened);
} else {
kept.push(r);
}
}
self.write_extent_runs_with_state(&kept, &state)?;
Ok(())
}
fn staged_inode(&self) -> &super::Inode {
self.ext
.inodes
.iter()
.find(|(i, _)| *i == self.ino)
.map(|(_, i)| i)
.expect("inode is staged at handle construction")
}
fn staged_inode_mut(&mut self) -> &mut super::Inode {
self.ext
.inodes
.iter_mut()
.find(|(i, _)| *i == self.ino)
.map(|(_, i)| i)
.expect("inode is staged at handle construction")
}
fn read_indirect_block(&mut self, blk: u32) -> Result<Vec<u8>> {
let mut buf = vec![0u8; self.ext.layout.block_size as usize];
self.ext.read_block(self.dev, blk, &mut buf)?;
Ok(buf)
}
fn write_indirect_block(&mut self, blk: u32, bytes: &[u8]) -> Result<()> {
let bs = self.ext.layout.block_size as u64;
if let Some(slot) = self.ext.data_blocks.iter_mut().find(|(b, _)| *b == blk) {
slot.1.clear();
slot.1.extend_from_slice(bytes);
} else {
self.ext.data_blocks.push((blk, bytes.to_vec()));
}
self.dev.write_at(blk as u64 * bs, bytes)?;
Ok(())
}
fn get_or_alloc_block(&mut self, n: u32) -> Result<u32> {
if self.is_extent() {
return self.get_or_alloc_block_extent(n);
}
let bs = self.ext.layout.block_size;
let ptrs = bs / 4;
if (n as usize) < N_DIRECT {
let mut blk = self.staged_inode().block[n as usize];
if blk == 0 {
blk = self.ext.alloc_data_block()?;
self.zero_block_on_disk(blk)?;
self.staged_inode_mut().block[n as usize] = blk;
}
return Ok(blk);
}
let n_off = n - N_DIRECT as u32;
if n_off < ptrs {
let mut ind = self.staged_inode().block[IDX_INDIRECT];
if ind == 0 {
ind = self.ext.alloc_data_block()?;
self.zero_block_on_disk(ind)?;
self.staged_inode_mut().block[IDX_INDIRECT] = ind;
}
let mut buf = self.read_indirect_block(ind)?;
let slot = n_off as usize * 4;
let mut blk = read_u32_le(&buf, slot);
if blk == 0 {
blk = self.ext.alloc_data_block()?;
self.zero_block_on_disk(blk)?;
write_u32_le(&mut buf, slot, blk);
self.write_indirect_block(ind, &buf)?;
}
return Ok(blk);
}
let n_off = n_off - ptrs;
if n_off < ptrs * ptrs {
let mut dind = self.staged_inode().block[IDX_DOUBLE_INDIRECT];
if dind == 0 {
dind = self.ext.alloc_data_block()?;
self.zero_block_on_disk(dind)?;
self.staged_inode_mut().block[IDX_DOUBLE_INDIRECT] = dind;
}
let mut outer = self.read_indirect_block(dind)?;
let outer_slot = (n_off / ptrs) as usize * 4;
let mut sub = read_u32_le(&outer, outer_slot);
if sub == 0 {
sub = self.ext.alloc_data_block()?;
self.zero_block_on_disk(sub)?;
write_u32_le(&mut outer, outer_slot, sub);
self.write_indirect_block(dind, &outer)?;
}
let mut inner = self.read_indirect_block(sub)?;
let inner_slot = ((n_off % ptrs) as usize) * 4;
let mut blk = read_u32_le(&inner, inner_slot);
if blk == 0 {
blk = self.ext.alloc_data_block()?;
self.zero_block_on_disk(blk)?;
write_u32_le(&mut inner, inner_slot, blk);
self.write_indirect_block(sub, &inner)?;
}
return Ok(blk);
}
let n_off = n_off - ptrs * ptrs;
if n_off < ptrs * ptrs * ptrs {
let mut tind = self.staged_inode().block[IDX_TRIPLE_INDIRECT];
if tind == 0 {
tind = self.ext.alloc_data_block()?;
self.zero_block_on_disk(tind)?;
self.staged_inode_mut().block[IDX_TRIPLE_INDIRECT] = tind;
}
let mut l1 = self.read_indirect_block(tind)?;
let l1_slot = (n_off / (ptrs * ptrs)) as usize * 4;
let mut dind = read_u32_le(&l1, l1_slot);
if dind == 0 {
dind = self.ext.alloc_data_block()?;
self.zero_block_on_disk(dind)?;
write_u32_le(&mut l1, l1_slot, dind);
self.write_indirect_block(tind, &l1)?;
}
let rem = n_off % (ptrs * ptrs);
let mut l2 = self.read_indirect_block(dind)?;
let l2_slot = (rem / ptrs) as usize * 4;
let mut sub = read_u32_le(&l2, l2_slot);
if sub == 0 {
sub = self.ext.alloc_data_block()?;
self.zero_block_on_disk(sub)?;
write_u32_le(&mut l2, l2_slot, sub);
self.write_indirect_block(dind, &l2)?;
}
let mut inner = self.read_indirect_block(sub)?;
let inner_slot = ((rem % ptrs) as usize) * 4;
let mut blk = read_u32_le(&inner, inner_slot);
if blk == 0 {
blk = self.ext.alloc_data_block()?;
self.zero_block_on_disk(blk)?;
write_u32_le(&mut inner, inner_slot, blk);
self.write_indirect_block(sub, &inner)?;
}
return Ok(blk);
}
Err(crate::Error::Unsupported(
"ext2: logical block exceeds triple-indirect range".into(),
))
}
fn read_logical_block(&mut self, n: u32) -> Result<u32> {
if self.is_extent() {
return self.read_logical_block_extent(n);
}
let bs = self.ext.layout.block_size;
let ptrs = bs / 4;
let inode = *self.staged_inode();
if (n as usize) < N_DIRECT {
return Ok(inode.block[n as usize]);
}
let n_off = n - N_DIRECT as u32;
if n_off < ptrs {
let ind = inode.block[IDX_INDIRECT];
if ind == 0 {
return Ok(0);
}
let buf = self.read_indirect_block(ind)?;
return Ok(read_u32_le(&buf, n_off as usize * 4));
}
let n_off = n_off - ptrs;
if n_off < ptrs * ptrs {
let dind = inode.block[IDX_DOUBLE_INDIRECT];
if dind == 0 {
return Ok(0);
}
let outer = self.read_indirect_block(dind)?;
let sub = read_u32_le(&outer, (n_off / ptrs) as usize * 4);
if sub == 0 {
return Ok(0);
}
let inner = self.read_indirect_block(sub)?;
return Ok(read_u32_le(&inner, (n_off % ptrs) as usize * 4));
}
let n_off = n_off - ptrs * ptrs;
if n_off < ptrs * ptrs * ptrs {
let tind = inode.block[IDX_TRIPLE_INDIRECT];
if tind == 0 {
return Ok(0);
}
let l1 = self.read_indirect_block(tind)?;
let dind = read_u32_le(&l1, (n_off / (ptrs * ptrs)) as usize * 4);
if dind == 0 {
return Ok(0);
}
let rem = n_off % (ptrs * ptrs);
let l2 = self.read_indirect_block(dind)?;
let sub = read_u32_le(&l2, (rem / ptrs) as usize * 4);
if sub == 0 {
return Ok(0);
}
let inner = self.read_indirect_block(sub)?;
return Ok(read_u32_le(&inner, (rem % ptrs) as usize * 4));
}
Ok(0)
}
fn free_blocks_from(&mut self, from: u32) -> Result<()> {
if self.is_extent() {
return self.free_blocks_from_extent(from);
}
let bs = self.ext.layout.block_size;
let ptrs = bs / 4;
let inode = *self.staged_inode();
for n in (from as usize)..N_DIRECT {
let b = inode.block[n];
if b != 0 {
self.ext.free_block(b);
self.staged_inode_mut().block[n] = 0;
}
}
let direct_end = N_DIRECT as u32;
let single_end = direct_end + ptrs;
if from < single_end {
let ind = self.staged_inode().block[IDX_INDIRECT];
if ind != 0 {
let mut buf = self.read_indirect_block(ind)?;
let first = from.saturating_sub(direct_end) as usize;
let mut any_remaining = false;
for i in 0..ptrs as usize {
let p = read_u32_le(&buf, i * 4);
if i >= first {
if p != 0 {
self.ext.free_block(p);
write_u32_le(&mut buf, i * 4, 0);
}
} else if p != 0 {
any_remaining = true;
}
}
if any_remaining {
self.write_indirect_block(ind, &buf)?;
} else {
self.ext.free_block(ind);
self.staged_inode_mut().block[IDX_INDIRECT] = 0;
}
}
}
let double_end = single_end + ptrs * ptrs;
if from < double_end {
let dind = self.staged_inode().block[IDX_DOUBLE_INDIRECT];
if dind != 0 {
let mut outer = self.read_indirect_block(dind)?;
let base = single_end;
let mut any_outer = false;
for i in 0..ptrs as usize {
let sub = read_u32_le(&outer, i * 4);
if sub == 0 {
continue;
}
let sub_start = base + (i as u32) * ptrs;
let sub_end = sub_start + ptrs;
if from >= sub_end {
any_outer = true;
continue;
}
let first = if from > sub_start {
(from - sub_start) as usize
} else {
0
};
let mut inner = self.read_indirect_block(sub)?;
let mut any_inner = false;
for j in 0..ptrs as usize {
let p = read_u32_le(&inner, j * 4);
if j >= first {
if p != 0 {
self.ext.free_block(p);
write_u32_le(&mut inner, j * 4, 0);
}
} else if p != 0 {
any_inner = true;
}
}
if any_inner {
self.write_indirect_block(sub, &inner)?;
any_outer = true;
} else {
self.ext.free_block(sub);
write_u32_le(&mut outer, i * 4, 0);
}
}
if any_outer {
self.write_indirect_block(dind, &outer)?;
} else {
self.ext.free_block(dind);
self.staged_inode_mut().block[IDX_DOUBLE_INDIRECT] = 0;
}
}
}
let triple_end = double_end + ptrs * ptrs * ptrs;
if from < triple_end {
let tind = self.staged_inode().block[IDX_TRIPLE_INDIRECT];
if tind != 0 {
let mut l1 = self.read_indirect_block(tind)?;
let base = double_end;
let mut any_l1 = false;
for i in 0..ptrs as usize {
let dind = read_u32_le(&l1, i * 4);
if dind == 0 {
continue;
}
let dind_start = base + (i as u32) * ptrs * ptrs;
let dind_end = dind_start + ptrs * ptrs;
if from >= dind_end {
any_l1 = true;
continue;
}
let mut l2 = self.read_indirect_block(dind)?;
let mut any_l2 = false;
for j in 0..ptrs as usize {
let sub = read_u32_le(&l2, j * 4);
if sub == 0 {
continue;
}
let sub_start = dind_start + (j as u32) * ptrs;
let sub_end = sub_start + ptrs;
if from >= sub_end {
any_l2 = true;
continue;
}
let first = if from > sub_start {
(from - sub_start) as usize
} else {
0
};
let mut inner = self.read_indirect_block(sub)?;
let mut any_inner = false;
for k in 0..ptrs as usize {
let p = read_u32_le(&inner, k * 4);
if k >= first {
if p != 0 {
self.ext.free_block(p);
write_u32_le(&mut inner, k * 4, 0);
}
} else if p != 0 {
any_inner = true;
}
}
if any_inner {
self.write_indirect_block(sub, &inner)?;
any_l2 = true;
} else {
self.ext.free_block(sub);
write_u32_le(&mut l2, j * 4, 0);
}
}
if any_l2 {
self.write_indirect_block(dind, &l2)?;
any_l1 = true;
} else {
self.ext.free_block(dind);
write_u32_le(&mut l1, i * 4, 0);
}
}
if any_l1 {
self.write_indirect_block(tind, &l1)?;
} else {
self.ext.free_block(tind);
self.staged_inode_mut().block[IDX_TRIPLE_INDIRECT] = 0;
}
}
}
Ok(())
}
fn zero_block_on_disk(&mut self, blk: u32) -> Result<()> {
let bs = self.ext.layout.block_size as u64;
let zeros = vec![0u8; bs as usize];
self.dev.write_at(blk as u64 * bs, &zeros)?;
Ok(())
}
fn write_at_pos(&mut self, pos: u64, data: &[u8]) -> Result<u64> {
if data.is_empty() {
return Ok(0);
}
let bs = self.ext.layout.block_size as u64;
let mut written = 0u64;
let mut cur_pos = pos;
while written < data.len() as u64 {
let n = (cur_pos / bs) as u32;
let off_in_block = (cur_pos % bs) as usize;
let space = bs as usize - off_in_block;
let to_write = (data.len() - written as usize).min(space);
let blk = self.get_or_alloc_block(n)?;
let abs = blk as u64 * bs;
if off_in_block == 0 && to_write == bs as usize {
self.dev
.write_at(abs, &data[written as usize..written as usize + to_write])?;
} else {
let mut buf = vec![0u8; bs as usize];
self.dev.read_at(abs, &mut buf)?;
buf[off_in_block..off_in_block + to_write]
.copy_from_slice(&data[written as usize..written as usize + to_write]);
self.dev.write_at(abs, &buf)?;
}
written += to_write as u64;
cur_pos += to_write as u64;
}
if cur_pos > self.len {
self.set_inode_size(cur_pos)?;
}
Ok(written)
}
fn read_at_pos(&mut self, pos: u64, out: &mut [u8]) -> Result<usize> {
if pos >= self.len {
return Ok(0);
}
let bs = self.ext.layout.block_size as u64;
let remaining_in_file = self.len - pos;
let mut read = 0usize;
let max = (out.len() as u64).min(remaining_in_file) as usize;
let mut cur_pos = pos;
while read < max {
let n = (cur_pos / bs) as u32;
let off_in_block = (cur_pos % bs) as usize;
let space = bs as usize - off_in_block;
let to_read = (max - read).min(space);
let blk = self.read_logical_block(n)?;
if blk == 0 {
out[read..read + to_read].fill(0);
} else {
let mut buf = vec![0u8; bs as usize];
self.dev.read_at(blk as u64 * bs, &mut buf)?;
out[read..read + to_read]
.copy_from_slice(&buf[off_in_block..off_in_block + to_read]);
}
read += to_read;
cur_pos += to_read as u64;
}
Ok(read)
}
fn grow_to(&mut self, new_len: u64) -> Result<()> {
let bs = self.ext.layout.block_size as u64;
let old_len = self.len;
if new_len <= old_len {
return Ok(());
}
if old_len % bs != 0 {
let last_n = (old_len / bs) as u32;
let blk = self.read_logical_block(last_n)?;
if blk != 0 {
let off = (old_len % bs) as usize;
let mut buf = vec![0u8; bs as usize];
self.dev.read_at(blk as u64 * bs, &mut buf)?;
for b in &mut buf[off..] {
*b = 0;
}
self.dev.write_at(blk as u64 * bs, &buf)?;
}
}
let last_needed = if new_len == 0 {
0
} else {
((new_len - 1) / bs) as u32
};
let first_to_alloc = old_len.div_ceil(bs) as u32;
for n in first_to_alloc..=last_needed {
let _ = self.get_or_alloc_block(n)?;
}
self.set_inode_size(new_len)?;
Ok(())
}
fn shrink_to(&mut self, new_len: u64) -> Result<()> {
let bs = self.ext.layout.block_size as u64;
if new_len >= self.len {
return Ok(());
}
let from = new_len.div_ceil(bs) as u32;
self.free_blocks_from(from)?;
self.set_inode_size(new_len)?;
Ok(())
}
}
impl<'a> Read for Ext2FileHandle<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.read_at_pos(self.pos, buf).map_err(io::Error::other)?;
self.pos += n as u64;
Ok(n)
}
}
impl<'a> Write for Ext2FileHandle<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.write_at_pos(self.pos, buf).map_err(io::Error::other)?;
self.pos += n;
Ok(n as usize)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<'a> Seek for Ext2FileHandle<'a> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let new_pos = match pos {
SeekFrom::Start(p) => p as i128,
SeekFrom::Current(d) => self.pos as i128 + d as i128,
SeekFrom::End(d) => self.len as i128 + d as i128,
};
if new_pos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek to negative offset",
));
}
self.pos = new_pos as u64;
Ok(self.pos)
}
}
impl<'a> FileHandle for Ext2FileHandle<'a> {
fn len(&self) -> u64 {
self.len
}
fn set_len(&mut self, new_len: u64) -> Result<()> {
if new_len > self.len {
self.grow_to(new_len)?;
} else if new_len < self.len {
self.shrink_to(new_len)?;
}
self.recompute_blocks_512()?;
Ok(())
}
fn sync(&mut self) -> Result<()> {
self.recompute_blocks_512()?;
self.ext.flush(self.dev)
}
}
impl<'a> Drop for Ext2FileHandle<'a> {
fn drop(&mut self) {
let _ = self.recompute_blocks_512();
}
}
pub(crate) fn open_file_rw_ext<'a>(
ext: &'a mut Ext,
dev: &'a mut dyn BlockDevice,
path: &std::path::Path,
flags: crate::fs::OpenFlags,
meta: Option<crate::fs::FileMeta>,
) -> Result<Box<dyn FileHandle + 'a>> {
let fi = ext.sb.feature_incompat;
if fi & constants::feature::INCOMPAT_JOURNAL_DEV != 0 {
return Err(crate::Error::Unsupported(
"ext: image is an external journal device — partial writes not applicable".into(),
));
}
if ext.sb.feature_compat & constants::feature::COMPAT_HAS_JOURNAL != 0 {
ensure_journal_clean(ext, dev)?;
}
let path_str = path
.to_str()
.ok_or_else(|| crate::Error::InvalidArgument(format!("ext: non-UTF-8 path {path:?}")))?;
let ino = match ext.path_to_inode(dev, path_str) {
Ok(ino) => {
let inode = ext.read_inode(dev, ino)?;
if inode.mode & S_IFMT != S_IFREG {
return Err(crate::Error::InvalidArgument(format!(
"ext: {path_str} is not a regular file"
)));
}
if inode.flags & EXT4_EXTENTS_FL != 0 {
let bytes = extent::iblock_to_bytes(&inode.block);
let header = extent::decode_header(&bytes[..12])?;
if header.depth > 1 {
return Err(crate::Error::Unsupported(format!(
"ext4: extent tree depth {} not yet supported by open_file_rw (depth-0 and depth-1 only)",
header.depth
)));
}
}
ino
}
Err(_) if flags.create => {
let meta = meta.ok_or_else(|| {
crate::Error::InvalidArgument(
"ext: open_file_rw with create=true requires meta".into(),
)
})?;
let (parent, name) = super::split_path(path)?;
let parent_str = parent.to_str().ok_or_else(|| {
crate::Error::InvalidArgument("ext: non-UTF-8 parent path".into())
})?;
let parent_ino = ext.path_to_inode(dev, parent_str)?;
let mut empty = std::io::Cursor::new(Vec::<u8>::new());
ext.add_file_to_streaming(dev, parent_ino, name.as_bytes(), &mut empty, 0, meta)?
}
Err(e) => return Err(e),
};
let inode = ext.read_inode(dev, ino)?;
let mut len = inode.size as u64;
let mut handle = Ext2FileHandle::new(ext, dev, ino, len)?;
if flags.truncate && len > 0 {
handle.shrink_to(0)?;
handle.recompute_blocks_512()?;
len = 0;
}
if flags.append {
handle.pos = len;
}
Ok(Box::new(handle))
}
#[inline]
fn read_u32_le(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
}
#[inline]
fn write_u32_le(buf: &mut [u8], off: usize, val: u32) {
buf[off..off + 4].copy_from_slice(&val.to_le_bytes());
}
const JBD2_MAGIC: u32 = 0xC03B_3998;
const JBD2_BLOCKTYPE_SB_V1: u32 = 3;
const JBD2_BLOCKTYPE_SB_V2: u32 = 4;
fn ensure_journal_clean(ext: &Ext, dev: &mut dyn BlockDevice) -> Result<()> {
if ext.sb.feature_incompat & constants::feature::INCOMPAT_RECOVER != 0 {
return Err(crate::Error::Unsupported(
"ext: journal needs recovery (INCOMPAT_RECOVER set) — refusing in-place writes".into(),
));
}
let jino = ext.sb.journal_inum;
if jino == 0 {
return Err(crate::Error::InvalidImage(
"ext: COMPAT_HAS_JOURNAL set but s_journal_inum is 0".into(),
));
}
let inode = ext.read_inode(dev, jino)?;
let blk0 = ext.file_block(dev, &inode, 0)?;
if blk0 == 0 {
return Err(crate::Error::InvalidImage(
"ext: journal inode has no block 0".into(),
));
}
let bs = ext.layout.block_size as usize;
let mut buf = vec![0u8; bs];
dev.read_at(blk0 as u64 * bs as u64, &mut buf)?;
let magic = u32::from_be_bytes(buf[0..4].try_into().unwrap());
if magic != JBD2_MAGIC {
return Err(crate::Error::InvalidImage(format!(
"ext: bad JBD2 magic {magic:#010x} on journal block 0"
)));
}
let blocktype = u32::from_be_bytes(buf[4..8].try_into().unwrap());
if blocktype != JBD2_BLOCKTYPE_SB_V1 && blocktype != JBD2_BLOCKTYPE_SB_V2 {
return Err(crate::Error::InvalidImage(format!(
"ext: journal block 0 has blocktype {blocktype} (expected v1=3 or v2=4 SB)"
)));
}
let s_start = u32::from_be_bytes(buf[28..32].try_into().unwrap());
if s_start != 0 {
return Err(crate::Error::Unsupported(format!(
"ext: journal is dirty (s_start={s_start}) — refusing in-place writes until JBD2 replay is implemented"
)));
}
Ok(())
}