fs/ext2: indirect block reading

This commit is contained in:
Mark Poliakov 2024-07-30 22:21:46 +03:00
parent 6e07fa91db
commit b4fbc5cd4c
5 changed files with 294 additions and 232 deletions

View File

@ -0,0 +1,218 @@
use core::ops::{Deref, DerefMut};
use alloc::{boxed::Box, vec};
use bytemuck::{Pod, Zeroable};
use libk::vfs::Metadata;
use static_assertions::const_assert_eq;
use yggdrasil_abi::io::{FileMode, FileType, GroupId, UserId};
use crate::Ext2Fs;
pub const SUPERBLOCK_OFFSET: u64 = 1024;
pub const EXT2_SIGNATURE: u16 = 0xEF53;
pub const ROOT_INODE: u32 = 2;
pub const DIRECT_BLOCK_COUNT: usize = 12;
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct InodeMode(u16);
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Superblock {
pub total_inodes: u32,
pub total_blocks: u32,
pub root_reserved_blocks: u32,
pub total_unallocated_blocks: u32,
pub total_unallocated_inodes: u32,
pub superblock_number: u32,
pub block_size_log2: u32,
pub fragment_size_log2: u32,
pub block_group_block_count: u32,
pub block_group_fragment_count: u32,
pub block_group_inode_count: u32,
pub last_mount_time: u32,
pub last_written_time: u32,
pub mounts_since_fsck: u16,
pub mounts_allowed_between_fsck: u16,
pub signature: u16,
pub state: u16,
pub error_behavior: u16,
pub version_minor: u16,
pub last_fsck_time: u32,
pub fsck_interval: u32,
pub creator_os_id: u32,
pub version_major: u32,
pub root_user_id: u16,
pub root_group_id: u16,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ExtendedSuperblock {
pub base: Superblock,
pub first_non_reserved_inode: u32,
pub inode_struct_size: u16,
pub superblock_block_group_number: u16,
pub optional_features: u32,
pub required_features: u32,
pub readonly_features: u32,
pub filesystem_id: [u8; 16],
pub volume_name: [u8; 16],
pub last_mount_path: [u8; 64],
pub compression_algorithms: u32,
pub file_prealloc_block_count: u8,
pub directory_prealloc_block_count: u8,
_0: u16,
pub journal_id: [u8; 16],
pub journal_inode: u32,
pub journal_device: u32,
pub orphan_inode_list_head: u32,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct BlockGroupDescriptor {
pub block_usage_bitmap: u32,
pub inode_usage_bitmap: u32,
pub inode_table: u32,
pub unallocated_blocks: u16,
pub unallocated_inodes: u16,
pub unallocated_directories: u16,
_0: [u8; 14],
}
pub struct BlockGroupDescriptorTable {
pub(crate) data: Box<[u8]>,
pub(crate) len: usize,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Inode {
pub mode: InodeMode,
pub uid: u16,
pub size_lower: u32,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub dtime: u32,
pub gid: u16,
pub hard_links: u16,
pub sector_count: u32,
pub flags: u32,
pub os_val1: u32,
pub direct_blocks: [u32; DIRECT_BLOCK_COUNT],
pub indirect_block_l1: u32,
pub indirect_block_l2: u32,
pub indirect_block_l3: u32,
pub generation: u32,
pub facl: u32,
pub size_upper: u32,
pub frag_block_no: u32,
pub os_val2: u32,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Dirent {
pub ino: u32,
pub ent_size: u16,
pub name_length_low: u8,
pub type_indicator: u8,
}
impl BlockGroupDescriptorTable {
pub fn with_capacity(size: usize, len: usize) -> Self {
let data = vec![0; size].into_boxed_slice();
Self { data, len }
}
pub fn descriptor(&self, index: u32) -> &BlockGroupDescriptor {
let index = index as usize;
if index >= self.len {
panic!();
}
bytemuck::from_bytes(
&self.data[index * size_of::<BlockGroupDescriptor>()
..(index + 1) * size_of::<BlockGroupDescriptor>()],
)
}
}
const_assert_eq!(size_of::<BlockGroupDescriptor>(), 32);
impl ExtendedSuperblock {
pub fn inode_size(&self) -> usize {
if self.base.version_major != 0 {
self.inode_struct_size as _
} else {
128
}
}
pub fn required_features(&self) -> u32 {
if self.base.version_major != 0 {
self.required_features
} else {
todo!()
}
}
}
impl Deref for ExtendedSuperblock {
type Target = Superblock;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for ExtendedSuperblock {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl Inode {
pub fn blocks(&self, fs: &Ext2Fs) -> usize {
(self.size_lower as usize + fs.block_size - 1) / fs.block_size
}
pub fn user_id(&self) -> UserId {
unsafe { UserId::from_raw(self.uid as _) }
}
pub fn group_id(&self) -> GroupId {
unsafe { GroupId::from_raw(self.uid as _) }
}
pub fn metadata(&self) -> Metadata {
Metadata {
uid: self.user_id(),
gid: self.group_id(),
mode: self.mode.permissions(),
}
}
}
impl InodeMode {
pub fn node_type(&self) -> Option<FileType> {
match self.0 & 0xF000 {
0x4000 => Some(FileType::Directory),
0x8000 => Some(FileType::File),
_ => todo!(),
}
}
pub fn permissions(&self) -> FileMode {
unsafe { FileMode::from_raw(self.0 as u32 & 0o777) }
}
}
impl From<InodeMode> for u16 {
fn from(value: InodeMode) -> Self {
value.0
}
}

View File

@ -7,7 +7,7 @@ use libk::{
vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef},
};
use yggdrasil_abi::{
io::{DirectoryEntry, FileMode, FileType, GroupId, UserId},
io::{DirectoryEntry, FileType},
util::FixedString,
};
@ -158,11 +158,7 @@ impl CommonImpl for DirectoryNode {
}
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
Ok(Metadata {
uid: unsafe { UserId::from_raw(self.inode.uid as _) },
gid: unsafe { GroupId::from_raw(self.inode.gid as _) },
mode: unsafe { FileMode::from_raw(self.inode.mode as u32 & 0o777) },
})
Ok(self.inode.metadata())
}
}

View File

@ -6,7 +6,7 @@ use libk::{
error::Error,
vfs::{CommonImpl, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl},
};
use yggdrasil_abi::io::{FileMode, GroupId, OpenOptions, UserId};
use yggdrasil_abi::io::OpenOptions;
use crate::{Ext2Fs, Inode};
@ -55,11 +55,7 @@ impl RegularNode {
impl CommonImpl for RegularNode {
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
Ok(Metadata {
uid: unsafe { UserId::from_raw(self.inode.uid as _) },
gid: unsafe { GroupId::from_raw(self.inode.gid as _) },
mode: unsafe { FileMode::from_raw(self.inode.mode as u32 & 0o777) },
})
Ok(self.inode.metadata())
}
fn as_any(&self) -> &dyn Any {

View File

@ -2,10 +2,8 @@
extern crate alloc;
use core::ops::{Deref, DerefMut};
use alloc::{boxed::Box, sync::Arc, vec};
use bytemuck::{Pod, Zeroable};
use alloc::sync::Arc;
use bytemuck::Zeroable;
use dir::DirectoryNode;
use file::RegularNode;
use libk::{
@ -19,14 +17,15 @@ use libk::{
},
};
use libk_util::OneTimeInit;
use static_assertions::const_assert_eq;
mod data;
pub mod dir;
pub mod file;
pub const SUPERBLOCK_OFFSET: u64 = 1024;
pub const EXT2_SIGNATURE: u16 = 0xEF53;
pub const BGDT_BLOCK_NUMBER: u32 = 2;
pub use data::{
BlockGroupDescriptor, BlockGroupDescriptorTable, Dirent, ExtendedSuperblock, Inode, Superblock,
};
use yggdrasil_abi::io::FileType;
pub struct Ext2Fs {
cache: BlockCache,
@ -37,169 +36,11 @@ pub struct Ext2Fs {
inode_size: usize,
block_size: usize,
inodes_per_block: usize,
pointers_per_block: usize,
root: OneTimeInit<NodeRef>,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Superblock {
total_inodes: u32,
total_blocks: u32,
root_reserved_blocks: u32,
total_unallocated_blocks: u32,
total_unallocated_inodes: u32,
superblock_number: u32,
block_size_log2: u32,
fragment_size_log2: u32,
block_group_block_count: u32,
block_group_fragment_count: u32,
block_group_inode_count: u32,
last_mount_time: u32,
last_written_time: u32,
mounts_since_fsck: u16,
mounts_allowed_between_fsck: u16,
signature: u16,
state: u16,
error_behavior: u16,
version_minor: u16,
last_fsck_time: u32,
fsck_interval: u32,
creator_os_id: u32,
version_major: u32,
root_user_id: u16,
root_group_id: u16,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ExtendedSuperblock {
base: Superblock,
first_non_reserved_inode: u32,
inode_struct_size: u16,
superblock_block_group_number: u16,
optional_features: u32,
required_features: u32,
readonly_features: u32,
filesystem_id: [u8; 16],
volume_name: [u8; 16],
last_mount_path: [u8; 64],
compression_algorithms: u32,
file_prealloc_block_count: u8,
directory_prealloc_block_count: u8,
_0: u16,
journal_id: [u8; 16],
journal_inode: u32,
journal_device: u32,
orphan_inode_list_head: u32,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct BlockGroupDescriptor {
block_usage_bitmap: u32,
inode_usage_bitmap: u32,
inode_table: u32,
unallocated_blocks: u16,
unallocated_inodes: u16,
unallocated_directories: u16,
_0: [u8; 14],
}
struct BlockGroupDescriptorTable {
data: Box<[u8]>,
len: usize,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Inode {
mode: u16,
uid: u16,
size_lower: u32,
atime: u32,
ctime: u32,
mtime: u32,
dtime: u32,
gid: u16,
hard_links: u16,
sector_count: u32,
flags: u32,
os_val1: u32,
direct_blocks: [u32; 12],
indirect_block_l1: u32,
indirect_block_l2: u32,
indirect_block_l3: u32,
generation: u32,
facl: u32,
size_upper: u32,
frag_block_no: u32,
os_val2: u32,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Dirent {
ino: u32,
ent_size: u16,
name_length_low: u8,
type_indicator: u8,
}
pub struct BlockReader<'a> {
fs: &'a Ext2Fs,
inode: &'a Inode,
len: u32,
index: u32,
}
impl<'a> BlockReader<'a> {
pub fn new(fs: &'a Ext2Fs, inode: &'a Inode) -> Self {
let len = if inode.mode & 0xF000 == 0x4000 {
(inode.size_lower as u64 / fs.block_size as u64) as u32
} else {
todo!()
};
Self {
fs,
inode,
len,
index: 0,
}
}
pub async fn next(&mut self) -> Option<Result<CachedBlockRef, Error>> {
if self.index >= self.len {
return None;
}
let block = self.fs.inode_block(self.inode, self.index).await;
if block.is_ok() {
self.index += 1;
}
Some(block)
}
}
impl BlockGroupDescriptorTable {
pub fn with_capacity(size: usize, len: usize) -> Self {
let data = vec![0; size].into_boxed_slice();
Self { data, len }
}
pub fn descriptor(&self, index: u32) -> &BlockGroupDescriptor {
let index = index as usize;
if index >= self.len {
panic!();
}
bytemuck::from_bytes(
&self.data[index * size_of::<BlockGroupDescriptor>()
..(index + 1) * size_of::<BlockGroupDescriptor>()],
)
}
}
const_assert_eq!(size_of::<BlockGroupDescriptor>(), 32);
impl Ext2Fs {
pub async fn create(device: &'static dyn BlockDevice) -> Result<NodeRef, Error> {
let fs = Self::create_fs(device).await.map_err(|e| {
@ -207,7 +48,7 @@ impl Ext2Fs {
e
})?;
let fs = Arc::new(fs);
let root = fs.load_node(2).await?;
let root = fs.load_node(data::ROOT_INODE).await?;
fs.root.init(root.clone());
@ -217,20 +58,23 @@ impl Ext2Fs {
async fn create_fs(device: &'static dyn BlockDevice) -> Result<Self, Error> {
let mut superblock = ExtendedSuperblock::zeroed();
device
.read(SUPERBLOCK_OFFSET, bytemuck::bytes_of_mut(&mut superblock))
.read(
data::SUPERBLOCK_OFFSET,
bytemuck::bytes_of_mut(&mut superblock),
)
.await?;
if superblock.signature != EXT2_SIGNATURE {
if superblock.signature != data::EXT2_SIGNATURE {
log::warn!(
"Invalid ext2 signature: expected {:#x}, got {:#x}",
EXT2_SIGNATURE,
data::EXT2_SIGNATURE,
superblock.signature
);
return Err(Error::InvalidArgument);
}
let bgdt_offset = 1;
let block_size = 1024usize << superblock.block_size_log2;
let bgdt_block_index = (data::SUPERBLOCK_OFFSET as usize + block_size - 1) / block_size;
let bgdt_entry_count = ((superblock.total_blocks + superblock.block_group_block_count - 1)
/ superblock.block_group_block_count) as usize;
@ -254,7 +98,7 @@ impl Ext2Fs {
bgdt_entry_count,
);
for i in 0..bgdt_block_count {
let disk_offset = (i as u64 + bgdt_offset) * block_size as u64;
let disk_offset = (i + bgdt_block_index) as u64 * block_size as u64;
device
.read_exact(
disk_offset,
@ -269,9 +113,10 @@ impl Ext2Fs {
block_size,
inode_size: superblock.inode_size(),
inodes_per_block: block_size / superblock.inode_size(),
pointers_per_block: block_size / size_of::<u32>(),
// 128 × 8 cache
cache: BlockCache::with_capacity(device, block_size, 128),
cache: BlockCache::with_capacity(device, block_size, 512),
superblock,
bgdt,
@ -280,12 +125,10 @@ impl Ext2Fs {
}
fn create_node(self: &Arc<Self>, inode: Inode, ino: u32) -> Result<NodeRef, Error> {
match inode.mode & 0xF000 {
// Directory
0x4000 => Ok(DirectoryNode::new(self.clone(), inode, ino)),
// Regular file
0x8000 => Ok(RegularNode::new(self.clone(), inode, ino)),
_ => todo!("Unknown file type: {:#x}", inode.mode),
match inode.mode.node_type() {
Some(FileType::Directory) => Ok(DirectoryNode::new(self.clone(), inode, ino)),
Some(FileType::File) => Ok(RegularNode::new(self.clone(), inode, ino)),
e => todo!("Unhandled inode type: {:?} ({:#x?})", e, inode.mode),
}
}
@ -325,49 +168,58 @@ impl Ext2Fs {
self.block(block_index).await
}
async fn read_index(&self, block_index: u32, index: usize) -> Result<u32, Error> {
let block = self.block(block_index).await?;
let indirect: &[u32] = unsafe {
core::slice::from_raw_parts(&block[0] as *const _ as *const _, self.pointers_per_block)
};
return Ok(indirect[index]);
}
async fn inode_block_index(&self, inode: &Inode, index: u32) -> Result<u32, Error> {
if index < 12 {
Ok(inode.direct_blocks[index as usize])
} else {
todo!()
let mut index = index as usize;
// L0
if index < inode.direct_blocks.len() {
return Ok(inode.direct_blocks[index]);
}
}
}
impl ExtendedSuperblock {
pub fn inode_size(&self) -> usize {
if self.base.version_major != 0 {
self.inode_struct_size as _
} else {
128
// L1
index -= inode.direct_blocks.len();
if index < self.pointers_per_block {
return self.read_index(inode.indirect_block_l1, index).await;
}
}
pub fn required_features(&self) -> u32 {
if self.base.version_major != 0 {
self.required_features
} else {
todo!()
// L2
index -= self.pointers_per_block;
if index < self.pointers_per_block * self.pointers_per_block {
let l1_index = self
.read_index(inode.indirect_block_l2, index / self.pointers_per_block)
.await?;
return self
.read_index(l1_index, index % self.pointers_per_block)
.await;
}
}
}
impl Deref for ExtendedSuperblock {
type Target = Superblock;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for ExtendedSuperblock {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl Inode {
pub fn blocks(&self, fs: &Ext2Fs) -> usize {
(self.size_lower as usize + fs.block_size - 1) / fs.block_size
// L3
index -= self.pointers_per_block * self.pointers_per_block;
if index < self.pointers_per_block * self.pointers_per_block * self.pointers_per_block {
let l2_pointer_index = index / self.pointers_per_block;
let l2_index = self
.read_index(
inode.indirect_block_l3,
l2_pointer_index / self.pointers_per_block,
)
.await?;
let l1_index = self
.read_index(l2_index, l2_pointer_index % self.pointers_per_block)
.await?;
return self
.read_index(l1_index, index % self.pointers_per_block)
.await;
}
log::error!("Invalid inode block index (over L3 indirect)");
Err(Error::InvalidFile)
}
}

View File

@ -82,8 +82,8 @@ impl BlockCache {
.try_get_or_insert_with_async(address, || self.fetch_block(address))
.await?;
if evicted.is_some() {
todo!()
if let Some((address, block)) = evicted {
self.evict_block(address, block).await;
}
Ok(value)