diff --git a/.gitignore b/.gitignore index 45b53673..b097cbd1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ *.img .idea/ .vscode/ + +ext4_mount/ diff --git a/Cargo.lock b/Cargo.lock index 4cb9b9fc..0c5eacdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,21 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -74,6 +89,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ext4-view" +version = "0.9.3" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#c12b2acf43c7472096752b509af0fd92b0731e8c" +dependencies = [ + "async-trait", + "bitflags", + "crc", +] + [[package]] name = "fdt-parser" version = "0.4.18" @@ -192,6 +217,7 @@ version = "0.0.0" dependencies = [ "async-trait", "bitflags", + "ext4-view", "intrusive-collections", "log", "object", @@ -479,9 +505,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ "proc-macro2", "quote", diff --git a/libkernel/Cargo.toml b/libkernel/Cargo.toml index a36cd9de..409608b9 100644 --- a/libkernel/Cargo.toml +++ b/libkernel/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2024" [dependencies] +ext4-view = { git = "https://github.com/arihant2math/ext4-view-rs.git", branch = "main" } paste = "1.0.15" thiserror = { version = "2.0.12", default-features = false } tock-registers = "0.10.1" diff --git a/libkernel/src/fs/attr.rs b/libkernel/src/fs/attr.rs index 2e90f368..a385fcf4 100644 --- a/libkernel/src/fs/attr.rs +++ b/libkernel/src/fs/attr.rs @@ -25,25 +25,31 @@ bitflags::bitflags! { bitflags! { #[derive(Clone, Copy, Debug)] pub struct FilePermissions: u16 { - // Owner permissions - const S_IRUSR = 0o400; // Read permission, owner - const S_IWUSR = 0o200; // Write permission, owner - const S_IXUSR = 0o100; // Execute/search permission, owner - - // Group permissions - const S_IRGRP = 0o040; // Read permission, group - const S_IWGRP = 0o020; // Write permission, group - const S_IXGRP = 0o010; // Execute/search permission, group - - // Others permissions - const S_IROTH = 0o004; // Read permission, others - const S_IWOTH = 0o002; // Write permission, others - const S_IXOTH = 0o001; // Execute/search permission, others - - // Optional: sticky/setuid/setgid bits - const S_ISUID = 0o4000; // Set-user-ID on execution - const S_ISGID = 0o2000; // Set-group-ID on execution - const S_ISVTX = 0o1000; // Sticky bit + const S_IXOTH = 0x0001; + const S_IWOTH = 0x0002; + const S_IROTH = 0x0004; + + const S_IXGRP = 0x0008; + const S_IWGRP = 0x0010; + const S_IRGRP = 0x0020; + + const S_IXUSR = 0x0040; + const S_IWUSR = 0x0080; + const S_IRUSR = 0x0100; + + const S_ISVTX = 0x0200; + + const S_ISGID = 0x0400; + const S_ISUID = 0x0800; + + // Mutually-exclusive file types: + const S_IFIFO = 0x1000; + const S_IFCHR = 0x2000; + const S_IFDIR = 0x4000; + const S_IFBLK = 0x6000; + const S_IFREG = 0x8000; + const S_IFLNK = 0xA000; + const S_IFSOCK = 0xC000; } } diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs new file mode 100644 index 00000000..b1abe1f7 --- /dev/null +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -0,0 +1,275 @@ +//! EXT4 Filesystem Driver + +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_imports)] + +use crate::error::FsError; +use crate::fs::pathbuf::PathBuf; +use crate::fs::{DirStream, Dirent}; +use crate::proc::ids::{Gid, Uid}; +use crate::{ + error::{KernelError, Result}, + fs::{ + FileType, Filesystem, Inode, InodeId, + attr::{FileAttr, FilePermissions}, + blk::buffer::BlockBuffer, + }, +}; +use alloc::string::ToString; +use alloc::{ + boxed::Box, + sync::{Arc, Weak}, +}; +use async_trait::async_trait; +use core::error::Error; +use ext4_view::{ + AsyncIterator, AsyncSkip, Ext4, Ext4Read, File, FollowSymlinks, Metadata, ReadDir, + get_dir_entry_inode_by_name, +}; + +#[async_trait] +impl Ext4Read for BlockBuffer { + async fn read( + &self, + start_byte: u64, + dst: &mut [u8], + ) -> core::result::Result<(), Box> { + Ok(self.read_at(start_byte, dst).await?) + } +} + +impl From for KernelError { + fn from(err: ext4_view::Ext4Error) -> Self { + match err { + ext4_view::Ext4Error::NotFound => KernelError::Fs(FsError::NotFound), + ext4_view::Ext4Error::NotADirectory => KernelError::Fs(FsError::NotADirectory), + ext4_view::Ext4Error::Corrupt(_) => KernelError::Fs(FsError::InvalidFs), + _ => KernelError::Fs(FsError::InvalidFs), + } + } +} + +impl From for FileType { + fn from(ft: ext4_view::FileType) -> Self { + match ft { + ext4_view::FileType::BlockDevice => todo!(), + ext4_view::FileType::CharacterDevice => todo!(), + ext4_view::FileType::Directory => FileType::Directory, + ext4_view::FileType::Fifo => FileType::Fifo, + ext4_view::FileType::Regular => FileType::File, + ext4_view::FileType::Socket => FileType::Socket, + ext4_view::FileType::Symlink => FileType::Symlink, + } + } +} + +impl From for FileAttr { + fn from(meta: Metadata) -> Self { + FileAttr { + size: meta.size_in_bytes, + file_type: meta.file_type.into(), + // Infallible, since they are identical + mode: FilePermissions::from_bits(meta.mode.bits()).unwrap(), + uid: Uid::new(meta.uid), + gid: Gid::new(meta.gid), + ..Default::default() + } + } +} + +pub struct ReadDirWrapper { + inner: AsyncSkip, + fs_id: u64, + current_off: u64, +} + +impl ReadDirWrapper { + pub fn new(inner: ReadDir, fs_id: u64, start_offset: u64) -> Self { + Self { + inner: inner.skip(start_offset as usize), + fs_id, + current_off: start_offset, + } + } +} + +#[async_trait] +impl DirStream for ReadDirWrapper { + async fn next_entry(&mut self) -> Result> { + match self.inner.next().await { + Some(entry) => { + let entry = entry?; + self.current_off += 1; + Ok(Some(Dirent { + id: InodeId::from_fsid_and_inodeid(self.fs_id, entry.inode.get() as u64), + name: entry.file_name().as_str().unwrap().to_string(), + file_type: entry.file_type()?.into(), + offset: self.current_off, + })) + } + None => Ok(None), + } + } +} + +pub struct Ext4Inode { + fs_ref: Weak, + inner: ext4_view::Inode, + path: ext4_view::PathBuf, +} + +#[async_trait] +impl Inode for Ext4Inode { + fn id(&self) -> InodeId { + let fs = self.fs_ref.upgrade().unwrap(); + InodeId::from_fsid_and_inodeid(fs.id(), self.inner.index.get() as u64) + } + + async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result { + // Must be a regular file. + if self.inner.metadata.file_type != ext4_view::FileType::Regular { + return Err(KernelError::NotSupported); + } + + let file_size = self.inner.metadata.size_in_bytes; + + // Past EOF = nothing to read. + if offset >= file_size { + return Ok(0); + } + + // Do not read past the end of the file. + let to_read = core::cmp::min(buf.len() as u64, file_size - offset) as usize; + + let fs = self.fs_ref.upgrade().unwrap(); + let mut file = File::open_inode(&fs.inner, self.inner.clone())?; + + file.seek_to(offset).await?; + + // `ext4_view::File::read_bytes` may return fewer bytes than requested + // if the read crosses a block boundary. Loop until we've filled + // `to_read` bytes or hit EOF. + let mut total_read = 0; + while total_read < to_read { + let bytes_read = file.read_bytes(&mut buf[total_read..to_read]).await?; + if bytes_read == 0 { + break; // EOF + } + total_read += bytes_read; + } + + Ok(total_read) + } + + async fn write_at(&self, _offset: u64, _buf: &[u8]) -> Result { + Err(KernelError::NotSupported) + } + + async fn truncate(&self, _size: u64) -> Result<()> { + Err(KernelError::NotSupported) + } + + async fn getattr(&self) -> Result { + Ok(self.inner.metadata.clone().into()) + } + + async fn lookup(&self, name: &str) -> Result> { + let fs = self.fs_ref.upgrade().unwrap(); + let child_inode = get_dir_entry_inode_by_name( + &fs.inner, + &self.inner, + ext4_view::DirEntryName::try_from(name) + .map_err(|_| KernelError::Fs(FsError::InvalidInput))?, + ) + .await?; + let child_path = self.path.join(name); + Ok(Arc::new(Ext4Inode { + fs_ref: self.fs_ref.clone(), + inner: child_inode, + path: child_path, + })) + } + + async fn create( + &self, + _name: &str, + _file_type: FileType, + _permissions: FilePermissions, + ) -> Result> { + Err(KernelError::NotSupported) + } + + async fn unlink(&self, _name: &str) -> Result<()> { + Err(KernelError::NotSupported) + } + + async fn readdir(&self, start_offset: u64) -> Result> { + if self.inner.metadata.file_type != ext4_view::FileType::Directory { + return Err(KernelError::NotSupported); + } + let fs = self.fs_ref.upgrade().unwrap(); + Ok(Box::new(ReadDirWrapper::new( + ReadDir::new(fs.inner.clone(), &self.inner, self.path.clone())?, + fs.id(), + start_offset, + ))) + } + + async fn readlink(&self) -> Result { + if self.inner.metadata.file_type != ext4_view::FileType::Symlink { + return Err(KernelError::NotSupported); + } + let fs = self.fs_ref.upgrade().unwrap(); + // Conversion has to ensure path is valid UTF-8 (O(n) time). + Ok(self + .inner + .symlink_target(&fs.inner) + .await + .map(|p| PathBuf::from(p.to_str().unwrap()))?) + } +} + +/// An EXT4 filesystem instance. +/// +/// For now this struct only stores the underlying block buffer and an ID +/// assigned by the VFS when the filesystem is mounted. +pub struct Ext4Filesystem { + inner: Ext4, + id: u64, + this: Weak, +} + +impl Ext4Filesystem { + /// Construct a new EXT4 filesystem instance. + pub async fn new(dev: BlockBuffer, id: u64) -> Result> { + let inner = Ext4::load(Box::new(dev)).await?; + Ok(Arc::new_cyclic(|weak| Self { + inner, + id, + this: weak.clone(), + })) + } +} + +#[async_trait] +impl Filesystem for Ext4Filesystem { + fn id(&self) -> u64 { + self.id + } + + /// Returns the root inode of the mounted EXT4 filesystem. + async fn root_inode(&self) -> Result> { + Ok(Arc::new(Ext4Inode { + fs_ref: self.this.clone(), + inner: self.inner.read_root_inode().await?, + path: ext4_view::PathBuf::new("/"), + })) + } + + /// Flushes any dirty data to the underlying block device. The current + /// stub implementation simply forwards the request to `BlockBuffer::sync`. + async fn sync(&self) -> Result<()> { + Ok(()) + } +} diff --git a/libkernel/src/fs/filesystems/fat32/bpb.rs b/libkernel/src/fs/filesystems/fat32/bpb.rs index d9b6f608..b0420aee 100644 --- a/libkernel/src/fs/filesystems/fat32/bpb.rs +++ b/libkernel/src/fs/filesystems/fat32/bpb.rs @@ -106,7 +106,7 @@ impl BiosParameterBlock { } pub fn fat_region(&self, fat_number: usize) -> Option<(Sector, Sector)> { - if fat_number >= self.num_fats as _ { + if fat_number >= self.num_fats as usize { None } else { let start = self.fat_region_start() + self.fat_len() * fat_number; diff --git a/libkernel/src/fs/filesystems/mod.rs b/libkernel/src/fs/filesystems/mod.rs index c50261a8..4b763b6a 100644 --- a/libkernel/src/fs/filesystems/mod.rs +++ b/libkernel/src/fs/filesystems/mod.rs @@ -1,2 +1,3 @@ +pub mod ext4; pub mod fat32; pub mod tmpfs; diff --git a/src/drivers/fs/ext4.rs b/src/drivers/fs/ext4.rs new file mode 100644 index 00000000..2bb4a877 --- /dev/null +++ b/src/drivers/fs/ext4.rs @@ -0,0 +1,43 @@ +use crate::{drivers::Driver, fs::FilesystemDriver}; +use alloc::{boxed::Box, sync::Arc}; +use async_trait::async_trait; +use libkernel::{ + error::{KernelError, Result}, + fs::{BlockDevice, Filesystem, blk::buffer::BlockBuffer, filesystems::ext4::Ext4Filesystem}, +}; +use log::warn; + +pub struct Ext4FsDriver {} + +impl Ext4FsDriver { + pub fn new() -> Self { + Self {} + } +} + +impl Driver for Ext4FsDriver { + fn name(&self) -> &'static str { + "ext4fs" + } + + fn as_filesystem_driver(self: Arc) -> Option> { + Some(self) + } +} + +#[async_trait] +impl FilesystemDriver for Ext4FsDriver { + async fn construct( + &self, + fs_id: u64, + device: Option>, + ) -> Result> { + match device { + Some(dev) => Ok(Ext4Filesystem::new(BlockBuffer::new(dev), fs_id).await?), + None => { + warn!("Could not mount fat32 fs with no block device"); + Err(KernelError::InvalidValue) + } + } + } +} diff --git a/src/drivers/fs/mod.rs b/src/drivers/fs/mod.rs index a5688b66..89399462 100644 --- a/src/drivers/fs/mod.rs +++ b/src/drivers/fs/mod.rs @@ -1,5 +1,6 @@ use alloc::sync::Arc; use dev::DevFsDriver; +use ext4::Ext4FsDriver; use fat32::Fat32FsDriver; use proc::ProcFsDriver; use tmpfs::TmpFsDriver; @@ -7,6 +8,7 @@ use tmpfs::TmpFsDriver; use super::DM; pub mod dev; +pub mod ext4; pub mod fat32; pub mod proc; pub mod tmpfs; @@ -14,6 +16,7 @@ pub mod tmpfs; pub fn register_fs_drivers() { let mut dm = DM.lock_save_irq(); + dm.insert_driver(Arc::new(Ext4FsDriver::new())); dm.insert_driver(Arc::new(Fat32FsDriver::new())); dm.insert_driver(Arc::new(DevFsDriver::new())); dm.insert_driver(Arc::new(ProcFsDriver::new())); diff --git a/src/process/caps.rs b/src/process/caps.rs index 893656a8..5c807d11 100644 --- a/src/process/caps.rs +++ b/src/process/caps.rs @@ -58,7 +58,7 @@ pub async fn sys_capget(hdrp: TUA, datap: TUA) -> Re TASK_LIST .lock_save_irq() .iter() - .find(|task| task.0.tgid.value() == header.pid as _) + .find(|task| task.0.tgid.value() == header.pid as u32) .and_then(|task| task.1.upgrade()) .ok_or(KernelError::NoProcess)? }; @@ -93,7 +93,7 @@ pub async fn sys_capset(hdrp: TUA, datap: TUA) -> Re TASK_LIST .lock_save_irq() .iter() - .find(|task| task.0.tgid.value() == header.pid as _) + .find(|task| task.0.tgid.value() == header.pid as u32) .and_then(|task| task.1.upgrade()) .ok_or(KernelError::NoProcess)? };