use crate::image::Image;
use snafu::{ResultExt, Snafu, ensure};
use zerocopy::{
FromBytes, Immutable, KnownLayout,
little_endian::{U16, U32, U64},
};
#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)]
#[repr(C)]
pub struct InodeRaw {
pub mode: U16,
pub nlink: U16,
pub flags: U32,
pub size: U64,
pub size_compressed: U64,
pub atime: U64,
pub mtime: U64,
pub ctime: U64,
pub birthtime: U64,
pub mtimensec: U32,
pub atimensec: U32,
pub ctimensec: U32,
pub birthnsec: U32,
pub uid: U32,
pub gid: U32,
pub spare: [u8; 16],
pub blocks: U32,
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum LoadBlocksError {
#[snafu(display("cannot read block #{block}"))]
Read { block: u32, source: std::io::Error },
#[snafu(display("block #{block} does not exist"))]
NotExists { block: u32 },
#[snafu(display("double indirect block is not supported for inode #{inode}"))]
DoubleIndirectBlockNotSupported { inode: usize },
}
pub struct Inode {
index: usize,
raw: InodeRaw,
direct_blocks: [u32; 12],
#[allow(dead_code)] direct_sigs: [Option<[u8; 32]>; 12],
indirect_blocks: [u32; 5],
#[allow(dead_code)] indirect_sigs: [Option<[u8; 32]>; 5],
signed: bool,
}
impl Inode {
fn blocks(&self) -> u32 {
self.raw.blocks.get()
}
pub(crate) fn contiguous_blocks(&self) -> Option<(u32, u32)> {
let count = self.blocks();
if count == 0 {
return None;
}
if self.direct_blocks[1] == 0xffffffff {
return Some((self.direct_blocks[0], count));
}
if count == 1 {
return Some((self.direct_blocks[0], 1));
}
None
}
pub(super) fn from_raw32_unsigned(index: usize, src: &mut &[u8]) -> Result<Self, FromRawError> {
let (raw, rest) = InodeRaw::read_from_prefix(src).map_err(|_| FromRawError::TooSmall)?;
*src = rest;
ensure!(src.len() >= 68, from_raw_error::TooSmallSnafu);
let block_data = &src[..68];
*src = &src[68..];
let mut direct_blocks = [0u32; 12];
let mut indirect_blocks = [0u32; 5];
for (i, block) in direct_blocks.iter_mut().enumerate() {
let offset = i * 4;
*block = u32::from_le_bytes(block_data[offset..offset + 4].try_into().unwrap());
}
for (i, block) in indirect_blocks.iter_mut().enumerate() {
let offset = 48 + i * 4;
*block = u32::from_le_bytes(block_data[offset..offset + 4].try_into().unwrap());
}
Ok(Self {
index,
raw,
direct_blocks,
direct_sigs: [None; 12],
indirect_blocks,
indirect_sigs: [None; 5],
signed: false,
})
}
pub(super) fn from_raw32_signed(index: usize, src: &mut &[u8]) -> Result<Self, FromRawError> {
let (raw, rest) = InodeRaw::read_from_prefix(src).map_err(|_| FromRawError::TooSmall)?;
*src = rest;
ensure!(src.len() >= 612, from_raw_error::TooSmallSnafu);
let block_data = &src[..612];
*src = &src[612..];
let mut direct_blocks = [0u32; 12];
let mut direct_sigs: [Option<[u8; 32]>; 12] = [None; 12];
let mut indirect_blocks = [0u32; 5];
let mut indirect_sigs: [Option<[u8; 32]>; 5] = [None; 5];
let mut offset = 0;
for (sig, block) in direct_sigs.iter_mut().zip(direct_blocks.iter_mut()) {
*sig = Some(block_data[offset..offset + 32].try_into().unwrap());
*block = u32::from_le_bytes(block_data[offset + 32..offset + 36].try_into().unwrap());
offset += 36;
}
for (sig, block) in indirect_sigs.iter_mut().zip(indirect_blocks.iter_mut()) {
*sig = Some(block_data[offset..offset + 32].try_into().unwrap());
*block = u32::from_le_bytes(block_data[offset + 32..offset + 36].try_into().unwrap());
offset += 36;
}
Ok(Self {
index,
raw,
direct_blocks,
direct_sigs,
indirect_blocks,
indirect_sigs,
signed: true,
})
}
pub fn load_block_map(
&self,
image: &dyn Image,
block_size: u32,
) -> Result<Vec<u32>, LoadBlocksError> {
let block_count = self.blocks() as usize;
let mut blocks: Vec<u32> = Vec::with_capacity(block_count);
if block_count == 0 {
return Ok(blocks);
}
if self.direct_blocks[1] == 0xffffffff {
let start = self.direct_blocks[0];
for block in start..(start + self.blocks()) {
blocks.push(block);
}
return Ok(blocks);
}
for i in 0..12 {
blocks.push(self.direct_blocks[i]);
if blocks.len() == block_count {
return Ok(blocks);
}
}
let bs = block_size as u64;
let block_num = self.indirect_blocks[0];
let offset = (block_num as u64) * bs;
let mut block0 = vec![0; block_size as usize];
image
.read_exact_at(offset, &mut block0)
.context(ReadSnafu { block: block_num })?;
let mut data = block0.as_slice();
while let Some(i) = self.read_indirect(&mut data) {
blocks.push(i);
if blocks.len() == block_count {
return Ok(blocks);
}
}
let block_num = self.indirect_blocks[1];
let offset = (block_num as u64) * bs;
image
.read_exact_at(offset, &mut block0)
.context(ReadSnafu { block: block_num })?;
let mut block1 = vec![0; block_size as usize];
let mut data0 = block0.as_slice();
while let Some(i) = self.read_indirect(&mut data0) {
let offset = (i as u64) * bs;
image
.read_exact_at(offset, &mut block1)
.context(ReadSnafu { block: block_num })?;
let mut data1 = block1.as_slice();
while let Some(j) = self.read_indirect(&mut data1) {
blocks.push(j);
if blocks.len() == block_count {
return Ok(blocks);
}
}
}
DoubleIndirectBlockNotSupportedSnafu { inode: self.index }.fail()
}
fn read_indirect(&self, raw: &mut &[u8]) -> Option<u32> {
let (entry_size, value_offset) = if self.signed { (36, 32) } else { (4, 0) };
if raw.len() < entry_size {
return None;
}
let value = u32::from_le_bytes(raw[value_offset..value_offset + 4].try_into().unwrap());
*raw = &raw[entry_size..];
Some(value)
}
pub fn mode(&self) -> u16 {
self.raw.mode.get()
}
pub fn flags(&self) -> InodeFlags {
InodeFlags(self.raw.flags.get())
}
pub fn size(&self) -> u64 {
self.raw.size.get()
}
pub fn compressed_len(&self) -> u64 {
self.raw.size_compressed.get()
}
pub fn atime(&self) -> u64 {
self.raw.atime.get()
}
pub fn mtime(&self) -> u64 {
self.raw.mtime.get()
}
pub fn ctime(&self) -> u64 {
self.raw.ctime.get()
}
pub fn birthtime(&self) -> u64 {
self.raw.birthtime.get()
}
pub fn mtimensec(&self) -> u32 {
self.raw.mtimensec.get()
}
pub fn atimensec(&self) -> u32 {
self.raw.atimensec.get()
}
pub fn ctimensec(&self) -> u32 {
self.raw.ctimensec.get()
}
pub fn birthnsec(&self) -> u32 {
self.raw.birthnsec.get()
}
pub fn uid(&self) -> u32 {
self.raw.uid.get()
}
pub fn gid(&self) -> u32 {
self.raw.gid.get()
}
pub const fn raw(&self) -> &InodeRaw {
&self.raw
}
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct InodeFlags(u32);
impl InodeFlags {
pub fn is_compressed(self) -> bool {
self.0 & 0x00000001 != 0
}
pub fn value(self) -> u32 {
self.0
}
}
#[derive(Debug, Snafu)]
#[snafu(module)]
#[non_exhaustive]
pub enum FromRawError {
#[snafu(display("i/o failed"))]
IoFailed { source: std::io::Error },
#[snafu(display("data too small"))]
TooSmall,
}