use crate::block_io::BlockDevice;
use crate::error::{Error, Result};
pub const DIRECT_COUNT: u64 = 12;
#[inline]
fn ppb(block_size: u32) -> u64 {
(block_size / 4) as u64
}
#[inline]
fn ptr_at(buf: &[u8], i: usize) -> u64 {
let off = i * 4;
u32::from_le_bytes(buf[off..off + 4].try_into().unwrap()) as u64
}
pub struct IndirectCache {
cached: Option<(u64, Vec<u8>)>,
}
impl Default for IndirectCache {
fn default() -> Self {
Self::new()
}
}
impl IndirectCache {
pub fn new() -> Self {
Self { cached: None }
}
fn get(&mut self, dev: &dyn BlockDevice, block_no: u64, block_size: u32) -> Result<&[u8]> {
let needs_read = !matches!(&self.cached, Some((b, _)) if *b == block_no);
if needs_read {
let mut buf = vec![0u8; block_size as usize];
let byte_off = block_no
.checked_mul(block_size as u64)
.ok_or(Error::Corrupt("indirect: block byte offset overflow"))?;
dev.read_at(byte_off, &mut buf)?;
self.cached = Some((block_no, buf));
}
Ok(&self.cached.as_ref().unwrap().1)
}
}
pub fn map_logical_any(
i_block: &[u8; 60],
inode_flags: u32,
dev: &dyn BlockDevice,
block_size: u32,
logical_block: u64,
) -> Result<Option<u64>> {
if (inode_flags & crate::inode::InodeFlags::EXTENTS.bits()) != 0 {
crate::extent::map_logical(i_block, dev, block_size, logical_block)
} else {
let mut cache = IndirectCache::new();
lookup(i_block, dev, block_size, logical_block, &mut cache)
}
}
pub fn lookup(
i_block: &[u8; 60],
dev: &dyn BlockDevice,
block_size: u32,
logical_block: u64,
cache: &mut IndirectCache,
) -> Result<Option<u64>> {
if block_size < 1024 || !block_size.is_power_of_two() {
return Err(Error::Corrupt("indirect: block_size out of range"));
}
let ppb = ppb(block_size);
if logical_block < DIRECT_COUNT {
let phys = ptr_at(i_block, logical_block as usize);
return Ok(if phys == 0 { None } else { Some(phys) });
}
let single_base = DIRECT_COUNT;
let single_end = single_base + ppb;
if logical_block < single_end {
let single_blk = ptr_at(i_block, 12);
if single_blk == 0 {
return Ok(None);
}
let idx = (logical_block - single_base) as usize;
let buf = cache.get(dev, single_blk, block_size)?;
let phys = ptr_at(buf, idx);
return Ok(if phys == 0 { None } else { Some(phys) });
}
let double_base = single_end;
let double_end = double_base + ppb * ppb;
if logical_block < double_end {
let double_blk = ptr_at(i_block, 13);
if double_blk == 0 {
return Ok(None);
}
let off = logical_block - double_base;
let outer_idx = (off / ppb) as usize;
let inner_idx = (off % ppb) as usize;
let outer_buf = cache.get(dev, double_blk, block_size)?;
let inner_blk = ptr_at(outer_buf, outer_idx);
if inner_blk == 0 {
return Ok(None);
}
let inner_buf = cache.get(dev, inner_blk, block_size)?;
let phys = ptr_at(inner_buf, inner_idx);
return Ok(if phys == 0 { None } else { Some(phys) });
}
let triple_base = double_end;
let triple_end = triple_base + ppb * ppb * ppb;
if logical_block < triple_end {
let triple_blk = ptr_at(i_block, 14);
if triple_blk == 0 {
return Ok(None);
}
let off = logical_block - triple_base;
let l1_idx = (off / (ppb * ppb)) as usize;
let rem = off % (ppb * ppb);
let l2_idx = (rem / ppb) as usize;
let l3_idx = (rem % ppb) as usize;
let l1_buf = cache.get(dev, triple_blk, block_size)?;
let l2_blk = ptr_at(l1_buf, l1_idx);
if l2_blk == 0 {
return Ok(None);
}
let l2_buf = cache.get(dev, l2_blk, block_size)?;
let l3_blk = ptr_at(l2_buf, l2_idx);
if l3_blk == 0 {
return Ok(None);
}
let l3_buf = cache.get(dev, l3_blk, block_size)?;
let phys = ptr_at(l3_buf, l3_idx);
return Ok(if phys == 0 { None } else { Some(phys) });
}
Err(Error::Corrupt(
"indirect: logical block exceeds triple-indirect address space",
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block_io::BlockDevice;
use std::sync::Mutex;
struct MemDev {
data: Mutex<Vec<u8>>,
}
impl MemDev {
fn new(size: usize) -> Self {
Self {
data: Mutex::new(vec![0u8; size]),
}
}
fn write_block(&self, block_no: u64, block_size: u32, contents: &[u8]) {
let mut d = self.data.lock().unwrap();
let off = (block_no as usize) * (block_size as usize);
d[off..off + contents.len()].copy_from_slice(contents);
}
}
impl BlockDevice for MemDev {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let d = self.data.lock().unwrap();
let start = offset as usize;
buf.copy_from_slice(&d[start..start + buf.len()]);
Ok(())
}
fn size_bytes(&self) -> u64 {
self.data.lock().unwrap().len() as u64
}
}
fn make_iblock(ptrs: [u32; 15]) -> [u8; 60] {
let mut out = [0u8; 60];
for (i, p) in ptrs.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&p.to_le_bytes());
}
out
}
fn pack_ptrs(ptrs: &[u32], block_size: u32) -> Vec<u8> {
let mut buf = vec![0u8; block_size as usize];
for (i, p) in ptrs.iter().enumerate() {
buf[i * 4..i * 4 + 4].copy_from_slice(&p.to_le_bytes());
}
buf
}
#[test]
fn direct_pointer_lookup() {
let bs = 1024u32;
let dev = MemDev::new(1024 * 64);
let mut cache = IndirectCache::new();
let mut ptrs = [0u32; 15];
ptrs[0] = 100;
ptrs[5] = 105;
let i_block = make_iblock(ptrs);
assert_eq!(
lookup(&i_block, &dev, bs, 0, &mut cache).unwrap(),
Some(100)
);
assert_eq!(
lookup(&i_block, &dev, bs, 5, &mut cache).unwrap(),
Some(105)
);
assert_eq!(lookup(&i_block, &dev, bs, 1, &mut cache).unwrap(), None);
assert_eq!(lookup(&i_block, &dev, bs, 11, &mut cache).unwrap(), None);
}
#[test]
fn single_indirect_lookup() {
let bs = 1024u32; let dev = MemDev::new(1024 * 1024);
let mut cache = IndirectCache::new();
let mut indirect_ptrs = vec![0u32; 256];
indirect_ptrs[0] = 200;
indirect_ptrs[3] = 203;
indirect_ptrs[255] = 455;
dev.write_block(50, bs, &pack_ptrs(&indirect_ptrs, bs));
let mut ptrs = [0u32; 15];
ptrs[12] = 50;
let i_block = make_iblock(ptrs);
assert_eq!(
lookup(&i_block, &dev, bs, 12, &mut cache).unwrap(),
Some(200)
);
assert_eq!(
lookup(&i_block, &dev, bs, 15, &mut cache).unwrap(),
Some(203)
);
assert_eq!(lookup(&i_block, &dev, bs, 13, &mut cache).unwrap(), None);
assert_eq!(
lookup(&i_block, &dev, bs, 12 + 255, &mut cache).unwrap(),
Some(455)
);
}
#[test]
fn single_indirect_zero_pointer_is_hole() {
let bs = 1024u32;
let dev = MemDev::new(1024 * 64);
let mut cache = IndirectCache::new();
let i_block = make_iblock([0u32; 15]);
assert_eq!(lookup(&i_block, &dev, bs, 12, &mut cache).unwrap(), None);
assert_eq!(lookup(&i_block, &dev, bs, 200, &mut cache).unwrap(), None);
}
#[test]
fn double_indirect_lookup() {
let bs = 1024u32; let dev = MemDev::new(1024 * 4096);
let mut cache = IndirectCache::new();
let mut outer = vec![0u32; 256];
outer[0] = 71;
outer[2] = 72;
dev.write_block(70, bs, &pack_ptrs(&outer, bs));
let mut inner1 = vec![0u32; 256];
inner1[5] = 555;
dev.write_block(71, bs, &pack_ptrs(&inner1, bs));
let mut inner2 = vec![0u32; 256];
inner2[10] = 1010;
dev.write_block(72, bs, &pack_ptrs(&inner2, bs));
let mut ptrs = [0u32; 15];
ptrs[13] = 70;
let i_block = make_iblock(ptrs);
assert_eq!(
lookup(&i_block, &dev, bs, 273, &mut cache).unwrap(),
Some(555)
);
assert_eq!(
lookup(&i_block, &dev, bs, 790, &mut cache).unwrap(),
Some(1010)
);
assert_eq!(
lookup(&i_block, &dev, bs, 268 + 256, &mut cache).unwrap(),
None
);
assert_eq!(lookup(&i_block, &dev, bs, 274, &mut cache).unwrap(), None);
}
#[test]
fn triple_indirect_lookup() {
let bs = 1024u32; let dev = MemDev::new(1024 * 8192);
let mut cache = IndirectCache::new();
let mut l1 = vec![0u32; 256];
l1[0] = 81;
dev.write_block(80, bs, &pack_ptrs(&l1, bs));
let mut l2 = vec![0u32; 256];
l2[0] = 82;
dev.write_block(81, bs, &pack_ptrs(&l2, bs));
let mut l3 = vec![0u32; 256];
l3[7] = 7777;
dev.write_block(82, bs, &pack_ptrs(&l3, bs));
let mut ptrs = [0u32; 15];
ptrs[14] = 80;
let i_block = make_iblock(ptrs);
assert_eq!(
lookup(&i_block, &dev, bs, 65811, &mut cache).unwrap(),
Some(7777)
);
assert_eq!(lookup(&i_block, &dev, bs, 65812, &mut cache).unwrap(), None);
}
#[test]
fn beyond_triple_indirect_is_corrupt() {
let bs = 1024u32;
let dev = MemDev::new(1024 * 64);
let mut cache = IndirectCache::new();
let i_block = make_iblock([0u32; 15]);
let result = lookup(&i_block, &dev, bs, 1u64 << 32, &mut cache);
assert!(matches!(result, Err(Error::Corrupt(_))));
}
}