From 7fe4e7f010d2735ec0c08b09275c5b635e4be6cf Mon Sep 17 00:00:00 2001
From: pigeonmoelleux <pigeonmoelleux@crans.org>
Date: Mon, 22 May 2023 23:13:49 +0200
Subject: [PATCH] feat(ext2): read superblock + block group descriptors

---
 .gitignore                   |  2 ++
 Cargo.toml                   |  2 ++
 src/fs/ext2/block.rs         | 47 ++++++++++++++++++++++++++++++++++++
 src/fs/ext2/directory.rs     | 22 +++++++++++++++++
 src/fs/ext2/mod.rs           | 46 +++++++++++++++++++++++++++++++++++
 src/fs/mod.rs                | 24 ++++++++++++++++++
 src/fs/node.rs               |  8 +++---
 src/kernel/device/ata.rs     | 14 +++++++----
 src/kernel/device/block.rs   |  4 +--
 src/kernel/device/mod.rs     |  4 +++
 src/kernel/interrupt/cmos.rs |  3 +++
 src/kernel/mod.rs            |  5 ----
 src/main.rs                  | 22 +++++++++++++++++
 13 files changed, 188 insertions(+), 15 deletions(-)
 create mode 100644 src/fs/ext2/directory.rs

diff --git a/.gitignore b/.gitignore
index 6d33677..e7ebaf2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 /coverage
 /target
+
+storage
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 7f751a5..e1ffa5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,6 +36,7 @@ x86_64 = "0.14"
 minicov = "0.3"
 
 [package.metadata.skavos-bootimage]
