374 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![cfg_attr(not(test), no_std)]
extern crate alloc;
use core::ops::{Deref, DerefMut};
use alloc::{boxed::Box, sync::Arc, vec};
use bytemuck::{Pod, Zeroable};
use dir::DirectoryNode;
use file::RegularNode;
use libk::{
error::Error,
vfs::{
block::{
cache::{BlockCache, CachedBlockRef},
BlockDevice,
},
NodeRef,
},
};
use libk_util::OneTimeInit;
use static_assertions::const_assert_eq;
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 struct Ext2Fs {
cache: BlockCache,
superblock: ExtendedSuperblock,
bgdt: BlockGroupDescriptorTable,
inode_size: usize,
block_size: usize,
inodes_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| {
log::error!("Ext2 init error: {:?}", e);
e
})?;
let fs = Arc::new(fs);
let root = fs.load_node(2).await?;
fs.root.init(root.clone());
Ok(root)
}
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))
.await?;
if superblock.signature != EXT2_SIGNATURE {
log::warn!(
"Invalid ext2 signature: expected {:#x}, got {:#x}",
EXT2_SIGNATURE,
superblock.signature
);
return Err(Error::InvalidArgument);
}
let bgdt_offset = 1;
let block_size = 1024usize << superblock.block_size_log2;
let bgdt_entry_count = ((superblock.total_blocks + superblock.block_group_block_count - 1)
/ superblock.block_group_block_count) as usize;
let bgdt_block_count =
(bgdt_entry_count * size_of::<BlockGroupDescriptor>() + block_size - 1) / block_size;
log::info!(
"ext2 v{}.{}",
superblock.version_major,
superblock.version_minor
);
log::info!("Block groups: {}", bgdt_entry_count);
log::info!(
"BGDT size: {} blocks ({} bytes)",
bgdt_block_count,
bgdt_block_count * block_size
);
let mut bgdt = BlockGroupDescriptorTable::with_capacity(
bgdt_block_count * block_size,
bgdt_entry_count,
);
for i in 0..bgdt_block_count {
let disk_offset = (i as u64 + bgdt_offset) * block_size as u64;
device
.read_exact(
disk_offset,
&mut bgdt.data[i * block_size..(i + 1) * block_size],
)
.await?;
}
log::info!("Inode size: {}", superblock.inode_size());
Ok(Self {
block_size,
inode_size: superblock.inode_size(),
inodes_per_block: block_size / superblock.inode_size(),
// 128 × 8 cache
cache: BlockCache::with_capacity(device, block_size, 128),
superblock,
bgdt,
root: OneTimeInit::new(),
})
}
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),
}
}
pub async fn load_node(self: &Arc<Self>, ino: u32) -> Result<NodeRef, Error> {
let inode = self.read_inode(ino).await?;
self.create_node(inode, ino)
}
pub async fn block(&self, index: u32) -> Result<CachedBlockRef, Error> {
let address = index as u64 * self.block_size as u64;
self.cache.get(address).await
}
pub async fn read_inode(&self, ino: u32) -> Result<Inode, Error> {
if ino < 1 || ino >= self.superblock.total_inodes {
todo!()
}
let ino = ino - 1;
let ino_group = ino / self.superblock.block_group_inode_count;
let ino_in_group = ino % self.superblock.block_group_inode_count;
let ino_block = self.bgdt.descriptor(ino_group).inode_table
+ ino_in_group / self.inodes_per_block as u32;
let offset_in_block = (ino_in_group as usize % self.inodes_per_block) * self.inode_size;
assert!(offset_in_block < self.block_size);
let block = self.block(ino_block).await?;
Ok(*bytemuck::from_bytes(
&block[offset_in_block..offset_in_block + size_of::<Inode>()],
))
}
pub async fn inode_block(&self, inode: &Inode, index: u32) -> Result<CachedBlockRef, Error> {
let block_index = self.inode_block_index(inode, index).await?;
self.block(block_index).await
}
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!()
}
}
}
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
}
}