374 lines
9.7 KiB
Rust
374 lines
9.7 KiB
Rust
#![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
|
||
}
|
||
}
|