From aadd97fc8e850ffc0123a59cc39674c6961bc4c0 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Sat, 2 Dec 2023 20:28:43 +0200 Subject: [PATCH] fs: initial rework --- lib/hosted-tests/Cargo.toml | 8 + lib/hosted-tests/src/lib.rs | 9 + lib/kernel-util/src/sync.rs | 13 + lib/vfs/Cargo.toml | 7 + lib/vfs/src/device.rs | 80 ++++++ lib/vfs/src/file/device.rs | 84 ++++++ lib/vfs/src/file/directory.rs | 106 +++++++ lib/vfs/src/file/mod.rs | 500 ++++++++++++++++++++++++++++++++++ lib/vfs/src/file/regular.rs | 85 ++++++ lib/vfs/src/lib.rs | 18 +- lib/vfs/src/node/impls.rs | 294 ++++++++++++++++++++ lib/vfs/src/node/mod.rs | 196 +++++++++++++ lib/vfs/src/node/ops.rs | 47 ++++ lib/vfs/src/node/traits.rs | 88 ++++++ lib/vfs/src/node/tree.rs | 39 +++ lib/vfs/src/traits.rs | 17 ++ 16 files changed, 1590 insertions(+), 1 deletion(-) create mode 100644 lib/hosted-tests/Cargo.toml create mode 100644 lib/hosted-tests/src/lib.rs create mode 100644 lib/vfs/src/device.rs create mode 100644 lib/vfs/src/file/device.rs create mode 100644 lib/vfs/src/file/directory.rs create mode 100644 lib/vfs/src/file/mod.rs create mode 100644 lib/vfs/src/file/regular.rs create mode 100644 lib/vfs/src/node/impls.rs create mode 100644 lib/vfs/src/node/mod.rs create mode 100644 lib/vfs/src/node/ops.rs create mode 100644 lib/vfs/src/node/traits.rs create mode 100644 lib/vfs/src/node/tree.rs create mode 100644 lib/vfs/src/traits.rs diff --git a/lib/hosted-tests/Cargo.toml b/lib/hosted-tests/Cargo.toml new file mode 100644 index 00000000..2699c8a0 --- /dev/null +++ b/lib/hosted-tests/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hosted-tests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/lib/hosted-tests/src/lib.rs b/lib/hosted-tests/src/lib.rs new file mode 100644 index 00000000..aa7fc159 --- /dev/null +++ b/lib/hosted-tests/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +#[no_mangle] +fn __acquire_irq_guard() -> bool { + false +} + +#[no_mangle] +fn __release_irq_guard(_: bool) {} diff --git a/lib/kernel-util/src/sync.rs b/lib/kernel-util/src/sync.rs index c567357c..146d8613 100644 --- a/lib/kernel-util/src/sync.rs +++ b/lib/kernel-util/src/sync.rs @@ -1,6 +1,7 @@ //! Synchronization primitives use core::{ cell::UnsafeCell, + mem, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, }; @@ -116,6 +117,12 @@ impl IrqSafeSpinlock { } } + #[inline] + pub fn replace(&self, value: T) -> T { + let mut lock = self.lock(); + mem::replace(&mut lock, value) + } + /// Attempts to acquire a lock. IRQs will be disabled until the lock is released. pub fn lock(&self) -> IrqSafeSpinlockGuard { // Disable IRQs to avoid IRQ handler trying to acquire the same lock @@ -141,6 +148,12 @@ impl IrqSafeSpinlock { } } +impl IrqSafeSpinlock { + pub fn cloned(&self) -> T { + self.lock().clone() + } +} + impl<'a, T> Deref for IrqSafeSpinlockGuard<'a, T> { type Target = T; diff --git a/lib/vfs/Cargo.toml b/lib/vfs/Cargo.toml index 6d86b5ea..e089a224 100644 --- a/lib/vfs/Cargo.toml +++ b/lib/vfs/Cargo.toml @@ -6,3 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" } +kernel-util = { path = "../kernel-util" } + +log = "0.4.20" + +[dev-dependencies] +hosted-tests = { path = "../hosted-tests" } diff --git a/lib/vfs/src/device.rs b/lib/vfs/src/device.rs new file mode 100644 index 00000000..0fbc1643 --- /dev/null +++ b/lib/vfs/src/device.rs @@ -0,0 +1,80 @@ +use yggdrasil_abi::error::Error; + +use crate::node::{CommonImpl, NodeRef}; + +pub trait BlockDevice { + fn read(&self, pos: u64, buf: &mut [u8]) -> Result { + Err(Error::NotImplemented) + } + fn write(&self, pos: u64, buf: &[u8]) -> Result { + Err(Error::NotImplemented) + } + + fn is_readable(&self) -> bool { + true + } + fn is_writable(&self) -> bool { + true + } + fn size(&self) -> Result; +} + +pub trait CharDevice { + fn read(&self, buf: &mut [u8]) -> Result { + Err(Error::NotImplemented) + } + fn write(&self, buf: &[u8]) -> Result { + Err(Error::NotImplemented) + } + + fn is_readable(&self) -> bool { + true + } + fn is_writable(&self) -> bool { + true + } + fn is_terminal(&self) -> bool { + false + } +} + +#[derive(Clone)] +pub struct BlockDeviceWrapper(pub(crate) &'static dyn BlockDevice); +#[derive(Clone)] +pub struct CharDeviceWrapper(pub(crate) &'static dyn CharDevice); + +impl BlockDeviceWrapper { + pub fn is_readable(&self) -> bool { + self.0.is_readable() + } + + pub fn is_writable(&self) -> bool { + self.0.is_writable() + } +} + +impl CommonImpl for BlockDeviceWrapper { + fn size(&self, _node: &NodeRef) -> Result { + self.0.size() + } +} + +impl CharDeviceWrapper { + pub fn is_terminal(&self) -> bool { + self.0.is_terminal() + } + + pub fn is_readable(&self) -> bool { + self.0.is_readable() + } + + pub fn is_writable(&self) -> bool { + self.0.is_writable() + } +} + +impl CommonImpl for CharDeviceWrapper { + fn size(&self, _node: &NodeRef) -> Result { + Ok(0) + } +} diff --git a/lib/vfs/src/file/device.rs b/lib/vfs/src/file/device.rs new file mode 100644 index 00000000..58a73826 --- /dev/null +++ b/lib/vfs/src/file/device.rs @@ -0,0 +1,84 @@ +use core::cell::Cell; + +use yggdrasil_abi::{error::Error, io::SeekFrom}; + +use crate::{ + device::{BlockDeviceWrapper, CharDeviceWrapper}, + node::NodeRef, +}; + +pub struct BlockFile { + pub(super) device: BlockDeviceWrapper, + pub(super) node: NodeRef, + pub(super) position: Cell, + pub(super) read: bool, + pub(super) write: bool, +} + +pub struct CharFile { + pub(super) device: CharDeviceWrapper, + pub(super) node: NodeRef, + pub(super) read: bool, + pub(super) write: bool, +} + +impl BlockFile { + pub fn read(&self, buf: &mut [u8]) -> Result { + let pos = self.position.get(); + let count = self.device.0.read(pos, buf)?; + self.position.set(pos + count as u64); + Ok(count) + } + + pub fn write(&self, buf: &[u8]) -> Result { + let pos = self.position.get(); + let count = self.device.0.write(pos, buf)?; + self.position.set(pos + count as u64); + Ok(count) + } + + pub fn seek(&self, from: SeekFrom) -> Result { + let pos = self.position.get(); + let newpos = match from { + SeekFrom::Current(off) => { + let newpos = i64::try_from(pos).unwrap() + off; + if newpos < 0 { + return Err(Error::InvalidArgument); + } + newpos as u64 + } + SeekFrom::Start(pos) => pos, + SeekFrom::End(off) => { + let size = i64::try_from(self.device.0.size()?).unwrap(); + let newpos = size + off; + + if newpos < 0 { + return Err(Error::InvalidArgument); + } + + newpos as u64 + } + }; + + self.position.set(newpos); + Ok(newpos) + } +} + +impl CharFile { + pub fn read(&self, buf: &mut [u8]) -> Result { + if self.read { + self.device.0.read(buf) + } else { + Err(Error::InvalidOperation) + } + } + + pub fn write(&self, buf: &[u8]) -> Result { + if self.write { + self.device.0.write(buf) + } else { + Err(Error::ReadOnly) + } + } +} diff --git a/lib/vfs/src/file/directory.rs b/lib/vfs/src/file/directory.rs new file mode 100644 index 00000000..1043a8c5 --- /dev/null +++ b/lib/vfs/src/file/directory.rs @@ -0,0 +1,106 @@ +use core::{cell::Cell, mem::MaybeUninit, str::FromStr}; + +use yggdrasil_abi::{error::Error, io::DirectoryEntry, util::FixedString}; + +use crate::node::NodeRef; + +use super::DirectoryOpenPosition; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum DirectoryCachePosition { + Dot, + DotDot, + Index(usize), +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum DirectoryPosition { + Cache(DirectoryCachePosition), + Physical(u64), +} + +pub struct DirectoryFile { + pub(super) node: NodeRef, + pub(super) position: Cell, +} + +impl DirectoryFile { + pub(super) fn read_cached( + node: &NodeRef, + mut pos: DirectoryCachePosition, + entries: &mut [MaybeUninit], + ) -> Result<(usize, DirectoryCachePosition), Error> { + let directory = node.as_directory()?; + let children = directory.children.lock(); + let mut rem = entries.len(); + let mut off = 0; + + while rem != 0 { + let entry = match pos { + DirectoryCachePosition::Dot => { + pos = DirectoryCachePosition::DotDot; + Some((FixedString::from_str(".").unwrap(), node.clone())) + } + DirectoryCachePosition::DotDot => { + pos = DirectoryCachePosition::Index(0); + Some((FixedString::from_str("..").unwrap(), node.parent())) + } + DirectoryCachePosition::Index(index) if let Some((name, node)) = children.get(index) => { + pos = DirectoryCachePosition::Index(index + 1); + Some((FixedString::from_str(name)?, node.clone())) + } + DirectoryCachePosition::Index(_) => None + }; + + let Some((name, node)) = entry else { + break; + }; + + let ty = node.ty(); + + entries[off].write(DirectoryEntry { name, ty }); + + off += 1; + rem -= 1; + } + + Ok((off, pos)) + } + + pub(super) fn read_physical( + node: &NodeRef, + pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + node.read_directory(pos, entries) + } + + pub(super) fn read_entries( + &self, + entries: &mut [MaybeUninit], + ) -> Result { + let pos = self.position.get(); + let (count, pos) = match pos { + DirectoryPosition::Cache(pos) => { + let (count, pos) = DirectoryFile::read_cached(&self.node, pos, entries)?; + (count, DirectoryPosition::Cache(pos)) + } + DirectoryPosition::Physical(off) => { + let (count, pos) = DirectoryFile::read_physical(&self.node, off, entries)?; + (count, DirectoryPosition::Physical(pos)) + } + }; + self.position.set(pos); + + Ok(count) + } +} + +impl From for DirectoryPosition { + fn from(value: DirectoryOpenPosition) -> Self { + match value { + DirectoryOpenPosition::FromCache => Self::Cache(DirectoryCachePosition::Dot), + DirectoryOpenPosition::FromPhysical(off) => Self::Physical(off), + } + } +} diff --git a/lib/vfs/src/file/mod.rs b/lib/vfs/src/file/mod.rs new file mode 100644 index 00000000..d7de4f45 --- /dev/null +++ b/lib/vfs/src/file/mod.rs @@ -0,0 +1,500 @@ +use core::{any::Any, cell::Cell, fmt, mem::MaybeUninit}; + +use alloc::{boxed::Box, sync::Arc}; +use yggdrasil_abi::{ + error::Error, + io::{DirectoryEntry, OpenOptions, SeekFrom}, +}; + +use crate::{ + device::{BlockDeviceWrapper, CharDeviceWrapper}, + node::NodeRef, + traits::{Read, Seek, Write}, +}; + +use self::{ + device::{BlockFile, CharFile}, + directory::DirectoryFile, + regular::RegularFile, +}; + +mod device; +mod directory; +mod regular; + +pub enum DirectoryOpenPosition { + FromPhysical(u64), + FromCache, +} + +pub type FileRef = Arc; + +// TODO some kind of a mutex instead? +pub enum File { + Directory(DirectoryFile), + Regular(RegularFile), + Block(BlockFile), + Char(CharFile), +} + +impl File { + pub fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc { + let position = Cell::new(position.into()); + Arc::new(Self::Directory(DirectoryFile { node, position })) + } + + pub fn regular( + node: NodeRef, + position: u64, + instance_data: Option>, + opts: OpenOptions, + ) -> Arc { + let read = opts.contains(OpenOptions::READ); + let write = opts.contains(OpenOptions::WRITE); + + Arc::new(Self::Regular(RegularFile { + node, + read, + write, + instance_data, + position: Cell::new(position), + })) + } + + pub fn block( + device: BlockDeviceWrapper, + node: NodeRef, + opts: OpenOptions, + ) -> Result, Error> { + let read = opts.contains(OpenOptions::READ); + let write = opts.contains(OpenOptions::WRITE); + + if read && !device.is_readable() { + return Err(Error::InvalidOperation); + } + if write && !device.is_writable() { + return Err(Error::ReadOnly); + } + + Ok(Arc::new(Self::Block(BlockFile { + device, + node, + position: Cell::new(0), + read, + write, + }))) + } + + pub fn char( + device: CharDeviceWrapper, + node: NodeRef, + opts: OpenOptions, + ) -> Result, Error> { + let read = opts.contains(OpenOptions::READ); + let write = opts.contains(OpenOptions::WRITE); + + if read && !device.is_readable() { + return Err(Error::InvalidOperation); + } + if write && !device.is_writable() { + return Err(Error::ReadOnly); + } + + Ok(Arc::new(Self::Char(CharFile { + device, + node, + read, + write, + }))) + } + + pub fn read_dir(&self, entries: &mut [MaybeUninit]) -> Result { + match self { + Self::Directory(dir) => dir.read_entries(entries), + _ => Err(Error::NotADirectory), + } + } +} + +impl Read for File { + fn read(&self, buf: &mut [u8]) -> Result { + match self { + Self::Regular(file) => file.read(buf), + Self::Block(file) => file.read(buf), + Self::Char(file) => file.read(buf), + Self::Directory(_) => Err(Error::IsADirectory), + } + } +} + +impl Write for File { + fn write(&self, buf: &[u8]) -> Result { + match self { + Self::Regular(file) => file.write(buf), + Self::Block(file) => file.write(buf), + Self::Char(file) => file.write(buf), + Self::Directory(_) => Err(Error::IsADirectory), + } + } +} + +impl Seek for File { + fn tell(&self) -> Result { + match self { + Self::Regular(file) => Ok(file.position.get()), + Self::Block(file) => Ok(file.position.get()), + Self::Char(_) => Err(Error::InvalidOperation), + Self::Directory(_) => Err(Error::IsADirectory), + } + } + + fn seek(&self, from: SeekFrom) -> Result { + match self { + Self::Regular(file) => file.seek(from), + Self::Block(file) => file.seek(from), + Self::Char(_) => Err(Error::InvalidOperation), + Self::Directory(_) => Err(Error::IsADirectory), + } + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Regular(file) => f + .debug_struct("RegularFile") + .field("position", &file.position.get()) + .field("read", &file.read) + .field("write", &file.write) + .finish_non_exhaustive(), + Self::Block(file) => f + .debug_struct("BlockFile") + .field("position", &file.position.get()) + .field("read", &file.read) + .field("write", &file.write) + .finish_non_exhaustive(), + Self::Char(file) => f + .debug_struct("CharFile") + .field("read", &file.read) + .field("write", &file.write) + .finish_non_exhaustive(), + Self::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(), + } + } +} + +#[cfg(test)] +mod tests { + use core::{any::Any, cell::RefCell, mem::MaybeUninit, str::FromStr}; + use std::sync::Arc; + + use kernel_util::sync::IrqSafeSpinlock; + use yggdrasil_abi::{ + error::Error, + io::{DirectoryEntry, FileType, OpenOptions, SeekFrom}, + util::FixedString, + }; + + use crate::{ + device::{BlockDevice, CharDevice}, + file::DirectoryOpenPosition, + node::{CommonImpl, DirectoryImpl, Node, NodeRef, RegularImpl}, + traits::{Read, Seek, Write}, + }; + + #[test] + fn physical_dir_read() { + struct D { + entries: Vec<(String, NodeRef)>, + } + struct F; + + impl CommonImpl for D {} + impl DirectoryImpl for D { + fn open(&self, _node: &NodeRef) -> Result { + Ok(DirectoryOpenPosition::FromPhysical(0)) + } + + fn read_entries( + &self, + _node: &NodeRef, + pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + let pos = pos as usize; + if pos == self.entries.len() { + return Ok((0, pos as u64)); + } + + let count = core::cmp::min(entries.len(), self.entries.len() - pos); + for i in 0..count { + let (name, node) = &self.entries[i]; + let entry = DirectoryEntry { + name: FixedString::from_str(name)?, + ty: node.ty(), + }; + + entries[i].write(entry); + } + + Ok((count, (pos + count) as u64)) + } + } + + impl CommonImpl for F {} + impl RegularImpl for F {} + + let d = Node::directory(D { + entries: Vec::from_iter([ + ("f1".to_owned(), Node::regular(F)), + ("f2".to_owned(), Node::regular(F)), + ("f3".to_owned(), Node::regular(F)), + ]), + }); + + let f = d.open_directory().unwrap(); + + let mut entries = [MaybeUninit::uninit(); 16]; + let count = f.read_dir(&mut entries).unwrap(); + assert_eq!(count, 3); + + unsafe { + assert_eq!( + MaybeUninit::slice_assume_init_ref(&entries[..count]), + &[ + DirectoryEntry { + name: FixedString::from_str("f1").unwrap(), + ty: FileType::File, + }, + DirectoryEntry { + name: FixedString::from_str("f2").unwrap(), + ty: FileType::File, + }, + DirectoryEntry { + name: FixedString::from_str("f3").unwrap(), + ty: FileType::File + } + ] + ); + } + + let count = f.read_dir(&mut entries).unwrap(); + assert_eq!(count, 0); + } + + #[test] + fn cache_dir_read() { + struct D; + + impl CommonImpl for D {} + impl DirectoryImpl for D { + fn open(&self, _node: &NodeRef) -> Result { + Ok(DirectoryOpenPosition::FromCache) + } + } + + let d = Node::directory(D); + let child = Node::directory(D); + + d.add_child("child1", child).unwrap(); + + let f = d.open_directory().unwrap(); + + let mut entries = [MaybeUninit::uninit(); 16]; + let count = f.read_dir(&mut entries).unwrap(); + assert_eq!(count, 3); + unsafe { + assert_eq!( + MaybeUninit::slice_assume_init_ref(&entries[..count]), + &[ + DirectoryEntry { + name: FixedString::from_str(".").unwrap(), + ty: FileType::Directory + }, + DirectoryEntry { + name: FixedString::from_str("..").unwrap(), + ty: FileType::Directory + }, + DirectoryEntry { + name: FixedString::from_str("child1").unwrap(), + ty: FileType::Directory + } + ] + ); + } + + let count = f.read_dir(&mut entries).unwrap(); + assert_eq!(count, 0); + } + + #[test] + fn file_read_write() { + struct F { + data: Arc>>, + } + + impl CommonImpl for F { + fn size(&self, _node: &NodeRef) -> Result { + Ok(self.data.borrow().len()) + } + } + impl RegularImpl for F { + fn open( + &self, + _node: &NodeRef, + _opts: OpenOptions, + ) -> Result<(u64, Option>), Error> { + Ok((0, None)) + } + + fn read( + &self, + _node: &NodeRef, + _instance: Option<&Box>, + pos: u64, + buf: &mut [u8], + ) -> Result { + let pos = pos as usize; + let data = self.data.borrow(); + if pos >= data.len() { + return Ok(0); + } + let count = core::cmp::min(data.len() - pos, buf.len()); + buf[..count].copy_from_slice(&data[pos..pos + count]); + Ok(count) + } + + fn write( + &self, + _node: &NodeRef, + _instance: Option<&Box>, + pos: u64, + buf: &[u8], + ) -> Result { + let pos = pos as usize; + let mut data = self.data.borrow_mut(); + data.resize(pos + buf.len(), 0); + data[pos..pos + buf.len()].copy_from_slice(buf); + Ok(buf.len()) + } + } + + let data = Arc::new(RefCell::new(vec![])); + let node = Node::regular(F { data: data.clone() }); + let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap(); + let mut buf = [0; 512]; + + assert_eq!(&*data.borrow(), &[]); + assert_eq!(file.tell().unwrap(), 0); + + assert_eq!(file.write(b"Hello").unwrap(), 5); + assert_eq!(file.tell().unwrap(), 5); + assert_eq!(&*data.borrow(), b"Hello"); + + assert_eq!(file.seek(SeekFrom::End(-2)).unwrap(), 3); + assert_eq!(file.tell().unwrap(), 3); + + assert_eq!(file.write(b"123456").unwrap(), 6); + assert_eq!(file.tell().unwrap(), 9); + assert_eq!(&*data.borrow(), b"Hel123456"); + + assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2); + assert_eq!(file.read(&mut buf).unwrap(), 7); + assert_eq!(file.tell().unwrap(), 9); + assert_eq!(&buf[..7], b"l123456"); + } + + #[test] + fn block_device() { + struct B { + data: Arc>>, + } + + impl BlockDevice for B { + fn read(&self, pos: u64, buf: &mut [u8]) -> Result { + let data = self.data.lock(); + let pos = pos as usize; + if pos >= data.len() { + return Ok(0); + } + + let count = core::cmp::min(data.len() - pos, buf.len()); + buf[..count].copy_from_slice(&data[pos..pos + count]); + Ok(count) + } + + fn write(&self, pos: u64, buf: &[u8]) -> Result { + let mut data = self.data.lock(); + let pos = pos as usize; + if pos >= data.len() { + return Ok(0); + } + + let count = core::cmp::min(data.len() - pos, buf.len()); + data[pos..pos + count].copy_from_slice(&buf[..count]); + Ok(count) + } + + fn size(&self) -> Result { + Ok(self.data.lock().len()) + } + } + + let vec = vec![0; 1024]; + let state = Arc::new(IrqSafeSpinlock::new(vec)); + let data = state.clone(); + let dev = Box::leak(Box::new(B { data })); + let mut buf = [0; 512]; + + let node = Node::block(dev); + + let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap(); + + assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 1024); + assert_eq!(file.write(b"12345").unwrap(), 0); + assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0); + assert_eq!(file.write(b"12345").unwrap(), 5); + assert_eq!(&state.lock()[..6], b"12345\0"); + + assert_eq!(file.read(&mut buf).unwrap(), 512); + assert_eq!(buf, [0; 512]); + assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2); + assert_eq!(file.read(&mut buf[..8]).unwrap(), 8); + assert_eq!(&buf[..8], b"345\0\0\0\0\0"); + } + + #[test] + fn char_device() { + struct C; + + impl CharDevice for C { + fn read(&self, buf: &mut [u8]) -> Result { + buf.fill(b'@'); + Ok(buf.len()) + } + + fn is_writable(&self) -> bool { + false + } + } + + static DEV: C = C; + + let node = Node::char(&DEV); + let mut buf = [0; 512]; + + let err = node.open(OpenOptions::WRITE).unwrap_err(); + assert_eq!(err, Error::ReadOnly); + + let file = node.open(OpenOptions::READ).unwrap(); + assert_eq!(file.tell().unwrap_err(), Error::InvalidOperation); + assert_eq!( + file.seek(SeekFrom::Start(10)).unwrap_err(), + Error::InvalidOperation + ); + assert_eq!(file.read(&mut buf).unwrap(), 512); + assert_eq!(buf, [b'@'; 512]); + + assert_eq!(file.write(b"1234").unwrap_err(), Error::ReadOnly); + } +} diff --git a/lib/vfs/src/file/regular.rs b/lib/vfs/src/file/regular.rs new file mode 100644 index 00000000..b5e64798 --- /dev/null +++ b/lib/vfs/src/file/regular.rs @@ -0,0 +1,85 @@ +use alloc::boxed::Box; +use core::{any::Any, cell::Cell}; +use yggdrasil_abi::{error::Error, io::SeekFrom}; + +use crate::node::NodeRef; + +pub struct RegularFile { + pub(super) node: NodeRef, + pub(super) read: bool, + pub(super) write: bool, + pub(super) instance_data: Option>, + pub(super) position: Cell, +} + +impl RegularFile { + pub fn read(&self, buf: &mut [u8]) -> Result { + if !self.read { + return Err(Error::InvalidFile); + } + let reg = self.node.as_regular()?; + let pos = self.position.get(); + let count = reg.read(&self.node, self.instance_data.as_ref(), pos, buf)?; + self.position.set(pos + count as u64); + Ok(count) + } + + pub fn write(&self, buf: &[u8]) -> Result { + if !self.write { + return Err(Error::InvalidFile); + } + let reg = self.node.as_regular()?; + let pos = self.position.get(); + let count = reg.write(&self.node, self.instance_data.as_ref(), pos, buf)?; + self.position.set(pos + count as u64); + Ok(count) + } + + // TODO should seek beyond the end of a read-only file be allowed? + pub fn seek(&self, from: SeekFrom) -> Result { + let pos = self.position.get(); + let newpos = match from { + SeekFrom::Current(off) => { + let newpos = i64::try_from(pos).unwrap() + off; + if newpos < 0 { + return Err(Error::InvalidArgument); + } + newpos as u64 + } + SeekFrom::Start(pos) => pos, + SeekFrom::End(off) => { + let reg = self.node.as_regular()?; + let size = i64::try_from(reg.size(&self.node)?).unwrap(); + let newpos = size + off; + + if newpos < 0 { + return Err(Error::InvalidArgument); + } + + newpos as u64 + } + }; + + self.position.set(newpos); + Ok(newpos) + } +} + +impl Drop for RegularFile { + fn drop(&mut self) { + let reg = match self.node.as_regular() { + Ok(reg) => reg, + Err(err) => { + log::warn!( + "RegularFile::Drop: self.node.as_regular() failed: {:?}", + err + ); + return; + } + }; + + if let Err(err) = reg.close(&self.node, self.instance_data.as_ref()) { + log::warn!("RegularFile::Drop: close() failed: {:?}", err); + } + } +} diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index 0c9ac1ac..1d0d95dc 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -1 +1,17 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] +#![feature(trait_upcasting, if_let_guard, maybe_uninit_slice, trait_alias)] + +#[cfg(test)] +extern crate hosted_tests; + +extern crate alloc; + +pub(crate) mod device; +pub(crate) mod file; +pub(crate) mod node; +pub(crate) mod traits; + +pub use device::{BlockDevice, CharDevice}; +pub use file::{DirectoryOpenPosition, File, FileRef}; +pub use node::{impls, Node, NodeRef}; +pub use traits::{Read, Seek, Write}; diff --git a/lib/vfs/src/node/impls.rs b/lib/vfs/src/node/impls.rs new file mode 100644 index 00000000..e3a2f6d2 --- /dev/null +++ b/lib/vfs/src/node/impls.rs @@ -0,0 +1,294 @@ +use core::{any::Any, cell::RefCell, marker::PhantomData, str::FromStr}; + +use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; + +use yggdrasil_abi::{error::Error, io::OpenOptions}; + +use super::{CommonImpl, Node, NodeRef, RegularImpl}; + +pub trait SliceRead { + fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize; +} + +pub trait SliceWrite { + fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize; +} + +trait IntoInstanceData { + fn into_instance_data(&self) -> Vec; +} + +impl> SliceRead for T { + fn read_slice(&self, pos: usize, buf: &mut [u8]) -> usize { + let value = self.as_ref(); + if pos >= value.len() { + return 0; + } + let count = core::cmp::min(value.len() - pos, buf.len()); + buf[..count].copy_from_slice(&value[pos..pos + count]); + count + } +} + +impl SliceWrite for Vec { + fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize { + if offset + buf.len() > self.len() { + self.resize(offset + buf.len(), 0); + } + self[offset..offset + buf.len()].copy_from_slice(buf); + buf.len() + } +} + +impl IntoInstanceData for T { + fn into_instance_data(&self) -> Vec { + self.to_string().as_bytes().to_vec() + } +} + +pub trait ReadFn = Fn() -> Result; +pub trait WriteFn = Fn(T) -> Result<(), Error>; + +enum FnNodeData { + Read(Vec), + Write(RefCell>), +} + +pub struct ReadOnlyFnNode> { + read: R, + _pd: PhantomData, +} + +pub struct FnNode, W: WriteFn> { + read: R, + write: W, + _pd: PhantomData, +} + +impl + 'static> ReadOnlyFnNode { + pub fn new(read: R) -> NodeRef { + Node::regular(Self::new_impl(read)) + } + + pub const fn new_impl(read: R) -> Self { + Self { + read, + _pd: PhantomData, + } + } +} + +impl> CommonImpl for ReadOnlyFnNode { + fn size(&self, _node: &NodeRef) -> Result { + Ok(0) + } +} + +impl> RegularImpl for ReadOnlyFnNode { + fn open( + &self, + _node: &NodeRef, + opts: OpenOptions, + ) -> Result<(u64, Option>), Error> { + if opts.contains(OpenOptions::WRITE) { + return Err(Error::ReadOnly); + } + let t = (self.read)()?; + Ok((0, Some(Box::new(t.into_instance_data())))) + } + + fn read( + &self, + _node: &NodeRef, + instance: Option<&Box>, + pos: u64, + buf: &mut [u8], + ) -> Result { + let value = instance.unwrap().downcast_ref::>().unwrap(); + Ok(value.read_slice(pos as usize, buf)) + } + + fn write( + &self, + _node: &NodeRef, + _instance: Option<&Box>, + _pos: u64, + _buf: &[u8], + ) -> Result { + Err(Error::ReadOnly) + } +} + +// Read-write FnNode + +impl FnNodeData { + fn write() -> Self { + Self::Write(RefCell::new(Vec::new())) + } + + fn read(value: T) -> Self { + Self::Read(value.into_instance_data()) + } + + fn as_read(&self) -> Result<&Vec, Error> { + match self { + Self::Read(r) => Ok(r), + Self::Write(_) => Err(Error::InvalidOperation), + } + } + + fn as_write(&self) -> Result<&RefCell>, Error> { + match self { + Self::Write(w) => Ok(w), + Self::Read(_) => Err(Error::InvalidOperation), + } + } +} + +impl + 'static, W: WriteFn + 'static> + FnNode +{ + pub fn new(read: R, write: W) -> NodeRef { + Node::regular(Self::new_impl(read, write)) + } + + pub const fn new_impl(read: R, write: W) -> Self { + Self { + read, + write, + _pd: PhantomData, + } + } +} + +impl, W: WriteFn> CommonImpl for FnNode { + fn size(&self, _node: &NodeRef) -> Result { + Ok(0) + } +} + +impl, W: WriteFn> RegularImpl for FnNode { + fn open( + &self, + _node: &NodeRef, + opts: OpenOptions, + ) -> Result<(u64, Option>), Error> { + if opts.contains(OpenOptions::READ | OpenOptions::WRITE) { + Err(Error::InvalidOperation) + } else if opts.contains(OpenOptions::WRITE) { + Ok((0, Some(Box::new(FnNodeData::write())))) + } else if opts.contains(OpenOptions::READ) { + let t = (self.read)()?; + Ok((0, Some(Box::new(FnNodeData::read(t))))) + } else { + Err(Error::InvalidOperation) + } + } + + fn close(&self, _node: &NodeRef, instance: Option<&Box>) -> Result<(), Error> { + if let Ok(write) = instance + .unwrap() + .downcast_ref::() + .unwrap() + .as_write() + { + let write = write.borrow(); + // Flush write + let str = core::str::from_utf8(write.as_ref()) + .map_err(|_| Error::InvalidArgument)? + .trim(); + let t = T::from_str(str).map_err(|_| Error::InvalidArgument)?; + + (self.write)(t)?; + } + Ok(()) + } + + fn read( + &self, + _node: &NodeRef, + instance: Option<&Box>, + pos: u64, + buf: &mut [u8], + ) -> Result { + let instance = instance.unwrap().downcast_ref::().unwrap(); + Ok(instance.as_read()?.read_slice(pos as usize, buf)) + } + + fn write( + &self, + _node: &NodeRef, + instance: Option<&Box>, + pos: u64, + buf: &[u8], + ) -> Result { + let instance = instance.unwrap().downcast_ref::().unwrap(); + Ok(instance.as_write()?.borrow_mut().write_slice(pos as _, buf)) + } +} + +pub fn const_value_node(value: T) -> NodeRef { + ReadOnlyFnNode::new(move || Ok(value.clone())) +} + +pub fn value_node(value: T) -> NodeRef { + let rd_state = Arc::new(RefCell::new(value)); + let wr_state = rd_state.clone(); + + FnNode::new( + move || Ok(rd_state.borrow().clone()), + move |t| { + *wr_state.borrow_mut() = t; + Ok(()) + }, + ) +} + +#[cfg(test)] +mod tests { + use yggdrasil_abi::io::OpenOptions; + + use crate::{ + node::impls::{const_value_node, value_node}, + traits::{Read, Seek, Write}, + }; + + #[test] + fn read_only_fn_node() { + let node = const_value_node("abcdef"); + let file = node.open(OpenOptions::READ).unwrap(); + let mut buf = [0; 512]; + + assert_eq!(file.tell().unwrap(), 0); + + assert_eq!(file.read(&mut buf[..3]).unwrap(), 3); + assert_eq!(&buf[..3], b"abc"); + + assert_eq!(file.read(&mut buf).unwrap(), 3); + assert_eq!(&buf[..3], b"def"); + } + + #[test] + fn fn_node() { + let node = value_node(1234); + let mut buf = [0; 512]; + + // Try read the value + let file = node.open(OpenOptions::READ).unwrap(); + assert_eq!(file.tell().unwrap(), 0); + assert_eq!(file.read(&mut buf).unwrap(), 4); + assert_eq!(&buf[..4], b"1234"); + + // Try write the value + let file = node.open(OpenOptions::WRITE).unwrap(); + assert_eq!(file.tell().unwrap(), 0); + assert_eq!(file.write(b"654321").unwrap(), 6); + drop(file); + + // Try read the value again + let file = node.open(OpenOptions::READ).unwrap(); + assert_eq!(file.tell().unwrap(), 0); + assert_eq!(file.read(&mut buf).unwrap(), 6); + assert_eq!(&buf[..6], b"654321"); + } +} diff --git a/lib/vfs/src/node/mod.rs b/lib/vfs/src/node/mod.rs new file mode 100644 index 00000000..6f39003a --- /dev/null +++ b/lib/vfs/src/node/mod.rs @@ -0,0 +1,196 @@ +use core::any::Any; + +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use kernel_util::sync::IrqSafeSpinlock; +use yggdrasil_abi::{error::Error, io::FileType}; + +pub mod impls; +mod traits; + +// Node is implemented in the following modules +mod ops; +mod tree; + +pub use traits::{CommonImpl, DirectoryImpl, RegularImpl}; + +use crate::device::{BlockDevice, BlockDeviceWrapper, CharDevice, CharDeviceWrapper}; + +pub type NodeRef = Arc; + +pub(crate) struct DirectoryData { + pub(crate) imp: Box, + pub(crate) children: IrqSafeSpinlock>, +} + +enum NodeImpl { + Regular(Box), + Directory(DirectoryData), + Block(BlockDeviceWrapper), + Char(CharDeviceWrapper), +} + +pub struct Node { + data: NodeImpl, + parent: IrqSafeSpinlock>, +} + +impl Node { + pub fn directory(data: T) -> NodeRef { + let data = NodeImpl::Directory(DirectoryData { + imp: Box::new(data), + children: IrqSafeSpinlock::new(Vec::new()), + }); + Arc::new(Self { + data, + parent: IrqSafeSpinlock::new(None), + }) + } + + pub fn regular(data: T) -> NodeRef { + Arc::new(Self { + data: NodeImpl::Regular(Box::new(data)), + parent: IrqSafeSpinlock::new(None), + }) + } + + pub fn block(device: &'static dyn BlockDevice) -> NodeRef { + Arc::new(Self { + data: NodeImpl::Block(BlockDeviceWrapper(device)), + parent: IrqSafeSpinlock::new(None), + }) + } + + pub fn char(device: &'static dyn CharDevice) -> NodeRef { + Arc::new(Self { + data: NodeImpl::Char(CharDeviceWrapper(device)), + parent: IrqSafeSpinlock::new(None), + }) + } + + pub fn data_as_any(&self) -> &dyn Any { + match &self.data { + NodeImpl::Directory(dir) => dir.imp.as_any(), + NodeImpl::Regular(imp) => imp.as_any(), + NodeImpl::Block(w) => w.as_any(), + NodeImpl::Char(w) => w.as_any(), + } + } + + pub fn data_as_common(&self) -> &dyn CommonImpl { + match &self.data { + NodeImpl::Directory(dir) => dir.imp.as_ref(), + NodeImpl::Regular(imp) => imp.as_ref(), + NodeImpl::Block(w) => w, + NodeImpl::Char(w) => w, + } + } + + pub fn data_as_ref(&self) -> &T { + self.data_as_any().downcast_ref().unwrap() + } + + pub fn ty(&self) -> FileType { + match &self.data { + NodeImpl::Regular(_) => FileType::File, + NodeImpl::Directory(_) => FileType::Directory, + NodeImpl::Block(_) => FileType::Block, + NodeImpl::Char(_) => FileType::Char, + } + } + + pub fn is_directory(&self) -> bool { + matches!(&self.data, NodeImpl::Directory(_)) + } + + pub fn is_terminal(&self) -> bool { + if let NodeImpl::Char(dev) = &self.data { + dev.is_terminal() + } else { + false + } + } + + pub(crate) fn as_directory(&self) -> Result<&DirectoryData, Error> { + match &self.data { + NodeImpl::Directory(dir) => Ok(dir), + _ => Err(Error::InvalidFile), + } + } + + pub(crate) fn as_regular(&self) -> Result<&dyn RegularImpl, Error> { + match &self.data { + NodeImpl::Regular(imp) => Ok(imp.as_ref()), + _ => Err(Error::InvalidFile), + } + } +} + +#[cfg(test)] +mod tests { + use core::any::Any; + use std::sync::Arc; + + use super::{CommonImpl, DirectoryImpl, Node, RegularImpl}; + + struct DummyDirectory; + struct DummyFile; + + impl CommonImpl for DummyDirectory {} + impl DirectoryImpl for DummyDirectory {} + + impl CommonImpl for DummyFile {} + impl RegularImpl for DummyFile {} + + #[test] + fn dir_cache_add() { + let d1 = Node::directory(DummyDirectory); + let d2 = Node::directory(DummyDirectory); + let f1 = Node::regular(DummyFile); + + assert!(Arc::ptr_eq(&f1.parent(), &f1)); + assert_eq!(d1.children_len().unwrap(), 0); + + d1.add_child("f1", f1.clone()).unwrap(); + assert!(Arc::ptr_eq(&f1.parent(), &d1)); + assert_eq!(d1.children_len().unwrap(), 1); + + assert!(Arc::ptr_eq(&d2.parent(), &d2)); + d2.add_child("d1", d1.clone()).unwrap(); + assert!(Arc::ptr_eq(&f1.parent(), &d1)); + assert!(Arc::ptr_eq(&d1.parent(), &d2)); + assert_eq!(d1.children_len().unwrap(), 1); + assert_eq!(d2.children_len().unwrap(), 1); + } + + #[test] + fn in_mem_dir_size_coherence() { + let d = Node::directory(DummyDirectory); + + for i in 0..10 { + let name = format!("f{}", i); + let node = Node::regular(DummyFile); + + d.add_child(name, node).unwrap(); + assert_eq!(d.size().unwrap(), d.children_len().unwrap()); + } + } + + #[test] + fn data_any() { + struct AnyData { + value: u32, + } + + impl CommonImpl for AnyData { + fn as_any(&self) -> &dyn Any { + self + } + } + impl DirectoryImpl for AnyData {} + + let d = Node::directory(AnyData { value: 1234 }); + let r = d.data_as_ref::(); + + assert_eq!(r.value, 1234); + } +} diff --git a/lib/vfs/src/node/ops.rs b/lib/vfs/src/node/ops.rs new file mode 100644 index 00000000..934ad043 --- /dev/null +++ b/lib/vfs/src/node/ops.rs @@ -0,0 +1,47 @@ +use core::mem::MaybeUninit; + +use yggdrasil_abi::{ + error::Error, + io::{DirectoryEntry, OpenOptions}, +}; + +use crate::file::{File, FileRef}; + +use super::{Node, NodeImpl, NodeRef}; + +impl Node { + pub fn open(self: &NodeRef, opts: OpenOptions) -> Result { + match &self.data { + NodeImpl::Regular(imp) => { + let (pos, instance) = imp.open(self, opts)?; + Ok(File::regular(self.clone(), pos, instance, opts)) + } + NodeImpl::Block(dev) => File::block(dev.clone(), self.clone(), opts), + NodeImpl::Char(dev) => File::char(dev.clone(), self.clone(), opts), + // TODO: maybe merge open_directory and open? + NodeImpl::Directory(_) => todo!(), + } + } + + // Directory + + pub fn open_directory(self: &NodeRef) -> Result { + let dir = self.as_directory()?; + let pos = dir.imp.open(self)?; + Ok(File::directory(self.clone(), pos)) + } + + pub fn read_directory( + self: &NodeRef, + pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + self.as_directory()?.imp.read_entries(self, pos, entries) + } + + // Common + + pub fn size(self: &NodeRef) -> Result { + self.data_as_common().size(self) + } +} diff --git a/lib/vfs/src/node/traits.rs b/lib/vfs/src/node/traits.rs new file mode 100644 index 00000000..7081f49a --- /dev/null +++ b/lib/vfs/src/node/traits.rs @@ -0,0 +1,88 @@ +use alloc::boxed::Box; +use core::{any::Any, mem::MaybeUninit}; + +use yggdrasil_abi::{ + error::Error, + io::{DirectoryEntry, FileAttr, OpenOptions}, +}; + +use crate::file::DirectoryOpenPosition; + +use super::NodeRef; + +#[allow(unused)] +pub trait CommonImpl { + fn as_any(&self) -> &dyn Any { + unimplemented!(); + } + + fn metadata(&self, node: &NodeRef) -> Result { + Err(Error::NotImplemented) + } + fn size(&self, node: &NodeRef) -> Result { + // TODO this only works for dirs + if node.is_directory() { + node.children_len() + } else { + Err(Error::NotImplemented) + } + } +} + +#[allow(unused)] +pub trait RegularImpl: CommonImpl { + fn open( + &self, + node: &NodeRef, + opts: OpenOptions, + ) -> Result<(u64, Option>), Error> { + Err(Error::NotImplemented) + } + + fn close(&self, node: &NodeRef, instance: Option<&Box>) -> Result<(), Error> { + Ok(()) + } + + fn read( + &self, + node: &NodeRef, + instance: Option<&Box>, + pos: u64, + buf: &mut [u8], + ) -> Result { + Err(Error::NotImplemented) + } + fn write( + &self, + node: &NodeRef, + instance: Option<&Box>, + pos: u64, + buf: &[u8], + ) -> Result { + Err(Error::NotImplemented) + } +} + +#[allow(unused)] +pub trait DirectoryImpl: CommonImpl { + fn open(&self, node: &NodeRef) -> Result { + Err(Error::NotImplemented) + } + + fn read_entries( + &self, + node: &NodeRef, + pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + Err(Error::NotImplemented) + } + + fn lookup(&self, node: &NodeRef, name: &str) -> Result { + Err(Error::NotImplemented) + } + + fn len(&self, node: &NodeRef) -> Result { + Err(Error::NotImplemented) + } +} diff --git a/lib/vfs/src/node/tree.rs b/lib/vfs/src/node/tree.rs new file mode 100644 index 00000000..693585e8 --- /dev/null +++ b/lib/vfs/src/node/tree.rs @@ -0,0 +1,39 @@ +use yggdrasil_abi::error::Error; + +use alloc::string::String; + +use super::{Node, NodeRef}; + +impl Node { + pub fn parent(self: &NodeRef) -> NodeRef { + self.parent.lock().as_ref().unwrap_or(self).clone() + } + + pub fn children_len(&self) -> Result { + let directory = self.as_directory()?; + Ok(directory.children.lock().len()) + } + + pub fn add_child>( + self: &NodeRef, + name: S, + child: NodeRef, + ) -> Result<(), Error> { + let name = name.into(); + let directory = self.as_directory()?; + let mut children = directory.children.lock(); + + // TODO check if an entry already exists with such name + + // if children.contains_key(&name) { + // log::warn!("Directory cache already contains an entry: {:?}", name); + // return Err(Error::AlreadyExists); + // } + + assert!(child.parent.replace(Some(self.clone())).is_none()); + children.push((name, child)); + // children.insert(name, child); + + Ok(()) + } +} diff --git a/lib/vfs/src/traits.rs b/lib/vfs/src/traits.rs new file mode 100644 index 00000000..f5a7525a --- /dev/null +++ b/lib/vfs/src/traits.rs @@ -0,0 +1,17 @@ +use yggdrasil_abi::{error::Error, io::SeekFrom}; + +pub trait Read { + fn read(&self, buf: &mut [u8]) -> Result; +} + +pub trait Write { + fn write(&self, buf: &[u8]) -> Result; +} + +pub trait Seek { + fn seek(&self, from: SeekFrom) -> Result; + + fn tell(&self) -> Result { + self.seek(SeekFrom::Current(0)) + } +}