use alloc::vec::Vec;
use core::mem::size_of;
use core::slice::from_raw_parts;
use bitflags::bitflags;
use deku::{DekuRead, DekuWrite};
use itertools::Itertools;
use super::Ext2;
use super::block_group::BlockGroupDescriptor;
use super::error::Ext2Error;
use super::superblock::{OperatingSystem, Superblock};
use crate::arch::{u32_to_usize, usize_to_u64};
use crate::dev::Device;
use crate::dev::address::Address;
use crate::error::Error;
use crate::fs::error::FsError;
use crate::fs::file::Type;
use crate::fs::permissions::Permissions;
use crate::fs::structures::indirection::IndirectedBlocks;
pub const INODE_SIZE: usize = 128;
pub const DIRECT_BLOCK_POINTER_COUNT: u32 = 12;
pub const BAD_BLOCKS_INODE: u32 = 1;
pub const ROOT_DIRECTORY_INODE: u32 = 2;
pub const ACL_INDEX_INODE: u32 = 3;
pub const ACL_DATA_INODE: u32 = 4;
pub const BOOT_LOADER_INODE: u32 = 5;
pub const UNDELETED_DIRECTORY_INODE: u32 = 6;
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct Inode {
pub mode: u16,
pub uid: u16,
pub size: u32,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub dtime: u32,
pub gid: u16,
pub links_count: u16,
pub blocks: u32,
pub flags: u32,
pub osd1: u32,
pub direct_block_pointers: [u32; 12],
pub singly_indirect_block_pointer: u32,
pub doubly_indirect_block_pointer: u32,
pub triply_indirect_block_pointer: u32,
pub generation: u32,
pub file_acl: u32,
pub dir_acl: u32,
pub faddr: u32,
pub osd2: [u8; 12],
}
#[cfg(test)]
impl PartialEq for Inode {
fn eq(&self, other: &Self) -> bool {
let self_direct_block_pointers = self.direct_block_pointers;
let other_direct_block_pointers = other.direct_block_pointers;
self.mode == other.mode
&& self.uid == other.uid
&& self.size == other.size
&& self.gid == other.gid
&& self.links_count == other.links_count
&& self.blocks == other.blocks
&& self.flags == other.flags
&& self.osd1 == other.osd1
&& self_direct_block_pointers == other_direct_block_pointers
&& self.singly_indirect_block_pointer == other.singly_indirect_block_pointer
&& self.doubly_indirect_block_pointer == other.doubly_indirect_block_pointer
&& self.triply_indirect_block_pointer == other.triply_indirect_block_pointer
&& self.generation == other.generation
&& self.file_acl == other.file_acl
&& self.dir_acl == other.dir_acl
&& self.faddr == other.faddr
&& self.osd2 == other.osd2
}
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct TypePermissions: u16 {
const FIFO = 0x1000;
const CHARACTER_DEVICE = 0x2000;
const DIRECTORY = 0x4000;
const BLOCK_DEVICE = 0x6000;
const REGULAR_FILE = 0x8000;
const SYMBOLIC_LINK = 0xA000;
const SOCKET = 0xC000;
const OTHER_X = 0x0001;
const OTHER_W = 0x0002;
const OTHER_R = 0x0004;
const GROUP_X = 0x0008;
const GROUP_W = 0x0010;
const GROUP_R = 0x0020;
const USER_X = 0x0040;
const USER_W = 0x0080;
const USER_R = 0x0100;
const STICKY = 0x0200;
const SET_GROUP_ID = 0x0400;
const SET_USER_ID = 0x0800;
}
}
impl From<TypePermissions> for Permissions {
fn from(value: TypePermissions) -> Self {
Self::from_bits_truncate(value.bits())
}
}
impl From<Type> for TypePermissions {
fn from(value: Type) -> Self {
match value {
Type::Regular => Self::REGULAR_FILE,
Type::Directory => Self::DIRECTORY,
Type::SymbolicLink => Self::SYMBOLIC_LINK,
Type::Fifo => Self::FIFO,
Type::CharacterDevice => Self::CHARACTER_DEVICE,
Type::BlockDevice => Self::BLOCK_DEVICE,
Type::Socket => Self::SOCKET,
}
}
}
impl From<Permissions> for TypePermissions {
fn from(value: Permissions) -> Self {
Self::from_bits_truncate(value.bits())
}
}
impl TypePermissions {
#[must_use]
pub const fn file_type(self) -> Self {
Self::from_bits_truncate((self.bits() >> 12) << 12)
}
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct Flags: u32 {
const SECURE_DELETION = 0x0000_0001;
const DELETION_KEEP_DATA_COPY = 0x0000_0002;
const FILE_COMPRESSION = 0x0000_0004;
const SYNCHRONOUS_UPDATES = 0x0000_0008;
const IMMUTABLE_FILE = 0x0000_0010;
const APPEND_ONLY = 0x0000_0020;
const DUMP_EXCLUDED = 0x0000_0040;
const LAST_ACCESSED_TIME_UPDATE_DISABLE = 0x0000_0080;
const HASHED_INDEXED_DIR = 0x0001_0000;
const AFS_DIR = 0x0002_0000;
const JOURNAL_FILE_DATA = 0x0004_0000;
const RESERVED = 0x8000_0000;
}
}
#[derive(Debug, Clone, Copy)]
pub enum Osd2 {
Hurd {
frag: u8,
fsize: u8,
mode_high: u16,
uid_high: u16,
gid_high: u16,
author: u32,
},
Linux {
frag: u8,
fsize: u8,
uid_high: u16,
gid_high: u16,
},
Masix {
frag: u8,
fsize: u8,
},
Other([u8; 12]),
}
impl Osd2 {
#[must_use]
pub const fn from_bytes(bytes: [u8; 12], os: OperatingSystem) -> Self {
match os {
OperatingSystem::Linux => Self::Linux {
frag: bytes[0],
fsize: bytes[1],
uid_high: ((bytes[4] as u16) << 8_usize) + bytes[5] as u16,
gid_high: ((bytes[6] as u16) << 8_usize) + bytes[7] as u16,
},
OperatingSystem::GnuHurd => Self::Hurd {
frag: bytes[0],
fsize: bytes[1],
mode_high: ((bytes[2] as u16) << 8_usize) + bytes[3] as u16,
uid_high: ((bytes[4] as u16) << 8_usize) + bytes[5] as u16,
gid_high: ((bytes[6] as u16) << 8_usize) + bytes[7] as u16,
author: ((bytes[8] as u32) << 24_usize)
+ ((bytes[9] as u32) << 16_usize)
+ ((bytes[10] as u32) << 8_usize)
+ bytes[11] as u32,
},
OperatingSystem::Masix => Self::Masix {
frag: bytes[0],
fsize: bytes[1],
},
OperatingSystem::FreeBSD | OperatingSystem::OtherLites | OperatingSystem::Other(_) => Self::Other(bytes),
}
}
}
impl Inode {
#[must_use]
pub const fn block_group(superblock: &Superblock, n: u32) -> u32 {
(n - 1) / superblock.base().inodes_per_group
}
#[must_use]
pub const fn group_index(superblock: &Superblock, n: u32) -> u32 {
(n - 1) % superblock.base().inodes_per_group
}
#[must_use]
pub const fn containing_block(superblock: &Superblock, n: u32) -> u32 {
n * superblock.inode_size() as u32 / superblock.block_size()
}
#[must_use]
pub const fn type_permissions(&self) -> TypePermissions {
TypePermissions::from_bits_truncate(self.mode)
}
pub const fn file_type(&self) -> Result<Type, Ext2Error> {
let types_permissions = self.type_permissions();
if types_permissions.contains(TypePermissions::SYMBOLIC_LINK) {
Ok(Type::SymbolicLink)
} else if types_permissions.contains(TypePermissions::REGULAR_FILE) {
Ok(Type::Regular)
} else if types_permissions.contains(TypePermissions::DIRECTORY) {
Ok(Type::Directory)
} else if types_permissions.contains(TypePermissions::FIFO) {
Ok(Type::Fifo)
} else if types_permissions.contains(TypePermissions::CHARACTER_DEVICE) {
Ok(Type::CharacterDevice)
} else if types_permissions.contains(TypePermissions::BLOCK_DEVICE) {
Ok(Type::BlockDevice)
} else if types_permissions.contains(TypePermissions::SOCKET) {
Ok(Type::Socket)
} else {
Err(Ext2Error::BadFileType(types_permissions.bits()))
}
}
#[must_use]
pub const fn data_size(&self) -> u64 {
if TypePermissions::contains(&self.type_permissions(), TypePermissions::DIRECTORY) {
self.size as u64
} else {
self.size as u64 + ((self.dir_acl as u64) << 32_u64)
}
}
#[must_use]
pub const fn osd2(&self, superblock: &Superblock) -> Osd2 {
let os = superblock.creator_operating_system();
Osd2::from_bytes(self.osd2, os)
}
#[must_use]
#[allow(clippy::similar_names)]
pub const fn create(
mode: TypePermissions,
uid: u16,
gid: u16,
time: u32,
flags: Flags,
osd1: u32,
osd2: [u8; 12],
) -> Self {
Self {
mode: mode.bits(),
uid,
size: 0,
atime: time,
ctime: time,
mtime: time,
dtime: 0,
gid,
links_count: 1,
blocks: 0,
flags: flags.bits(),
osd1,
direct_block_pointers: [0_u32; 12],
singly_indirect_block_pointer: 0,
doubly_indirect_block_pointer: 0,
triply_indirect_block_pointer: 0,
generation: 0,
file_acl: 0,
dir_acl: 0,
faddr: 0,
osd2,
}
}
pub fn starting_addr<Dev: Device>(fs: &Ext2<Dev>, n: u32) -> Result<Address, Error<Ext2Error>> {
let base = fs.superblock().base();
if base.inodes_count < n || n == 0 {
return Err(Error::Fs(FsError::Implementation(Ext2Error::NonExistingInode(n))));
}
let block_group = Self::block_group(fs.superblock(), n);
let block_group_descriptor = BlockGroupDescriptor::parse(fs, block_group)?;
let inode_table_starting_block = block_group_descriptor.inode_table;
let index = Self::group_index(fs.superblock(), n);
Ok(Address::from(
inode_table_starting_block * fs.superblock().block_size() + index * u32::from(fs.superblock().inode_size()),
))
}
pub fn parse<Dev: Device>(fs: &Ext2<Dev>, n: u32) -> Result<Self, Error<Ext2Error>> {
let starting_addr = Self::starting_addr(fs, n)?;
let mut device = fs.device.lock();
let inode = device.read_from_bytes::<Self>(starting_addr, INODE_SIZE)?;
let inode = match inode.file_type() {
Ok(_) => inode,
Err(err) => Err(Error::Fs(FsError::Implementation(err)))?,
};
Ok(inode)
}
pub(crate) unsafe fn write_on_device<Dev: Device>(
fs: &Ext2<Dev>,
n: u32,
inode: Self,
) -> Result<(), Error<Ext2Error>> {
let starting_addr = Self::starting_addr(fs, n)?;
fs.device.lock().write_to_bytes(starting_addr, inode).map_err(Into::into)
}
pub fn indirected_blocks<Dev: Device>(
&self,
fs: &Ext2<Dev>,
) -> deku::no_std_io::Result<IndirectedBlocks<DIRECT_BLOCK_POINTER_COUNT>> {
#[allow(clippy::cast_ptr_alignment)]
fn read_indirect_block<Dev: Device>(fs: &Ext2<Dev>, block_number: u32) -> deku::no_std_io::Result<Vec<u32>> {
let mut device = fs.device.lock();
let block_address = Address::from(u32_to_usize(block_number) * u32_to_usize(fs.superblock().block_size()));
let slice = device.slice(block_address..block_address + u64::from(fs.superblock().block_size()))?;
let byte_array = slice.as_ref();
let address_array =
unsafe { from_raw_parts::<u32>(byte_array.as_ptr().cast::<u32>(), byte_array.len() / size_of::<u32>()) };
Ok(address_array.iter().filter(|&block_number| *block_number != 0).copied().collect_vec())
}
let data_blocks =
u32::try_from(1 + (self.data_size().saturating_sub(1)) / u64::from(fs.superblock().block_size()))
.expect("Ill-formed superblock: there should be at most u32::MAX blocks");
let mut parsed_data_blocks = 0_u32;
let blocks_per_indirection = fs.superblock().block_size() / 4;
let direct_block_pointers = self
.direct_block_pointers
.into_iter()
.filter(|block_number| *block_number != 0)
.collect_vec();
parsed_data_blocks += DIRECT_BLOCK_POINTER_COUNT.min(data_blocks);
let mut indirected_blocks = IndirectedBlocks::<DIRECT_BLOCK_POINTER_COUNT>::new(
blocks_per_indirection,
direct_block_pointers,
(self.singly_indirect_block_pointer, Vec::new()),
(self.doubly_indirect_block_pointer, Vec::new()),
(self.triply_indirect_block_pointer, Vec::new()),
);
if indirected_blocks.singly_indirected_blocks.0 == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
indirected_blocks
.singly_indirected_blocks
.1
.append(&mut read_indirect_block(fs, indirected_blocks.singly_indirected_blocks.0)?);
parsed_data_blocks += blocks_per_indirection;
if indirected_blocks.doubly_indirected_blocks.0 == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
let singly_indirect_block_pointers = read_indirect_block(fs, indirected_blocks.doubly_indirected_blocks.0)?;
for block_pointer in singly_indirect_block_pointers {
if block_pointer == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
indirected_blocks
.doubly_indirected_blocks
.1
.push((block_pointer, read_indirect_block(fs, block_pointer)?));
parsed_data_blocks += blocks_per_indirection;
}
if indirected_blocks.triply_indirected_blocks.0 == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
let triply_indirected_blocks = read_indirect_block(fs, indirected_blocks.triply_indirected_blocks.0)?;
for block_pointer_pointer in triply_indirected_blocks {
if block_pointer_pointer == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
let mut dib = Vec::new();
let singly_indirect_block_pointers = read_indirect_block(fs, block_pointer_pointer)?;
parsed_data_blocks += blocks_per_indirection;
for block_pointer in singly_indirect_block_pointers {
if block_pointer == 0 || parsed_data_blocks >= data_blocks {
indirected_blocks.truncate_back_data_blocks(data_blocks);
return Ok(indirected_blocks);
}
dib.push((block_pointer, read_indirect_block(fs, block_pointer)?));
}
indirected_blocks.triply_indirected_blocks.1.push((block_pointer_pointer, dib));
}
indirected_blocks.truncate_back_data_blocks(data_blocks);
Ok(indirected_blocks)
}
pub fn read_data<Dev: Device>(
&self,
fs: &Ext2<Dev>,
buffer: &mut [u8],
mut offset: u64,
) -> Result<usize, Error<Ext2Error>> {
let block_size = u32_to_usize(fs.superblock().block_size());
let indirected_blocks = self.indirected_blocks(fs)?;
let blocks = indirected_blocks.flatten_data_blocks();
let mut device = fs.device.lock();
let buffer_length = buffer.len();
let mut read_bytes = 0_usize;
for block_number in blocks {
if usize_to_u64(read_bytes) == self.data_size() || read_bytes == buffer_length {
break;
}
if offset == 0 {
let count = block_size.min(buffer_length - read_bytes);
let block_addr = Address::from(u64::from(block_number) * usize_to_u64(block_size));
let slice = device.slice(block_addr..block_addr + usize_to_u64(count))?;
let block_buffer = unsafe { buffer.get_mut(read_bytes..read_bytes + count).unwrap_unchecked() };
block_buffer.clone_from_slice(slice.as_ref());
read_bytes += count;
} else if offset >= u64::from(fs.superblock().block_size()) {
offset -= u64::from(fs.superblock().block_size());
} else {
let offset_usize = unsafe { usize::try_from(offset).unwrap_unchecked() };
let count = (block_size - offset_usize).min(buffer_length - read_bytes);
let block_addr = Address::from(u32_to_usize(block_number) * block_size);
let slice = device
.slice(block_addr + usize_to_u64(offset_usize)..block_addr + usize_to_u64(offset_usize + count))?;
let block_buffer = unsafe { buffer.get_mut(read_bytes..read_bytes + count).unwrap_unchecked() };
block_buffer.clone_from_slice(slice.as_ref());
read_bytes += count;
offset = 0;
}
}
Ok(read_bytes)
}
#[must_use]
#[allow(clippy::indexing_slicing)]
pub const fn is_free(inode_number: u32, superblock: &Superblock, bitmap: &[u8]) -> bool {
let index = Self::group_index(superblock, inode_number);
let offset = (inode_number - 1) % 8;
bitmap[(index / 8) as usize] >> offset & 1 == 0
}
#[must_use]
pub const fn is_used(inode_number: u32, superblock: &Superblock, bitmap: &[u8]) -> bool {
!Self::is_free(inode_number, superblock, bitmap)
}
}
#[cfg(test)]
mod test {
use core::mem::size_of;
use std::fs::File;
use crate::dev::Device;
use crate::fs::ext2::Ext2;
use crate::fs::ext2::inode::{INODE_SIZE, Inode, ROOT_DIRECTORY_INODE};
use crate::tests::new_device_id;
#[test]
fn struct_size() {
assert_eq!(size_of::<Inode>(), 128);
}
fn parse_root_base(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(Inode::parse(&fs, ROOT_DIRECTORY_INODE).is_ok());
}
fn parse_root_extended(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(Inode::parse(&fs, ROOT_DIRECTORY_INODE).is_ok());
}
fn failed_parse_base(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(Inode::parse(&fs, 0).is_err());
}
fn failed_parse_extended(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(Inode::parse(&fs, 0).is_err());
}
fn starting_addr(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
let root_auto = Inode::parse(&fs, ROOT_DIRECTORY_INODE).unwrap();
let starting_addr = Inode::starting_addr(&fs, ROOT_DIRECTORY_INODE).unwrap();
let root_manual =
<File as Device>::read_from_bytes::<Inode>(&mut fs.device.lock(), starting_addr, INODE_SIZE).unwrap();
assert_eq!(root_auto, root_manual);
}
mod generated {
use crate::tests::generate_fs_test;
generate_fs_test!(parse_root_base, "./tests/fs/ext2/base.ext2");
generate_fs_test!(parse_root_extended, "./tests/fs/ext2/extended.ext2");
generate_fs_test!(failed_parse_base, "./tests/fs/ext2/base.ext2");
generate_fs_test!(failed_parse_extended, "./tests/fs/ext2/extended.ext2");
generate_fs_test!(starting_addr, "./tests/fs/ext2/base.ext2");
}
}