use crate::Ext4;
use crate::block_index::FsBlockIndex;
use crate::checksum::Checksum;
use crate::error::{CorruptKind, Ext4Error};
use crate::file_type::FileType;
use crate::metadata::Metadata;
use crate::path::PathBuf;
use crate::util::{
read_u16le, read_u32le, u32_from_hilo, u64_from_hilo, usize_from_u32,
};
use alloc::vec;
use bitflags::bitflags;
use core::num::NonZeroU32;
pub(crate) type InodeIndex = NonZeroU32;
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct InodeFlags: u32 {
const IMMUTABLE = 0x10;
const DIRECTORY_ENCRYPTED = 0x800;
const DIRECTORY_HTREE = 0x1000;
const HUGE_FILE = 0x4_0000;
const EXTENTS = 0x8_0000;
const VERITY = 0x10_0000;
const EXTENDED_ATTRIBUTES = 0x20_0000;
const INLINE_DATA = 0x1000_0000;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct InodeMode: u16 {
const S_IXOTH = 0x0001;
const S_IWOTH = 0x0002;
const S_IROTH = 0x0004;
const S_IXGRP = 0x0008;
const S_IWGRP = 0x0010;
const S_IRGRP = 0x0020;
const S_IXUSR = 0x0040;
const S_IWUSR = 0x0080;
const S_IRUSR = 0x0100;
const S_ISVTX = 0x0200;
const S_ISGID = 0x0400;
const S_ISUID = 0x0800;
const S_IFIFO = 0x1000;
const S_IFCHR = 0x2000;
const S_IFDIR = 0x4000;
const S_IFBLK = 0x6000;
const S_IFREG = 0x8000;
const S_IFLNK = 0xA000;
const S_IFSOCK = 0xC000;
}
}
#[derive(Clone, Debug)]
pub(crate) struct Inode {
pub(crate) index: InodeIndex,
pub(crate) inline_data: [u8; Self::INLINE_DATA_LEN],
pub(crate) metadata: Metadata,
pub(crate) flags: InodeFlags,
pub(crate) checksum_base: Checksum,
file_size_in_blocks: u32,
}
impl Inode {
const INLINE_DATA_LEN: usize = 60;
const L_I_CHECKSUM_LO_OFFSET: usize = 0x74 + 0x8;
const I_CHECKSUM_HI_OFFSET: usize = 0x82;
fn from_bytes(
ext4: &Ext4,
index: InodeIndex,
data: &[u8],
) -> Result<(Self, u32), Ext4Error> {
if data.len() < 128 {
return Err(CorruptKind::InodeTruncated {
inode: index,
size: data.len(),
}
.into());
}
if ext4.has_metadata_checksums()
&& data.len() < (Self::I_CHECKSUM_HI_OFFSET + 2)
{
return Err(CorruptKind::InodeTruncated {
inode: index,
size: data.len(),
}
.into());
}
let i_mode = read_u16le(data, 0x0);
let i_uid = read_u16le(data, 0x2);
let i_size_lo = read_u32le(data, 0x4);
let i_gid = read_u16le(data, 0x18);
let i_flags = read_u32le(data, 0x20);
let i_block = data.get(0x28..0x28 + Self::INLINE_DATA_LEN).unwrap();
let i_generation = read_u32le(data, 0x64);
let i_size_high = read_u32le(data, 0x6c);
let l_i_uid_high = read_u16le(data, 0x74 + 0x4);
let l_i_gid_high = read_u16le(data, 0x74 + 0x6);
let (l_i_checksum_lo, i_checksum_hi) = if ext4.has_metadata_checksums()
{
(
read_u16le(data, Self::L_I_CHECKSUM_LO_OFFSET),
read_u16le(data, Self::I_CHECKSUM_HI_OFFSET),
)
} else {
(0, 0)
};
let size_in_bytes = u64_from_hilo(i_size_high, i_size_lo);
let uid = u32_from_hilo(l_i_uid_high, i_uid);
let gid = u32_from_hilo(l_i_gid_high, i_gid);
let checksum = u32_from_hilo(i_checksum_hi, l_i_checksum_lo);
let mode = InodeMode::from_bits_retain(i_mode);
let mut checksum_base =
Checksum::with_seed(ext4.0.superblock.checksum_seed);
checksum_base.update_u32_le(index.get());
checksum_base.update_u32_le(i_generation);
let file_size_in_blocks: u32 = size_in_bytes
.div_ceil(ext4.0.superblock.block_size.to_u64())
.try_into()
.map_err(|_| CorruptKind::TooManyBlocksInFile)?;
Ok((
Self {
index,
inline_data: i_block.try_into().unwrap(),
metadata: Metadata {
size_in_bytes,
mode,
uid,
gid,
file_type: FileType::try_from(mode).map_err(|_| {
CorruptKind::InodeFileType { inode: index, mode }
})?,
},
flags: InodeFlags::from_bits_retain(i_flags),
checksum_base,
file_size_in_blocks,
},
checksum,
))
}
pub(crate) fn read(
ext4: &Ext4,
inode: InodeIndex,
) -> Result<Self, Ext4Error> {
let (block_index, offset_within_block) =
get_inode_location(ext4, inode)?;
let mut data = vec![0; usize::from(ext4.0.superblock.inode_size)];
ext4.read_from_block(block_index, offset_within_block, &mut data)?;
let (inode, expected_checksum) = Self::from_bytes(ext4, inode, &data)?;
if ext4.has_metadata_checksums() {
let mut checksum = inode.checksum_base.clone();
checksum.update(&data[..Self::L_I_CHECKSUM_LO_OFFSET]);
checksum.update_u16_le(0);
checksum.update(
&data[Self::L_I_CHECKSUM_LO_OFFSET + 2
..Self::I_CHECKSUM_HI_OFFSET],
);
checksum.update_u16_le(0);
checksum.update(&data[Self::I_CHECKSUM_HI_OFFSET + 2..]);
let actual_checksum = checksum.finalize();
if actual_checksum != expected_checksum {
return Err(CorruptKind::InodeChecksum(inode.index).into());
}
}
Ok(inode)
}
pub(crate) fn symlink_target(
&self,
ext4: &Ext4,
) -> Result<PathBuf, Ext4Error> {
if !self.metadata.is_symlink() {
return Err(Ext4Error::NotASymlink);
}
if self.metadata.size_in_bytes == 0 {
return Err(CorruptKind::SymlinkTarget(self.index).into());
}
const MAX_INLINE_SYMLINK_LEN: u64 = 59;
if self.metadata.size_in_bytes <= MAX_INLINE_SYMLINK_LEN {
let len = usize::try_from(self.metadata.size_in_bytes).unwrap();
let target = &self.inline_data[..len];
PathBuf::try_from(target)
.map_err(|_| CorruptKind::SymlinkTarget(self.index).into())
} else {
let data = ext4.read_inode_file(self)?;
PathBuf::try_from(data)
.map_err(|_| CorruptKind::SymlinkTarget(self.index).into())
}
}
pub(crate) fn file_size_in_blocks(&self) -> u32 {
self.file_size_in_blocks
}
}
fn get_inode_location(
ext4: &Ext4,
inode: InodeIndex,
) -> Result<(FsBlockIndex, u32), Ext4Error> {
let sb = &ext4.0.superblock;
let inode_minus_1 = inode.get().checked_sub(1).unwrap();
let block_group_index = inode_minus_1 / sb.inodes_per_block_group;
let group = ext4
.0
.block_group_descriptors
.get(usize_from_u32(block_group_index))
.ok_or(CorruptKind::InodeBlockGroup {
inode,
block_group: block_group_index,
num_block_groups: ext4.0.block_group_descriptors.len(),
})?;
let index_within_group = inode_minus_1 % sb.inodes_per_block_group;
let err = || CorruptKind::InodeLocation {
inode,
block_group: block_group_index,
inodes_per_block_group: sb.inodes_per_block_group,
inode_size: sb.inode_size,
block_size: sb.block_size,
inode_table_first_block: group.inode_table_first_block,
};
let byte_offset_within_group = u64::from(index_within_group)
.checked_mul(u64::from(sb.inode_size))
.ok_or_else(err)?;
let byte_offset_of_group = sb
.block_size
.to_u64()
.checked_mul(group.inode_table_first_block)
.ok_or_else(err)?;
let start_byte = byte_offset_of_group
.checked_add(byte_offset_within_group)
.ok_or_else(err)?;
let block_index = start_byte / sb.block_size.to_nz_u64();
let offset_within_block =
u32::try_from(start_byte % sb.block_size.to_nz_u64())
.map_err(|_| err())?;
Ok((block_index, offset_within_block))
}