use std::io::{Read, Seek};
use crate::bytes::{le_u32, le_u64};
const SM_BLOCKS_PER_CHUNK: usize = 36; const SM_CHUNKS_PER_CIB: usize = 40; const SM_DEV0: usize = 48; const DEV_BLOCK_COUNT: usize = 0; const DEV_CHUNK_COUNT: usize = 8; const DEV_CIB_COUNT: usize = 16; const DEV_CAB_COUNT: usize = 20; const DEV_ADDR_OFFSET: usize = 32; const CIB_CHUNK_INFO_COUNT: usize = 36; const CIB_CHUNK_INFO: usize = 40; const CHUNK_INFO_LEN: usize = 32;
const CI_BITMAP_ADDR: usize = 24;
pub(crate) fn read_obj_block<R: Read + Seek>(
reader: &mut R,
paddr: u64,
block_size: usize,
) -> crate::Result<Vec<u8>> {
let mut buf = vec![0u8; block_size];
let offset = paddr.saturating_mul(block_size as u64);
reader.seek(std::io::SeekFrom::Start(offset))?;
reader.read_exact(&mut buf)?;
let stored = crate::object::fletcher64_stored(&buf);
let computed = crate::object::fletcher64_checksum(&buf);
if stored != computed {
return Err(crate::ApfsError::ChecksumMismatch {
block: le_u64(&buf, 8),
stored,
computed,
});
}
Ok(buf)
}
pub fn is_block_free<R: Read + Seek>(
reader: &mut R,
spaceman_paddr: u64,
block: u64,
block_size: usize,
) -> crate::Result<bool> {
let sm = read_obj_block(reader, spaceman_paddr, block_size)?;
let blocks_per_chunk = u64::from(le_u32(&sm, SM_BLOCKS_PER_CHUNK));
let chunks_per_cib = u64::from(le_u32(&sm, SM_CHUNKS_PER_CIB));
let block_count = le_u64(&sm, SM_DEV0 + DEV_BLOCK_COUNT);
let chunk_count = le_u64(&sm, SM_DEV0 + DEV_CHUNK_COUNT);
let cib_count = u64::from(le_u32(&sm, SM_DEV0 + DEV_CIB_COUNT));
let cab_count = u64::from(le_u32(&sm, SM_DEV0 + DEV_CAB_COUNT));
let addr_offset = le_u32(&sm, SM_DEV0 + DEV_ADDR_OFFSET) as usize;
let range = |field, value, cap| crate::ApfsError::FieldOutOfRange {
structure: "spaceman_phys",
field,
value,
cap,
};
if blocks_per_chunk == 0 || chunks_per_cib == 0 {
return Err(range("sm_blocks_per_chunk/sm_chunks_per_cib", 0, 1));
}
if block >= block_count {
return Err(range("block", block, block_count));
}
if cab_count != 0 {
return Err(crate::ApfsError::UnsupportedSpacemanCab { count: cab_count });
}
let chunk_index = block / blocks_per_chunk;
if chunk_index >= chunk_count {
return Err(range("chunk_index", chunk_index, chunk_count));
}
let cib_idx = chunk_index / chunks_per_cib;
let within_cib = (chunk_index % chunks_per_cib) as usize;
if cib_idx >= cib_count {
return Err(range("cib_index", cib_idx, cib_count));
}
let cib_paddr = le_u64(&sm, addr_offset + cib_idx as usize * 8);
let cib = read_obj_block(reader, cib_paddr, block_size)?;
let ci_count = le_u32(&cib, CIB_CHUNK_INFO_COUNT) as usize;
if within_cib >= ci_count {
return Err(range(
"chunk_info_index",
within_cib as u64,
ci_count as u64,
));
}
let ci_off = CIB_CHUNK_INFO + within_cib * CHUNK_INFO_LEN;
let bitmap_addr = le_u64(&cib, ci_off + CI_BITMAP_ADDR);
if bitmap_addr == 0 {
return Ok(true);
}
let mut bitmap = vec![0u8; block_size];
let offset = bitmap_addr.saturating_mul(block_size as u64);
reader.seek(std::io::SeekFrom::Start(offset))?;
reader.read_exact(&mut bitmap)?;
let bit = (block % blocks_per_chunk) as usize;
let allocated = bitmap
.get(bit / 8)
.is_some_and(|byte| byte & (1u8 << (bit % 8)) != 0);
Ok(!allocated)
}