From a35bd93f76979a7ebefcb03176e2dac3ef747116 Mon Sep 17 00:00:00 2001 From: pigeonmoelleux <pigeonmoelleux@crans.org> Date: Fri, 26 May 2023 02:46:46 +0200 Subject: [PATCH] feat(fs): functionnal ext2 directory interface --- fs_tests/bar/boo | 1 + fs_tests/bar/far.txt | 1 + fs_tests/foo.txt | 3 ++ src/fs/ext2/entry.rs | 121 ++++++++++++++++++++++++++++++------------- src/fs/ext2/inode.rs | 40 +++++++++++++- src/fs/ext2/mod.rs | 6 ++- src/fs/mod.rs | 30 +++++++++++ src/main.rs | 1 + 8 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 fs_tests/bar/boo create mode 100644 fs_tests/bar/far.txt create mode 100644 fs_tests/foo.txt diff --git a/fs_tests/bar/boo b/fs_tests/bar/boo new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/fs_tests/bar/boo @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/fs_tests/bar/far.txt b/fs_tests/bar/far.txt new file mode 100644 index 0000000..bd8fcb8 --- /dev/null +++ b/fs_tests/bar/far.txt @@ -0,0 +1 @@ +This is a second child of bar \ No newline at end of file diff --git a/fs_tests/foo.txt b/fs_tests/foo.txt new file mode 100644 index 0000000..135539b --- /dev/null +++ b/fs_tests/foo.txt @@ -0,0 +1,3 @@ +This is a file +and this is a second line +!!! \ No newline at end of file diff --git a/src/fs/ext2/entry.rs b/src/fs/ext2/entry.rs index 39b1ab8..8c8d616 100644 --- a/src/fs/ext2/entry.rs +++ b/src/fs/ext2/entry.rs @@ -9,11 +9,13 @@ use alloc::vec::Vec; use core::fmt::Debug; use core::mem; -use anyhow::{anyhow, Ok, Result}; +use anyhow::{Ok, Result}; +use log::warn; use spin::Mutex; -use super::inode::{Inode, TypePermissions, ROOT_INODE_INDEX}; +use super::inode::{Inode, TypePermissions}; use super::SUPERBLOCK; +use crate::fs::ext2::inode::INODE_CACHE; use crate::fs::ext2::{read, read_raw}; use crate::fs::node::{self, Interface, Kind, Node, Ref}; use crate::kernel::device::block::Manipulation; @@ -97,23 +99,15 @@ impl Interface for Entry { } impl Entry { - /// Returns the directory entry pointed by the inode with a given `offset` in bytes - fn new(inode: &Inode, offset: usize, _parent: usize) -> Result<Self> { - let types_and_permissions = TypePermissions::from_bits_truncate(inode.type_and_permissions); - if !types_and_permissions.intersects(TypePermissions::DIRECTORY) { - return Err(anyhow!("The given inode is not a directory : its flags are {:?}", types_and_permissions)); - } - - let base_offset = - inode.dbp00 as usize * SUPERBLOCK.get().expect("The superblock has not been initialized yet").block_size() + offset; - + /// Returns the entry at the given `offset` + fn new(offset: usize) -> Result<Self> { // SAFETY: there is always an entry at the base offset pointed by an inode - let subfields = unsafe { read::<Subfields>(base_offset) }?; + let subfields = unsafe { read::<Subfields>(offset) }?; let name_size = subfields.total_size as usize - mem::size_of::<Subfields>(); // SAFETY: it is ensure that the given offset correspond to an entry - let Result::Ok(mut name) = String::from_utf8(unsafe { read_raw(base_offset + mem::size_of::<Subfields>(), name_size) }?.to_vec()) else { unreachable!("Entry names are encoded in ISO-Latin-1 which is a subset of UTF-8"); }; + let Result::Ok(mut name) = String::from_utf8(unsafe { read_raw(offset + mem::size_of::<Subfields>(), name_size) }?.to_vec()) else { unreachable!("Entry names are encoded in ISO-Latin-1 which is a subset of UTF-8"); }; if let Some(index) = name.find('\0') { name.truncate(index); @@ -172,8 +166,9 @@ impl Interface for Directory { } impl node::Directory for Directory { - fn get(&self, _name: &str) -> Option<node::Node> { - todo!() + fn get(&self, name: &str) -> Option<node::Node> { + let entries = self.children(); + entries.into_iter().find(|entry| entry.name() == name) } fn insert(&mut self, _node: node::Node) -> Option<node::Node> { @@ -188,22 +183,24 @@ impl node::Directory for Directory { let mut children = Vec::new(); let self_reference: Arc<Mutex<dyn node::Directory + Send>> = Arc::new(Mutex::new(self.clone())); + let mut inode_cache = INODE_CACHE.lock(); for entry in &self.entries { - let inode = Inode::new(entry.inode as usize).expect("Could not retrieve an inode"); + let inode = inode_cache.get(entry.inode as usize).expect("Could not retrieve an inode"); let inode_type = TypePermissions::from_bits_truncate(inode.type_and_permissions); if inode_type.intersects(TypePermissions::DIRECTORY) { children.push(Node::Directory(Arc::new(Mutex::new( - Self::new(entry.inode as usize, entry.name.clone(), Arc::clone(&self_reference)) + Self::new(&inode, entry.name.clone(), Arc::clone(&self_reference)) .expect("Could not retrieve a directory contained in an inode"), )))); } else if inode_type.intersects(TypePermissions::FILE) { - children.push(Node::File(Arc::new(Mutex::new(File { - name: entry.name.clone(), - })))); + children.push(Node::File(Arc::new(Mutex::new(File::new(entry, &inode).unwrap_or_else(|err| panic!("{err}")))))); } else { - todo!() + warn!( + "Tried to use the entry {:?} : Ext2 is not implemented yet for other inode types than files and directory", + entry + ); } } @@ -216,15 +213,15 @@ unsafe impl Send for Directory {} impl Directory { /// Returns the complete directory pointed by the inode - pub fn new(inode_index: usize, name: String, parent: Ref<dyn node::Directory + Send>) -> Result<Self> { - let inode = Inode::new(inode_index)?; - + pub fn new(inode: &Inode, name: String, parent: Ref<dyn node::Directory + Send>) -> Result<Self> { + let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet"); let mut entries = Vec::new(); let mut accumulated_size = 0_usize; - for _ in 0..=(inode.total_hard_links as usize) { - let entry = Entry::new(&inode, accumulated_size, inode_index)?; + while accumulated_size < superblock.block_size() { + let entry = Entry::new(inode.dbp00 as usize * superblock.block_size() + accumulated_size)?; accumulated_size += entry.total_size as usize; + entries.push(entry); } @@ -237,12 +234,18 @@ impl Directory { /// Returns the root of the filesystem pointed by the given `inode` pub fn root(inode: &Inode) -> Result<Self> { + let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet"); let mut entries = Vec::new(); let mut accumulated_size = 0_usize; - for _ in 0..=(inode.total_hard_links as usize) { - let entry = Entry::new(inode, accumulated_size, ROOT_INODE_INDEX)?; + while accumulated_size < superblock.block_size() { + let entry = Entry::new(inode.dbp00 as usize * superblock.block_size() + accumulated_size)?; accumulated_size += entry.total_size as usize; + + if accumulated_size > superblock.block_size() { + break; + } + entries.push(entry); } @@ -259,14 +262,30 @@ impl Directory { /// An interface for a file #[derive(Debug)] +#[allow(unused)] pub struct File { - /// Name of the file - name: String, + /// Entry of this file + entry: Entry, + + /// Complete length of the file + length: usize, + + /// Direct block pointers to the data + direct_block_pointers: [u32; 12], + + /// Singly Indirect Block Pointer to the data + singly_indirect_block_pointer: u32, + + /// Doubly Indirect Block Pointer to the data + doubly_indirect_block_pointer: u32, + + /// Triply Indirect Block Pointer to the data + triply_indirect_block_pointer: u32, } impl Interface for File { fn name(&self) -> String { - self.name.clone() + self.entry.name() } fn kind(&self) -> Kind { @@ -274,11 +293,11 @@ impl Interface for File { } fn get_parent_dir(&self) -> Ref<dyn node::Directory + Send> { - todo!() + self.entry.get_parent_dir() } - fn set_parent_dir(&mut self, _new_parent: Ref<dyn node::Directory + Send>) { - todo!() + fn set_parent_dir(&mut self, new_parent: Ref<dyn node::Directory + Send>) { + self.entry.set_parent_dir(new_parent); } } @@ -288,7 +307,7 @@ impl Manipulation for File { } fn len(&self) -> usize { - todo!() + self.length } fn read(&self, _buffer: &mut [u8], _offset: usize, _length: usize) -> Result<usize> { @@ -305,3 +324,33 @@ impl Manipulation for File { } impl node::File for File {} + +// SAFETY: files will be always shared behind mutexes +unsafe impl Send for File {} + +impl File { + /// Creates a new file + fn new(entry: &Entry, inode: &Inode) -> Result<Self> { + Ok(Self { + entry: entry.clone(), + length: inode.data_size(), + direct_block_pointers: [ + inode.dbp00, + inode.dbp01, + inode.dbp02, + inode.dbp03, + inode.dbp04, + inode.dbp05, + inode.dbp06, + inode.dbp07, + inode.dbp08, + inode.dbp09, + inode.dbp10, + inode.dbp11, + ], + singly_indirect_block_pointer: inode.singly_indirect_block_pointer, + doubly_indirect_block_pointer: inode.doubly_indirect_block_pointer, + triply_indirect_block_pointer: inode.triply_indirect_block_pointer, + }) + } +} diff --git a/src/fs/ext2/inode.rs b/src/fs/ext2/inode.rs index 78bb973..cfb76cd 100644 --- a/src/fs/ext2/inode.rs +++ b/src/fs/ext2/inode.rs @@ -2,8 +2,12 @@ //! //! See the [OSdev wiki](https://wiki.osdev.org/Ext2#Inodes) for more informations +use alloc::collections::btree_map::Entry; +use alloc::collections::BTreeMap; + use anyhow::Result; use bitflags::bitflags; +use spin::Mutex; use super::block::{BlockGroupDescriptor, Superblock}; use super::{read, SUPERBLOCK}; @@ -15,7 +19,7 @@ pub const ROOT_INODE_INDEX: usize = 2; /// General structure of an Ext2 inode #[repr(packed)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Inode { /// Type and Permissions pub type_and_permissions: u16, @@ -120,7 +124,7 @@ impl Inode { /// Returns the `n`th inode /// /// Beware that the inode indexing starts at **1** - pub fn new(n: usize) -> Result<Self> { + fn new(n: usize) -> Result<Self> { let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet"); let block_group = Self::block_group(superblock, n); let block_group_descriptor = BlockGroupDescriptor::new(block_group)?; @@ -152,6 +156,12 @@ impl Inode { pub const fn containing_block(superblock: &Superblock, n: usize) -> usize { (n * superblock.inode_byte_size as usize) / superblock.block_size() } + + /// Returns the complete size of the data pointed by this inode + #[allow(clippy::cast_possible_truncation)] + pub const fn data_size(&self) -> usize { + self.lower_truncation_byte_size as usize & ((self.higher_truncation_byte_size_or_directory_acl as u64) << 32_u64) as usize + } } bitflags! { @@ -265,3 +275,29 @@ bitflags! { const JOURNAL_FILE_DATA = 0x0004_0000; } } + +/// Structure used to cache inodes not to read them when it is not useful +#[derive(Debug)] +pub struct Cache { + /// Inode dictionary + cache: BTreeMap<usize, Inode>, +} + +impl Cache { + /// Returns the inode with the given `index` + pub fn get(&mut self, index: usize) -> Result<Inode> { + match self.cache.entry(index) { + Entry::Vacant(entry) => { + let inode = Inode::new(index)?; + entry.insert(inode.clone()); + Ok(inode) + }, + Entry::Occupied(entry) => Ok(entry.get().clone()), + } + } +} + +/// Global inode cache +pub static INODE_CACHE: Mutex<Cache> = Mutex::new(Cache { + cache: BTreeMap::new(), +}); diff --git a/src/fs/ext2/mod.rs b/src/fs/ext2/mod.rs index a2de47a..047ac4e 100644 --- a/src/fs/ext2/mod.rs +++ b/src/fs/ext2/mod.rs @@ -11,8 +11,9 @@ use log::info; use spin::Mutex; use self::block::Superblock; -use self::inode::{Inode, ROOT_INODE_INDEX}; +use self::inode::ROOT_INODE_INDEX; use crate::fs::ext2::entry::Directory; +use crate::fs::ext2::inode::INODE_CACHE; use crate::kernel::device::storage::Device; #[allow(clippy::same_name_method)] @@ -67,7 +68,8 @@ pub fn search(device: &Arc<Mutex<dyn Device + Send>>) -> Result<Arc<Mutex<dyn su let superblock = Superblock::new()?; SUPERBLOCK.init_once(|| superblock); - let root_inode = Inode::new(ROOT_INODE_INDEX)?; + let mut inode_cache = INODE_CACHE.lock(); + let root_inode = inode_cache.get(ROOT_INODE_INDEX)?; let root = Directory::root(&root_inode)?; diff --git a/src/fs/mod.rs b/src/fs/mod.rs index f63002b..c2fdc97 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -30,3 +30,33 @@ pub fn init(device: &Arc<Mutex<dyn Device + Send>>) { Err(err) => error!("{}", err), } } + +#[cfg(test)] +mod test { + use alloc::string::String; + use alloc::vec::Vec; + + use ::alloc::vec; + + use super::node::Interface; + use super::ROOT; + use crate::fs::node::Node; + + #[test_case] + fn files_and_directories() { + let root = ROOT.get().unwrap().lock(); + + assert!(root.is_root()); + assert_eq!(root.children().iter().map(Interface::name).collect::<Vec<String>>(), vec![ + ".", + "..", + "lost+found", + "bar", + "foo.txt" + ]); + + let Node::Directory(bar) = root.get("bar").unwrap() else { panic!(); }; + let binding = bar.lock(); + assert_eq!(binding.children().iter().map(Interface::name).collect::<Vec<String>>(), vec![".", "..", "boo", "far.txt"]); + } +} diff --git a/src/main.rs b/src/main.rs index 4cdd3b9..8b629f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ clippy::separated_literal_suffix, clippy::shadow_reuse, clippy::shadow_unrelated, + clippy::todo, clippy::unreachable, clippy::unwrap_in_result, clippy::wildcard_in_or_patterns, -- GitLab