+run-storage = "storage"
 test-success-exit-code = 13
 test-args = [
   "-serial", "stdio", 
@@ -44,3 +45,4 @@ test-args = [
   "-display", "none",
   "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"
 ]
+test-mountpoint = "fs_tests"
\ No newline at end of file
diff --git a/src/fs/ext2/block.rs b/src/fs/ext2/block.rs
index 8ecfaeb..6ba2a10 100644
--- a/src/fs/ext2/block.rs
+++ b/src/fs/ext2/block.rs
@@ -2,8 +2,25 @@
 //!
 //! See the [OSdev wiki](https://wiki.osdev.org/Ext2) for more informations
 
+use alloc::sync::Arc;
+use core::mem;
+
+use anyhow::{anyhow, Result};
 use bitflags::bitflags;
 use log::error;
+use spin::Mutex;
+
+use super::read;
+use crate::kernel::device::storage::Device;
+
+/// Starting byte of the superblock in a ext2 storage device
+const SUPERBLOCK_START_BYTE: usize = 1024;
+
+/// Starting byte of the block group descriptor table in a ext2 storage device
+const BLOCK_GROUP_DESCRIPTOR_TABLE_START_BYTE: usize = 2048;
+
+/// Ext2 signature, used to help confirm the presence of an Ext2 volume
+const EXT2_SIGNATURE: u16 = 0xef53;
 
 /// Superblock of the ext2 filesystem
 ///
@@ -138,6 +155,29 @@ pub struct Superblock {
     pub head_orphan_node_list: u32,
 }
 
+impl Superblock {
+    /// Returns the superblock of the device if it exists
+    pub fn new(device: &Arc<Mutex<dyn Device + Send>>) -> Result<Self> {
+        // SAFETY: if the read is successful, a `Superblock` should be present in the first bytes of the buffer
+        let Ok(superblock) = (unsafe { read::<Superblock>(device, SUPERBLOCK_START_BYTE) }) else { return Err(anyhow!("ext2 superblock read: could not read the buffer pointer")) };
+        if superblock.ext2_signature == EXT2_SIGNATURE
+            && superblock.total_blocks.div_ceil(superblock.blocks_per_group)
+                == superblock.total_inodes.div_ceil(superblock.inodes_per_group)
+        {
+            Ok(superblock)
+        } else {
+            Err(anyhow!("ext2 superblock read: malformed ext2 superblock"))
+        }
+    }
+
+    /// Returns the number of block groups
+    ///
+    /// It is equal to the round up of the total number of blocks divided by the number of blocks per block group
+    const fn total_block_groups(&self) -> usize {
+        self.total_blocks.div_ceil(self.blocks_per_group) as usize
+    }
+}
+
 /// File System States
 ///
 /// See the [OSdev wiki](https://wiki.osdev.org/Ext2#File_System_States) for more informations
@@ -296,3 +336,10 @@ pub struct BlockGroupDescriptor {
     /// Number of directories in group
     pub total_dir: u16,
 }
+
+impl BlockGroupDescriptor {
+    /// Returns the `n`th block group descriptor
+    pub fn new(device: &Arc<Mutex<dyn Device + Send>>, n: usize) -> Result<Self> {
+        unsafe { read::<Self>(device, BLOCK_GROUP_DESCRIPTOR_TABLE_START_BYTE + n * mem::size_of::<Self>()) }
+    }
+}
diff --git a/src/fs/ext2/directory.rs b/src/fs/ext2/directory.rs
new file mode 100644
index 0000000..7e38856
--- /dev/null
+++ b/src/fs/ext2/directory.rs
@@ -0,0 +1,22 @@
+//! Implementation of directories in a ext2 filesystem
+//!
+//! See more informations on the [OSdev wiki](https://wiki.osdev.org/Ext2#Directories)
+
+/// Special inodes containing entries as contents
+#[derive(Debug)]
+pub struct Directory {
+    /// Inode
+    pub inode: u32,
+
+    /// Total size of this entry (Including all subfields)
+    pub total_size: u16,
+
+    /// Name Length least-significant 8 bits
+    pub name_length_least_bits: u8,
+
+    /// Type indicator (only if the feature bit for "directory entries have file type byte" is set, else this is the
+    /// most-significant 8 bits of the Name Length)
+    pub type_indicator: u8,
+
+    pub name: [u8],
+}
diff --git a/src/fs/ext2/mod.rs b/src/fs/ext2/mod.rs
index 9f2c900..f2595ba 100644
--- a/src/fs/ext2/mod.rs
+++ b/src/fs/ext2/mod.rs
@@ -1,6 +1,52 @@
 //! Implementation of the Second Extended Filesystem (ext2fs) filesystem
 
+use alloc::slice;
+use alloc::sync::Arc;
+use alloc::vec::Vec;
+use core::{mem, ptr};
+
+use anyhow::Result;
+use spin::Mutex;
+
+use self::block::{BlockGroupDescriptor, Superblock};
+use crate::kernel::device::storage::Device;
+use crate::println;
+
 #[allow(clippy::same_name_method)]
 mod block;
+mod directory;
 #[allow(clippy::same_name_method)]
 mod inode;
+
+/// Reads the device at the given offset **in bytes** and returns the expected structure
+///
+/// # Safety
+///
+/// Must ensure that the structure `T` is present on the device at the given offset
+unsafe fn read<T>(device: &Arc<Mutex<dyn Device + Send>>, offset: usize) -> Result<T> {
+    let mut binding = device.lock();
+    let block_size = binding.block_size();
+
+    let mut buffer_size = 0_usize;
+    while buffer_size < mem::size_of::<T>() {
+        buffer_size += block_size;
+    }
+
+    let (raw_buffer, _, _) = Vec::<u8>::with_capacity(buffer_size).into_raw_parts();
+
+    let buffer = slice::from_raw_parts_mut(raw_buffer, buffer_size);
+
+    binding.read(buffer, offset / block_size, buffer_size / block_size)?;
+
+    Ok(ptr::read::<T>((buffer.as_ptr().add(mem::size_of::<T>() * (offset % block_size))) as *const _))
+}
+
+/// Searches for an ext2 filesystem, and returns the root if found
+pub fn search(device: &Arc<Mutex<dyn Device + Send>>) -> Result<Arc<Mutex<dyn super::node::Directory + Send>>> {
+    let superblock = Superblock::new(device);
+    println!("{:?}", superblock);
+    println!("{:?}", BlockGroupDescriptor::new(device, 0));
+    println!("{:?}", BlockGroupDescriptor::new(device, 1));
+
+    todo!()
+}
diff --git a/src/fs/mod.rs b/src/fs/mod.rs
index 90153e5..45ebef9 100644
--- a/src/fs/mod.rs
+++ b/src/fs/mod.rs
@@ -2,7 +2,31 @@
 //!
 //! Contains modules for the Virtual File System (vfs) and an ext2 implementations
 
+use alloc::sync::Arc;
+
+use conquer_once::spin::OnceCell;
+use log::{error, trace};
+use spin::Mutex;
+
+use self::node::Directory;
+use crate::kernel::device::storage::Device;
+
 mod ext2;
 pub mod node;
 pub mod path;
 pub mod vfs;
+
+/// Root of the filesystem
+pub static ROOT: OnceCell<Arc<Mutex<dyn Directory + Send>>> = OnceCell::uninit();
+
+/// Initializes the filesystem in the given storage device and return the root of this filesystem
+///
+/// If no filesystem is found, an empty one will be created
+pub fn init(device: Arc<Mutex<dyn Device + Send>>) {
+    trace!("Initialization of the device {:?}", device.lock().uuid());
+
+    match ext2::search(&device) {
+        Ok(root) => ROOT.init_once(|| root),
+        Err(err) => error!("{}", err),
+    }
+}
diff --git a/src/fs/node.rs b/src/fs/node.rs
index 85d0386..3a5e8bf 100644
--- a/src/fs/node.rs
+++ b/src/fs/node.rs
@@ -10,7 +10,6 @@ use alloc::sync::Arc;
 use alloc::vec::Vec;
 
 use spin::Mutex;
-use x86_64::structures::paging::page::PageRangeInclusive;
 
 use super::path::Path;
 use crate::kernel::device::block::Manipulation;
@@ -145,8 +144,11 @@ pub trait Directory: Interface {
 
 /// General interface for files
 pub trait File: Interface + Manipulation {
-    /// Returns a memory region on which the file is mounted
-    fn as_pages(&self) -> anyhow::Result<PageRangeInclusive>;
+    /// Returns the raw bytes corresponding to the file
+    fn as_ref(&self) -> &[u8];
+
+    /// Returns the mutable raw bytes corresponding to the file
+    fn as_mut(&mut self) -> &mut [u8];
 }
 
 /// General interface for symbolic links
diff --git a/src/kernel/device/ata.rs b/src/kernel/device/ata.rs
index 05606b7..a2e7191 100644
--- a/src/kernel/device/ata.rs
+++ b/src/kernel/device/ata.rs
@@ -614,7 +614,7 @@ impl Bus {
     /// # Safety
     ///
     /// Must ensure that `lba_start` and `sector` are well-defined
-    unsafe fn write_pio(&mut self, buffer: &mut [u8], role: DriveRole, lba_start: usize, sector_count: usize) -> Result<usize> {
+    unsafe fn write_pio(&mut self, buffer: &[u8], role: DriveRole, lba_start: usize, sector_count: usize) -> Result<usize> {
         if sector_count == 0 {
             return Ok(0);
         }
@@ -627,7 +627,7 @@ impl Bus {
         for _ in lba_start..(lba_start + sector_count) {
             self.wait_ready()?;
 
-            for chunk in buffer.get_unchecked_mut(buffer_offset..(buffer_offset + BLOCK_SIZE)).chunks_exact(2) {
+            for chunk in buffer.get_unchecked(buffer_offset..(buffer_offset + BLOCK_SIZE)).chunks_exact(2) {
                 let word = u16::from(*chunk.get_unchecked(1)) << 8_u8 | u16::from(*chunk.get_unchecked(0));
                 self.data.write(word);
             }
@@ -1160,7 +1160,7 @@ impl Manipulation for Drive {
         self.read_pio(buffer, offset, length)
     }
 
-    fn write(&mut self, buffer: &mut [u8], offset: usize, length: usize) -> Result<usize> {
+    fn write(&mut self, buffer: &[u8], offset: usize, length: usize) -> Result<usize> {
         self.write_pio(buffer, offset, length)
     }
 
@@ -1238,7 +1238,7 @@ impl Drive {
     /// # Safety
     ///
     /// Must ensure that the given buffer has a byte length of at least `length * BLOCK_SIZE`
-    fn write_pio(&mut self, buffer: &mut [u8], offset: usize, length: usize) -> Result<usize> {
+    fn write_pio(&mut self, buffer: &[u8], offset: usize, length: usize) -> Result<usize> {
         if offset > self.size_in_blocks() {
             error!("Write PIO: offset out of bounds");
             return Err(anyhow::Error::msg("Write PIO: Offset out of bounds"));
@@ -1346,7 +1346,11 @@ impl IdeController {
 
         info!(
             "Found an ATA controller at {:?} containing :\n    * Primary master: {:?}\n    * Primary slave: {:?}\n    * Secondary master: {:?}\n    * Secondary slave: {:?}",
-            device.location, primary_master, primary_slave, secondary_master, secondary_slave
+            device.location,
+            primary_master.as_ref().map(|_| "Ok"),
+            primary_slave.as_ref().map(|_| "Ok"),
+            secondary_master.as_ref().map(|_| "Ok"),
+            secondary_slave.as_ref().map(|_| "Ok")
         );
 
         let to_ref = |drive| Arc::new(Mutex::new(drive));
diff --git a/src/kernel/device/block.rs b/src/kernel/device/block.rs
index b1a8fb8..ae7a7ad 100644
--- a/src/kernel/device/block.rs
+++ b/src/kernel/device/block.rs
@@ -1,6 +1,6 @@
 //! Structures and traits for block manipulation
 //!
-//! See the [OSdev wiki](https://wiki.osdev.org/Ext2#What_is_a_Block.3F) for more informations
+//! Blocks are stream of bytes of fixed length
 
 use anyhow::Result;
 
@@ -20,7 +20,7 @@ pub trait Manipulation {
     /// Writes `length` blocks of data from the given `buffer` into I/O stream starting at the given `offset` (in **blocks**)
     ///
     /// Returns the number of blocks written
-    fn write(&mut self, buffer: &mut [u8], offset: usize, length: usize) -> Result<usize>;
+    fn write(&mut self, buffer: &[u8], offset: usize, length: usize) -> Result<usize>;
 
     /// Flushes the output stream, ensuring all contents in intermediate buffers are fully written out
     fn flush(&mut self) -> Result<()>;
diff --git a/src/kernel/device/mod.rs b/src/kernel/device/mod.rs
index 082fb9e..4a0ac2e 100644
--- a/src/kernel/device/mod.rs
+++ b/src/kernel/device/mod.rs
@@ -20,6 +20,10 @@ pub mod storage;
 /// List of references towards all initialized storage controllers
 pub static STORAGE_CONTROLLERS: OnceCell<Vec<Arc<Mutex<dyn storage::Controller + Send>>>> = OnceCell::uninit();
 
+/// UUID of the UEFI file which should not be considered as an available storage device
+pub const UEFI_UUID: Uuid =
+    [0x4D, 0x51, 0x30, 0x30, 0x30, 0x30, 0x20, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20];
+
 /// Initializes the devices
 ///
 /// Currently, is is only used for storage devices.
diff --git a/src/kernel/interrupt/cmos.rs b/src/kernel/interrupt/cmos.rs
index 54276ed..96fe640 100644
--- a/src/kernel/interrupt/cmos.rs
+++ b/src/kernel/interrupt/cmos.rs
@@ -48,6 +48,7 @@ impl Display for Rtc {
 /// See the [OSdev wiki](https://wiki.osdev.org/CMOS#Getting_Current_Date_and_Time_from_RTC) for more details
 #[repr(u8)]
 #[derive(Debug, Clone)]
+#[allow(unused)]
 enum Register {
     /// Second register
     Second = 0x00,
@@ -213,6 +214,7 @@ impl Cmos {
     }
 
     /// Waits until the CMOS is updated
+    #[allow(unused)]
     fn wait_while_updating(&mut self) {
         while self.is_updating() {
             spin_loop();
@@ -220,6 +222,7 @@ impl Cmos {
     }
 
     /// Returns the current Real-Time Clock
+    #[allow(unused)]
     pub fn rtc(&mut self) -> Rtc {
         /// Believe in the docs
         const SRB_BCD_MODE: u8 = 0x04;
diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs
index 6884d46..a45e7f7 100644
--- a/src/kernel/mod.rs
+++ b/src/kernel/mod.rs
@@ -16,9 +16,7 @@ use x86_64::VirtAddr;
 
 use self::memory::paging::{PhysFrameAllocator, MAPPER};
 use self::memory::vmm::VIRTUAL_MEMORY_MANAGER;
-use crate::kernel::interrupt::cmos::CMOS;
 use crate::kernel::screen::buffer::Buffer;
-use crate::println;
 
 /// Initializes the kernel
 pub fn init(boot_info: &'static mut BootInfo) {
@@ -72,7 +70,4 @@ pub fn init(boot_info: &'static mut BootInfo) {
     device::init();
 
     info!("Devices initialized");
-
-    println!("Hello world!");
-    println!("It is currently {}.", CMOS.rtc());
 }
diff --git a/src/main.rs b/src/main.rs
index ab39a49..861475d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -59,9 +59,11 @@
 #![feature(custom_test_frameworks)]
 #![feature(error_in_core)]
 #![feature(extend_one)]
+#![feature(int_roundings)]
 #![feature(no_coverage)]
 #![feature(strict_provenance)]
 #![feature(trait_upcasting)]
+#![feature(vec_into_raw_parts)]
 #![reexport_test_harness_main = "test_main"]
 #![test_runner(test::runner)]
 
@@ -78,13 +80,18 @@ mod user;
 #[cfg(test)]
 mod test;
 
+use alloc::sync::Arc;
+use alloc::vec::Vec;
 use core::alloc::Layout;
 
 use bootloader_api::config::{Mapping, Mappings};
 use bootloader_api::{entry_point, BootInfo, BootloaderConfig};
+use kernel::device::storage::Device;
+use kernel::device::{STORAGE_CONTROLLERS, UEFI_UUID};
 use kernel::task::executor::Executor;
 use kernel::task::Task;
 use log::{logger, set_logger, set_max_level, LevelFilter};
+use spin::Mutex;
 #[cfg(test)]
 use test::exit;
 use user::tty::print_key;
@@ -112,6 +119,21 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
 
     kernel::init(boot_info);
 
+    let storage_devices = STORAGE_CONTROLLERS
+        .get()
+        .expect("Storage controllers have not been initialized yet")
+        .iter()
+        .map(|controller| controller.lock().devices())
+        .flatten()
+        .collect::<Vec<Arc<Mutex<dyn Device + Send>>>>()
+        .into_iter()
+        .filter(|device| if device.lock().uuid() == UEFI_UUID { false } else { true })
+        .collect::<Vec<Arc<Mutex<dyn Device + Send>>>>();
+
+    if let Some(device) = storage_devices.first() {
+        fs::init(Arc::clone(device));
+    }
+
     #[cfg(test)]
     {
         test_main();
-- 
GitLab