diff --git a/error/src/lib.rs b/error/src/lib.rs index 0cce0d5..45a4060 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -5,6 +5,7 @@ pub enum Errno { InvalidArgument, DoesNotExist, NotADirectory, + IsADirectory, OutOfMemory, WouldBlock, AlreadyExists, diff --git a/fs/vfs/src/file.rs b/fs/vfs/src/file.rs new file mode 100644 index 0000000..4388275 --- /dev/null +++ b/fs/vfs/src/file.rs @@ -0,0 +1,125 @@ +use crate::VnodeRef; +use alloc::rc::Rc; +use core::cell::RefCell; +use error::Errno; + +struct NormalFile { + vnode: VnodeRef, + pos: usize, +} + +enum FileInner { + Normal(NormalFile), +} + +pub struct File { + inner: FileInner, +} + +impl File { + pub fn normal(vnode: VnodeRef, pos: usize) -> Self { + Self { + inner: FileInner::Normal(NormalFile { vnode, pos }), + } + } + + pub fn read(&mut self, data: &mut [u8]) -> Result { + match &mut self.inner { + FileInner::Normal(inner) => { + let count = inner.vnode.read(inner.pos, data)?; + inner.pos += count; + Ok(count) + } + _ => unimplemented!(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{node::VnodeData, Filesystem, Ioctx, Vnode, VnodeImpl, VnodeKind, VnodeRef}; + use alloc::boxed::Box; + + pub struct DummyInode; + pub struct DummyFs; + + impl VnodeImpl for DummyInode { + fn create(&mut self, _at: VnodeRef, _node: VnodeRef) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn open(&mut self, _node: VnodeRef) -> Result { + Ok(0) + } + + fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result { + let len = 123; + if pos >= len { + return Ok(0); + } + let rem = core::cmp::min(len - pos, data.len()); + for i in 0..rem { + data[i] = ((pos + i) & 0xFF) as u8; + } + Ok(rem) + } + + fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { + Err(Errno::NotImplemented) + } + } + + impl Filesystem for DummyFs { + fn root(self: Rc) -> Result { + todo!() + } + + fn create_node(self: Rc, name: &str, kind: VnodeKind) -> Result { + let node = Vnode::new(name, kind); + node.set_data(VnodeData { + node: Box::new(DummyInode {}), + fs: self, + }); + Ok(node) + } + } + + #[test] + fn test_normal_read() { + let fs = Rc::new(DummyFs {}); + let node = fs.create_node("", VnodeKind::Regular).unwrap(); + let mut file = node.open().unwrap(); + + match &file.inner { + FileInner::Normal(inner) => { + assert!(Rc::ptr_eq(&inner.vnode, &node)); + assert_eq!(inner.pos, 0); + } + _ => panic!("Invalid file.inner"), + } + + let mut buf = [0u8; 4096]; + + assert_eq!(file.read(&mut buf[0..32]).unwrap(), 32); + for i in 0..32 { + assert_eq!((i & 0xFF) as u8, buf[i]); + } + assert_eq!(file.read(&mut buf[0..64]).unwrap(), 64); + for i in 0..64 { + assert_eq!(((i + 32) & 0xFF) as u8, buf[i]); + } + assert_eq!(file.read(&mut buf[0..64]).unwrap(), 27); + for i in 0..27 { + assert_eq!(((i + 96) & 0xFF) as u8, buf[i]); + } + } +} diff --git a/fs/vfs/src/ioctx.rs b/fs/vfs/src/ioctx.rs new file mode 100644 index 0000000..eca7810 --- /dev/null +++ b/fs/vfs/src/ioctx.rs @@ -0,0 +1,221 @@ +use crate::{util, FileMode, VnodeRef}; +use error::Errno; + +pub struct Ioctx { + root: VnodeRef, + cwd: VnodeRef, +} + +impl Ioctx { + pub fn new(root: VnodeRef) -> Self { + Self { + cwd: root.clone(), + root, + } + } + + fn _find(&self, mut at: VnodeRef, path: &str) -> Result { + let mut element; + let mut rest = path; + + loop { + (element, rest) = util::path_component_left(rest); + + if !at.is_directory() { + return Err(Errno::NotADirectory); + } + + match element { + ".." => { + at = at.parent(); + } + "." => {} + _ => break, + } + } + + if element.is_empty() && rest.is_empty() { + return Ok(at); + } + assert!(!element.is_empty()); + + let node = at.lookup(element).ok_or(Errno::DoesNotExist)?; + + if rest.is_empty() { + Ok(node) + } else { + self._find(node, rest) + } + } + + pub fn find(&self, at: Option, mut path: &str) -> Result { + let at = if path.starts_with('/') { + path = path.trim_start_matches('/'); + self.root.clone() + } else if let Some(at) = at { + at + } else { + self.cwd.clone() + }; + + self._find(at, path) + } + + pub fn mkdir(&self, at: Option, path: &str, mode: FileMode) -> Result<(), Errno> { + let (parent, name) = util::path_component_right(path); + let parent_node = self.find(at, parent)?; + + parent_node.mkdir(name, mode).map(|_| ()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{node::VnodeData, Filesystem, Vnode, VnodeImpl, VnodeKind}; + use alloc::{boxed::Box, rc::Rc}; + + pub struct DummyInode; + pub struct DummyFs; + + impl VnodeImpl for DummyInode { + fn create(&mut self, _at: VnodeRef, _node: VnodeRef) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn open(&mut self, _node: VnodeRef) -> Result { + Err(Errno::NotImplemented) + } + + fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { + Err(Errno::NotImplemented) + } + + fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { + Err(Errno::NotImplemented) + } + } + + impl Filesystem for DummyFs { + fn root(self: Rc) -> Result { + todo!() + } + + fn create_node(self: Rc, name: &str, kind: VnodeKind) -> Result { + let node = Vnode::new(name, kind); + node.set_data(VnodeData { + node: Box::new(DummyInode {}), + fs: self, + }); + Ok(node) + } + } + + #[test] + fn test_find_existing_absolute() { + let root = Vnode::new("", VnodeKind::Directory); + let d0 = Vnode::new("dir0", VnodeKind::Directory); + let d1 = Vnode::new("dir1", VnodeKind::Directory); + let d0d0 = Vnode::new("dir0", VnodeKind::Directory); + let d0f0 = Vnode::new("file0", VnodeKind::Regular); + let d1f0 = Vnode::new("file0", VnodeKind::Regular); + + root.attach(d0.clone()); + root.attach(d1.clone()); + d0.attach(d0d0.clone()); + d0.attach(d0f0.clone()); + d1.attach(d1f0.clone()); + + let ioctx = Ioctx::new(root.clone()); + + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/.").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/./.").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/.///.").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/..").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/../").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/../.").unwrap())); + assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/../..").unwrap())); + + assert!(Rc::ptr_eq(&d0, &ioctx.find(None, "/dir0").unwrap())); + assert!(Rc::ptr_eq(&d1, &ioctx.find(None, "/dir1").unwrap())); + assert!(Rc::ptr_eq(&d0, &ioctx.find(None, "/dir1/../dir0").unwrap())); + assert!(Rc::ptr_eq( + &d1, + &ioctx.find(None, "/dir1/../dir0/./../../.././dir1").unwrap() + )); + + assert!(Rc::ptr_eq(&d0d0, &ioctx.find(None, "/dir0/dir0").unwrap())); + assert!(Rc::ptr_eq( + &d0d0, + &ioctx.find(None, "/dir0/dir0/.").unwrap() + )); + assert!(Rc::ptr_eq(&d0, &ioctx.find(None, "/dir0/dir0/..").unwrap())); + assert!(Rc::ptr_eq( + &d0, + &ioctx.find(None, "/dir0/dir0/../").unwrap() + )); + assert!(Rc::ptr_eq( + &d0, + &ioctx.find(None, "/dir0/dir0/../.").unwrap() + )); + + assert!(Rc::ptr_eq(&d0f0, &ioctx.find(None, "/dir0/file0").unwrap())); + assert!(Rc::ptr_eq( + &d0f0, + &ioctx.find(None, "/dir1/../dir0/./file0").unwrap() + )); + } + + #[test] + fn test_find_rejects_file_dots() { + let root = Vnode::new("", VnodeKind::Directory); + let d0 = Vnode::new("dir0", VnodeKind::Directory); + let d0f0 = Vnode::new("file0", VnodeKind::Regular); + + root.attach(d0.clone()); + d0.attach(d0f0.clone()); + + let ioctx = Ioctx::new(root.clone()); + + assert_eq!( + ioctx.find(None, "/dir0/file0/.").unwrap_err(), + Errno::NotADirectory + ); + assert_eq!( + ioctx.find(None, "/dir0/file0/..").unwrap_err(), + Errno::NotADirectory + ); + + // TODO handle this case + // assert_eq!(ioctx.find(None, "/dir0/file0/").unwrap_err(), Errno::NotADirectory); + } + + #[test] + fn test_mkdir() { + let fs = Rc::new(DummyFs {}); + let root = Vnode::new("", VnodeKind::Directory); + let ioctx = Ioctx::new(root.clone()); + + root.set_data(VnodeData { + node: Box::new(DummyInode {}), + fs: fs.clone(), + }); + + assert!(ioctx.mkdir(None, "/dir0", FileMode::default_dir()).is_ok()); + assert_eq!( + ioctx + .mkdir(None, "/dir0", FileMode::default_dir()) + .unwrap_err(), + Errno::AlreadyExists + ); + } +} diff --git a/fs/vfs/src/lib.rs b/fs/vfs/src/lib.rs index fe30b69..be89b53 100644 --- a/fs/vfs/src/lib.rs +++ b/fs/vfs/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(destructuring_assignment)] #![no_std] extern crate alloc; @@ -7,4 +8,9 @@ pub use fs::Filesystem; pub mod stat; pub use stat::FileMode; pub mod node; -pub use node::{VnodeRef, Vnode, VnodeKind, VnodeImpl}; +pub use node::{Vnode, VnodeImpl, VnodeKind, VnodeRef}; +pub mod ioctx; +pub use ioctx::Ioctx; +pub mod file; +pub use file::File; +pub mod util; diff --git a/fs/vfs/src/node.rs b/fs/vfs/src/node.rs index 56e723f..bb886d8 100644 --- a/fs/vfs/src/node.rs +++ b/fs/vfs/src/node.rs @@ -1,6 +1,7 @@ +use crate::{File, FileMode, Filesystem}; use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec}; use core::cell::{RefCell, RefMut}; -use crate::{Filesystem, FileMode}; +use core::fmt; use error::Errno; pub type VnodeRef = Rc; @@ -40,6 +41,12 @@ pub struct Vnode { pub trait VnodeImpl { fn create(&mut self, at: VnodeRef, node: VnodeRef) -> Result<(), Errno>; fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno>; + + fn open(&mut self, node: VnodeRef /* TODO open mode */) -> Result; + fn close(&mut self, node: VnodeRef) -> Result<(), Errno>; + + fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result; + fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result; } impl Vnode { @@ -66,9 +73,13 @@ impl Vnode { self.data.borrow_mut() } + pub fn is_directory(&self) -> bool { + self.kind == VnodeKind::Directory + } + // Tree operations - fn attach(self: &VnodeRef, child: VnodeRef) { + pub fn attach(self: &VnodeRef, child: VnodeRef) { let parent_clone = self.clone(); let mut parent_borrow = self.tree.borrow_mut(); assert!(child @@ -110,6 +121,10 @@ impl Vnode { return Err(Errno::NotADirectory); } + if self.lookup(name).is_some() { + return Err(Errno::AlreadyExists); + } + if let Some(ref mut data) = *self.data.borrow_mut() { let vnode = data.fs.clone().create_node(name, VnodeKind::Directory)?; @@ -148,6 +163,37 @@ impl Vnode { Err(Errno::NotImplemented) } } + + pub fn open(self: &VnodeRef) -> Result { + if self.kind != VnodeKind::Regular { + return Err(Errno::IsADirectory); + } + + if let Some(ref mut data) = *self.data.borrow_mut() { + let pos = data.node.open(self.clone())?; + Ok(File::normal(self.clone(), pos)) + } else { + Err(Errno::NotImplemented) + } + } + + pub fn read(self: &VnodeRef, pos: usize, buf: &mut [u8]) -> Result { + if self.kind != VnodeKind::Regular { + return Err(Errno::IsADirectory); + } + + if let Some(ref mut data) = *self.data.borrow_mut() { + data.node.read(self.clone(), pos, buf) + } else { + Err(Errno::NotImplemented) + } + } +} + +impl fmt::Debug for Vnode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Vnode({:?})", self.name) + } } #[cfg(test)] @@ -166,6 +212,22 @@ mod tests { fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { Err(Errno::NotImplemented) } + + fn open(&mut self, _node: VnodeRef) -> Result { + Err(Errno::NotImplemented) + } + + fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { + Err(Errno::NotImplemented) + } + + fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { + Err(Errno::NotImplemented) + } + + fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { + Err(Errno::NotImplemented) + } } impl Filesystem for DummyFs { @@ -206,6 +268,11 @@ mod tests { let node = root.mkdir("test", FileMode::default_dir()).unwrap(); + assert_eq!( + root.mkdir("test", FileMode::default_dir()).unwrap_err(), + Errno::AlreadyExists + ); + assert_eq!(node.props.borrow().mode, FileMode::default_dir()); assert!(Rc::ptr_eq(&node, &root.lookup("test").unwrap())); assert!(node.data.borrow().is_some()); diff --git a/fs/vfs/src/util.rs b/fs/vfs/src/util.rs new file mode 100644 index 0000000..ba03dd5 --- /dev/null +++ b/fs/vfs/src/util.rs @@ -0,0 +1,15 @@ +pub fn path_component_left(path: &str) -> (&str, &str) { + if let Some((left, right)) = path.split_once('/') { + (left, right.trim_start_matches('/')) + } else { + (path, "") + } +} + +pub fn path_component_right(path: &str) -> (&str, &str) { + if let Some((left, right)) = path.rsplit_once('/') { + (left.trim_end_matches('/'), right) + } else { + (path, "") + } +}