use alloc::{vec, vec::Vec};
use crate::{
be,
block_read::BlockRead,
error::{Error, Location, Result},
format,
inode::Dinode,
superblock::Superblock,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Extent {
pub startoff: u64,
pub startblock: u64,
pub blockcount: u64,
pub unwritten: bool,
}
const REC_LEN: usize = 16;
const MAX_EXTENTS: usize = 1 << 22;
const MAX_BTREE_NODES: usize = 1 << 16;
pub(crate) fn decode_rec(buf: &[u8]) -> Option<Extent> {
let l0 = be::u64_at(buf, 0)?;
let l1 = be::u64_at(buf, 8)?;
Some(Extent {
unwritten: l0 >> 63 != 0,
startoff: (l0 >> 9) & ((1u64 << 54) - 1),
startblock: ((l0 & 0x1ff) << 43) | (l1 >> 21),
blockcount: l1 & ((1u64 << 21) - 1),
})
}
pub(crate) fn read_extents<R: BlockRead>(
reader: &mut R,
sb: &Superblock,
inode: &Dinode,
) -> Result<Vec<Extent>> {
match inode.format {
format::DINODE_FMT_EXTENTS => {
let fork = inode.data_fork();
let want = (inode.nextents as usize)
.min(fork.len() / REC_LEN)
.min(MAX_EXTENTS);
let mut out = Vec::with_capacity(want);
for i in 0..want {
let rec = &fork[i * REC_LEN..i * REC_LEN + REC_LEN];
out.push(decode_rec(rec).ok_or(Error::Inconsistent {
token: "bmbt_rec_short",
where_: Location::Bmbt {
ino: inode.ino,
block: 0,
},
})?);
}
Ok(out)
}
format::DINODE_FMT_BTREE => walk_btree(reader, sb, inode),
other => Err(Error::Inconsistent {
token: if other == format::DINODE_FMT_LOCAL {
"inode_unexpected_local"
} else {
"inode_unknown_format"
},
where_: Location::Inode { ino: inode.ino },
}),
}
}
fn walk_btree<R: BlockRead>(
reader: &mut R,
sb: &Superblock,
inode: &Dinode,
) -> Result<Vec<Extent>> {
let inconsistent = |token| Error::Inconsistent {
token,
where_: Location::Bmbt {
ino: inode.ino,
block: 0,
},
};
let fork = inode.data_fork();
let numrecs = be::u16_at(fork, 2).ok_or(inconsistent("bmbt_root_short"))? as usize;
let maxrecs = fork.len().saturating_sub(4) / 16;
let ptr_area = 4 + maxrecs * 8;
let numrecs = numrecs.min(maxrecs);
let mut pending: Vec<u64> = Vec::with_capacity(numrecs);
for i in 0..numrecs {
pending.push(be::u64_at(fork, ptr_area + i * 8).ok_or(inconsistent("bmbt_root_ptr"))?);
}
let mut out = Vec::new();
let mut nodes_visited = 0usize;
let mut block = vec![0u8; sb.blocksize as usize];
while let Some(fsbno) = pending.pop() {
nodes_visited += 1;
if nodes_visited > MAX_BTREE_NODES || out.len() > MAX_EXTENTS {
return Err(inconsistent("bmbt_too_large"));
}
let byte = sb.fsblock_byte_offset(fsbno);
reader.read_at(byte, &mut block).map_err(|_| Error::Io {
token: "io_bmbt",
offset: byte,
})?;
let level = be::u16_at(&block, 4).ok_or(inconsistent("bmbt_block_short"))?;
let nrecs = be::u16_at(&block, 6).ok_or(inconsistent("bmbt_block_short"))? as usize;
let hdr = btree_block_header_len(&block).ok_or(inconsistent("bmbt_bad_magic"))?;
if level == 0 {
let avail = (block.len().saturating_sub(hdr)) / REC_LEN;
let nrecs = nrecs.min(avail);
for i in 0..nrecs {
let at = hdr + i * REC_LEN;
out.push(
decode_rec(&block[at..at + REC_LEN]).ok_or(inconsistent("bmbt_rec_short"))?,
);
if out.len() > MAX_EXTENTS {
return Err(inconsistent("bmbt_too_large"));
}
}
} else {
let block_maxrecs = (block.len().saturating_sub(hdr)) / 16;
let nrecs = nrecs.min(block_maxrecs);
let ptrs_at = hdr + block_maxrecs * 8;
for i in 0..nrecs {
pending.push(
be::u64_at(&block, ptrs_at + i * 8).ok_or(inconsistent("bmbt_block_ptr"))?,
);
}
}
}
Ok(out)
}
fn btree_block_header_len(block: &[u8]) -> Option<usize> {
match be::u32_at(block, 0)? {
0x424d_4133 => Some(72), 0x424d_4150 => Some(24), _ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn encode(e: Extent) -> [u8; 16] {
let l0 = (u64::from(e.unwritten) << 63) | (e.startoff << 9) | (e.startblock >> 43);
let l1 = (e.startblock << 21) | e.blockcount;
let mut out = [0u8; 16];
out[..8].copy_from_slice(&l0.to_be_bytes());
out[8..].copy_from_slice(&l1.to_be_bytes());
out
}
#[test]
fn decode_rec_round_trips() {
for e in [
Extent {
startoff: 0,
startblock: 0,
blockcount: 1,
unwritten: false,
},
Extent {
startoff: 5,
startblock: 100,
blockcount: 3,
unwritten: false,
},
Extent {
startoff: 1 << 40,
startblock: 1 << 50,
blockcount: (1 << 21) - 1,
unwritten: true,
},
Extent {
startoff: (1 << 54) - 1,
startblock: (1 << 52) - 1,
blockcount: 1,
unwritten: false,
},
] {
assert_eq!(decode_rec(&encode(e)), Some(e), "round-trip {e:?}");
}
}
#[test]
fn decode_rec_rejects_short_buffer() {
assert_eq!(decode_rec(&[0u8; 15]), None);
}
#[test]
fn btree_header_len_by_magic() {
let mut b = [0u8; 8];
b[..4].copy_from_slice(&0x424d_4133u32.to_be_bytes());
assert_eq!(btree_block_header_len(&b), Some(72));
b[..4].copy_from_slice(&0x424d_4150u32.to_be_bytes());
assert_eq!(btree_block_header_len(&b), Some(24));
b[..4].copy_from_slice(&0xdead_beefu32.to_be_bytes());
assert_eq!(btree_block_header_len(&b), None);
}
}