diff --git a/Makefile b/Makefile index e017042b4..1be2486ce 100644 --- a/Makefile +++ b/Makefile @@ -171,6 +171,7 @@ QEMU_X86_ARGS+= -numa node,memdev=m0,cpus=0-3,nodeid=0 QEMU_X86_ARGS+= -numa node,memdev=m1,cpus=4-7,nodeid=1 QEMU_X86_ARGS+= -numa node,memdev=m2,cpus=8-11,nodeid=2 QEMU_X86_ARGS+= -numa node,memdev=m3,cpus=12-15,nodeid=3 +QEMU_X86_ARGS+= -nographic # QEMU_X86_ARGS+= -d int # debug interrupt # QEMU_X86_ARGS+= --trace apic_mem_writel # debug APIC # QEMU_X86_ARGS+= --trace "e1000e_irq_*" --trace "pci_cfg_*" # debug e1000e and PCI diff --git a/applications/tests/test_memfatfs/Cargo.toml b/applications/tests/test_memfatfs/Cargo.toml new file mode 100644 index 000000000..10e2b7926 --- /dev/null +++ b/applications/tests/test_memfatfs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test_memfatfs" +version = "0.1.0" +edition = "2024" + +[dependencies] +log = "0.4" + +[dependencies.awkernel_async_lib] +path = "../../../awkernel_async_lib" +default-features = false + +[dependencies.awkernel_lib] +path = "../../../awkernel_lib" +default-features = false diff --git a/applications/tests/test_memfatfs/src/lib.rs b/applications/tests/test_memfatfs/src/lib.rs new file mode 100644 index 000000000..954c2dee8 --- /dev/null +++ b/applications/tests/test_memfatfs/src/lib.rs @@ -0,0 +1,91 @@ +#![no_std] + +extern crate alloc; + +use alloc::vec; +use awkernel_async_lib::file::path::AsyncVfsPath; +use awkernel_lib::file::fatfs::init_memory_fatfs; + +pub async fn run() { + awkernel_async_lib::spawn( + "test fatfs".into(), + memfatfs_test(), + awkernel_async_lib::scheduler::SchedulerType::FIFO, + ) + .await; +} + +async fn memfatfs_test() { + // TODO - Remove this when the filesystem is fully ready. This will be done in the kernel initialization. + match init_memory_fatfs() { + Ok(_) => log::info!("In-memory FAT filesystem initialized successfully."), + Err(e) => { + log::error!("Failed to initialize in-memory FAT filesystem: {e:?}"); + return; + } + } + + let root_path = AsyncVfsPath::new_in_memory_fatfs(); + let file_name = "test.txt"; + let data_to_write = b"Hello from the in-memory FAT filesystem!"; + let bytes_written; + + let file_path = root_path.join(file_name).unwrap(); + log::info!("Attempting to create and write to file '{file_name}'"); + + match file_path.create_file().await { + Ok(mut file) => match file.write(data_to_write).await { + Ok(len) => { + bytes_written = len; + log::info!("Successfully wrote {bytes_written} bytes to '{file_name}'."); + } + Err(e) => { + log::error!("Failed to write to file '{file_name}': {e:?}"); + return; + } + }, + Err(e) => { + log::error!("Failed to create file '{file_name}': {e:?}"); + return; + } + }; + + log::info!("Attempting to open and read from file '{file_name}'"); + if bytes_written == 0 { + log::warn!("No bytes were written, skipping file read operation."); + return; + } + + match file_path.open_file().await { + Ok(mut file) => { + let mut read_buffer = vec![0; bytes_written]; + match file.read(&mut read_buffer).await { + Ok(bytes_read) => { + log::info!("Successfully read {bytes_read} bytes from '{file_name}'."); + if bytes_read != bytes_written { + log::warn!( + "Bytes read ({bytes_read}) does not match bytes written ({bytes_written})." + ); + } + + match core::str::from_utf8(&read_buffer[..bytes_read]) { + Ok(s) => log::info!("Content of '{file_name}': \"{s}\""), + Err(_) => log::warn!( + "Content of '{}' is not valid UTF-8. Raw bytes: {:?}", + file_name, + &read_buffer[..bytes_read] + ), + } + } + Err(e) => { + log::error!("Failed to read from file '{file_name}': {e:?}"); + } + } + } + Err(e) => { + log::error!("Failed to open file '{file_name}': {e:?}"); + } + } + + log::info!("FAT filesystem test completed."); +} diff --git a/awkernel_async_lib/src/file.rs b/awkernel_async_lib/src/file.rs index 6a54b8520..c65cd0ec6 100644 --- a/awkernel_async_lib/src/file.rs +++ b/awkernel_async_lib/src/file.rs @@ -1,2 +1,3 @@ +pub mod fatfs; pub mod filesystem; pub mod path; diff --git a/awkernel_async_lib/src/file/fatfs.rs b/awkernel_async_lib/src/file/fatfs.rs new file mode 100644 index 000000000..d7862ad8f --- /dev/null +++ b/awkernel_async_lib/src/file/fatfs.rs @@ -0,0 +1,255 @@ +use super::filesystem::{AsyncFileSystem, AsyncSeekAndRead, AsyncSeekAndWrite}; +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use async_trait::async_trait; +use awkernel_lib::{ + file::{ + fatfs::{ + error::Error, + file::File, + fs::{FileSystem, LossyOemCpConverter, OemCpConverter, ReadWriteSeek}, + get_memory_fatfs, + time::{Date, DateTime, NullTimeProvider, TimeProvider}, + }, + io::{Read, Seek, SeekFrom, Write}, + memfs::InMemoryDisk, + vfs::{ + error::{VfsError, VfsErrorKind, VfsIoError, VfsResult}, + path::{VfsFileType, VfsMetadata}, + }, + }, + sync::{mcs::MCSNode, mutex::Mutex}, + time::Time, +}; +use core::fmt::Debug; +use futures::stream::{self, Stream}; + +struct AsyncFile +where + IO: ReadWriteSeek + Send + Debug + Sync, + TP: TimeProvider + Send + Sync, + OCC: OemCpConverter + Send + Sync, +{ + file: Mutex>, +} + +#[async_trait] +impl AsyncSeekAndRead for AsyncFile +where + IO: ReadWriteSeek + Send + Sync + Debug, + IO::Error: Into, + TP: TimeProvider + Send + Sync, + OCC: OemCpConverter + Send + Sync, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut node = MCSNode::new(); + let mut file_guard = self.file.lock(&mut node); + (*file_guard) + .read(buf) + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + } + + async fn seek(&mut self, pos: SeekFrom) -> Result { + let mut node = MCSNode::new(); + let mut file_guard = self.file.lock(&mut node); + (*file_guard) + .seek(pos) + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + } +} + +#[async_trait] +impl AsyncSeekAndWrite for AsyncFile +where + IO: ReadWriteSeek + Send + Debug + Sync, + IO::Error: Into, + TP: TimeProvider + Send + Sync, + OCC: OemCpConverter + Send + Sync, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + let mut node = MCSNode::new(); + let mut file_guard = self.file.lock(&mut node); + (*file_guard) + .write(buf) + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + } + + async fn write_all(&mut self, buf: &[u8]) -> Result<(), VfsError> { + let mut node = MCSNode::new(); + let mut file_guard = self.file.lock(&mut node); + (*file_guard) + .write_all(buf) + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + } + + async fn flush(&mut self) -> Result<(), VfsError> { + let mut node = MCSNode::new(); + let mut file_guard = self.file.lock(&mut node); + (*file_guard) + .flush() + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + } + + async fn seek(&mut self, pos: SeekFrom) -> Result { + ::seek(self, pos).await + } +} + +#[derive(Debug)] +pub struct AsyncFatFs +where + IO: ReadWriteSeek + Send + Sync + Debug, + TP: TimeProvider + Send + Sync, + OCC: OemCpConverter + Send + Sync, +{ + fs: Arc>, +} + +impl AsyncFatFs { + pub fn new_in_memory() -> Self { + Self { + fs: get_memory_fatfs(), + } + } +} + +#[async_trait] +impl AsyncFileSystem for AsyncFatFs +where + IO: ReadWriteSeek + Send + Sync + Debug + 'static, + IO::Error: Into, + TP: TimeProvider + Send + Sync + 'static, + OCC: OemCpConverter + Send + Sync + 'static, +{ + async fn read_dir( + &self, + path: &str, + ) -> VfsResult + Send>> { + let dir = FileSystem::root_dir(&self.fs) + .open_dir(path) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + let entries: Result, _> = dir + .iter() + .map(|entry_res| { + entry_res + .map_err(|e| VfsError::from(VfsErrorKind::from(e))) + .map(|entry| entry.file_name()) + }) + .collect(); + entries.map(|names| { + Box::new(stream::iter(names)) as Box + Send> + }) + } + + async fn create_dir(&self, path: &str) -> VfsResult<()> { + FileSystem::root_dir(&self.fs) + .create_dir(path) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + Ok(()) + } + + async fn open_file(&self, path: &str) -> VfsResult> { + let file = FileSystem::root_dir(&self.fs) + .open_file(path) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + + Ok(Box::new(AsyncFile { + file: Mutex::new(file), + })) + } + + async fn create_file( + &self, + path: &str, + ) -> VfsResult> { + let file = FileSystem::root_dir(&self.fs) + .create_file(path) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + + Ok(Box::new(AsyncFile { + file: Mutex::new(file), + })) + } + + async fn append_file( + &self, + path: &str, + ) -> VfsResult> { + let file = { + let result: Result, Error> = (|| { + let mut file = FileSystem::root_dir(&self.fs).open_file(path)?; + file.seek(SeekFrom::End(0))?; + Ok(file) + })(); + result + } + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + + Ok(Box::new(AsyncFile { + file: Mutex::new(file), + })) + } + + async fn metadata(&self, path: &str) -> VfsResult { + if path.is_empty() { + return Ok(VfsMetadata { + file_type: VfsFileType::Directory, + len: 0, + created: None, + modified: None, + accessed: None, + }); + } + let entry = FileSystem::root_dir(&self.fs) + .find_entry(path, None, None) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + let metadata = VfsMetadata { + file_type: if entry.is_dir() { + VfsFileType::Directory + } else { + VfsFileType::File + }, + len: entry.len(), + created: to_vfs_datetime(entry.created()), + modified: to_vfs_datetime(entry.modified()), + accessed: to_vfs_date(entry.accessed()), + }; + Ok(metadata) + } + + async fn exists(&self, path: &str) -> VfsResult { + if path.is_empty() { + return Ok(true); + } + Ok(FileSystem::root_dir(&self.fs) + .find_entry(path, None, None) + .is_ok()) + } + + async fn remove_file(&self, path: &str) -> VfsResult<()> { + FileSystem::root_dir(&self.fs) + .remove(path) + .map_err(|e| VfsError::from(VfsErrorKind::from(e)))?; + + Ok(()) + } + + async fn remove_dir(&self, path: &str) -> VfsResult<()> { + self.remove_file(path).await + } +} + +fn to_vfs_datetime(_date_time: DateTime) -> Option