diff --git a/Cargo.lock b/Cargo.lock index 5bb038f..135cfbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -61,6 +72,26 @@ dependencies = [ "unsafe_unwrap", ] +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + [[package]] name = "init" version = "0.1.0" @@ -80,6 +111,12 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "libc" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" + [[package]] name = "libusr" version = "0.1.0" @@ -113,6 +150,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "osdev5" version = "0.1.0" @@ -193,3 +236,23 @@ name = "unsafe_unwrap" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "vfs" +version = "0.1.0" +dependencies = [ + "error", + "hashbrown", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/Cargo.toml b/Cargo.toml index bbf5076..a379754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [workspace] members = [ + "fs/vfs", "kernel", "libusr", "init", diff --git a/error/src/lib.rs b/error/src/lib.rs index 8ba16ca..0cce0d5 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -8,4 +8,5 @@ pub enum Errno { OutOfMemory, WouldBlock, AlreadyExists, + NotImplemented, } diff --git a/fs/vfs/Cargo.toml b/fs/vfs/Cargo.toml new file mode 100644 index 0000000..699e224 --- /dev/null +++ b/fs/vfs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "vfs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +error = { path = "../../error" } +hashbrown = "0.11.*" diff --git a/fs/vfs/src/fs.rs b/fs/vfs/src/fs.rs new file mode 100644 index 0000000..40f6f2c --- /dev/null +++ b/fs/vfs/src/fs.rs @@ -0,0 +1,8 @@ +use crate::{VnodeKind, VnodeRef}; +use alloc::rc::Rc; +use error::Errno; + +pub trait Filesystem { + fn root(self: Rc) -> Result; + fn create_node(self: Rc, name: &str, kind: VnodeKind) -> Result; +} diff --git a/fs/vfs/src/lib.rs b/fs/vfs/src/lib.rs new file mode 100644 index 0000000..fe30b69 --- /dev/null +++ b/fs/vfs/src/lib.rs @@ -0,0 +1,10 @@ +#![no_std] + +extern crate alloc; + +pub mod fs; +pub use fs::Filesystem; +pub mod stat; +pub use stat::FileMode; +pub mod node; +pub use node::{VnodeRef, Vnode, VnodeKind, VnodeImpl}; diff --git a/fs/vfs/src/node.rs b/fs/vfs/src/node.rs new file mode 100644 index 0000000..56e723f --- /dev/null +++ b/fs/vfs/src/node.rs @@ -0,0 +1,250 @@ +use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec}; +use core::cell::{RefCell, RefMut}; +use crate::{Filesystem, FileMode}; +use error::Errno; + +pub type VnodeRef = Rc; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum VnodeKind { + Directory, + Regular, +} + +pub struct TreeNode { + parent: Option, + children: Vec, +} + +pub struct VnodeData { + // Filesystem itself + pub fs: Rc, + // Something like "inode" struct + "ops" table + pub node: Box, +} + +pub struct VnodeProps { + mode: FileMode, +} + +pub struct Vnode { + name: String, + tree: RefCell, + props: RefCell, + + kind: VnodeKind, + + pub data: RefCell>, +} + +pub trait VnodeImpl { + fn create(&mut self, at: VnodeRef, node: VnodeRef) -> Result<(), Errno>; + fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno>; +} + +impl Vnode { + pub fn new(name: &str, kind: VnodeKind) -> VnodeRef { + Rc::new(Self { + name: name.to_owned(), + kind, + props: RefCell::new(VnodeProps { + mode: FileMode::empty(), + }), + tree: RefCell::new(TreeNode { + parent: None, + children: Vec::new(), + }), + data: RefCell::new(None), + }) + } + + pub fn set_data(&self, data: VnodeData) { + *self.data.borrow_mut() = Some(data); + } + + pub fn data(&self) -> RefMut> { + self.data.borrow_mut() + } + + // Tree operations + + fn attach(self: &VnodeRef, child: VnodeRef) { + let parent_clone = self.clone(); + let mut parent_borrow = self.tree.borrow_mut(); + assert!(child + .tree + .borrow_mut() + .parent + .replace(parent_clone) + .is_none()); + parent_borrow.children.push(child); + } + + fn detach(self: &VnodeRef) { + let mut self_borrow = self.tree.borrow_mut(); + let parent = self_borrow.parent.take().unwrap(); + let mut parent_borrow = parent.tree.borrow_mut(); + let index = parent_borrow + .children + .iter() + .position(|it| Rc::ptr_eq(&it, self)) + .unwrap(); + parent_borrow.children.remove(index); + } + + pub fn parent(self: &VnodeRef) -> VnodeRef { + self.tree.borrow().parent.as_ref().unwrap_or(self).clone() + } + + pub fn lookup(self: &VnodeRef, name: &str) -> Option { + self.tree + .borrow() + .children + .iter() + .find(|e| e.name == name) + .cloned() + } + + pub fn mkdir(self: &VnodeRef, name: &str, mode: FileMode) -> Result { + if self.kind != VnodeKind::Directory { + return Err(Errno::NotADirectory); + } + + if let Some(ref mut data) = *self.data.borrow_mut() { + let vnode = data.fs.clone().create_node(name, VnodeKind::Directory)?; + + vnode.props.borrow_mut().mode = mode; + + if let Err(err) = data.node.create(self.clone(), vnode.clone()) { + if err != Errno::NotImplemented { + return Err(err); + } + } + + self.attach(vnode.clone()); + Ok(vnode) + } else { + Err(Errno::NotImplemented) + } + } + + pub fn unlink(self: &VnodeRef, name: &str) -> Result<(), Errno> { + if self.kind != VnodeKind::Directory { + return Err(Errno::NotADirectory); + } + + if let Some(ref mut data) = *self.data.borrow_mut() { + let vnode = self.lookup(name).ok_or(Errno::DoesNotExist)?; + + if let Err(err) = data.node.remove(self.clone(), name) { + if err != Errno::NotImplemented { + return Err(err); + } + } + + vnode.detach(); + Ok(()) + } else { + Err(Errno::NotImplemented) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Filesystem; + + 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) + } + } + + 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_parent() { + let root = Vnode::new("", VnodeKind::Directory); + let node = Vnode::new("dir0", VnodeKind::Directory); + + root.attach(node.clone()); + + assert!(Rc::ptr_eq(&root.parent(), &root)); + assert!(Rc::ptr_eq(&node.parent(), &root)); + } + + #[test] + fn test_mkdir_unlink() { + let fs = Rc::new(DummyFs {}); + let root = Vnode::new("", VnodeKind::Directory); + + root.set_data(VnodeData { + node: Box::new(DummyInode {}), + fs: fs.clone(), + }); + + let node = root.mkdir("test", FileMode::default_dir()).unwrap(); + + assert_eq!(node.props.borrow().mode, FileMode::default_dir()); + assert!(Rc::ptr_eq(&node, &root.lookup("test").unwrap())); + assert!(node.data.borrow().is_some()); + + root.unlink("test").unwrap(); + + assert!(root.lookup("test").is_none()); + } + + #[test] + fn test_lookup_attach_detach() { + let root = Vnode::new("", VnodeKind::Directory); + let dir0 = Vnode::new("dir0", VnodeKind::Directory); + let dir1 = Vnode::new("dir1", VnodeKind::Directory); + + root.attach(dir0.clone()); + root.attach(dir1.clone()); + + assert!(Rc::ptr_eq(&dir0, &root.lookup("dir0").unwrap())); + assert!(Rc::ptr_eq(&dir1, &root.lookup("dir1").unwrap())); + assert!(Rc::ptr_eq( + &root, + dir0.tree.borrow().parent.as_ref().unwrap() + )); + assert!(Rc::ptr_eq( + &root, + dir1.tree.borrow().parent.as_ref().unwrap() + )); + assert!(root.lookup("dir2").is_none()); + + dir0.detach(); + + assert!(Rc::ptr_eq(&dir1, &root.lookup("dir1").unwrap())); + assert!(Rc::ptr_eq( + &root, + dir1.tree.borrow().parent.as_ref().unwrap() + )); + assert!(dir0.tree.borrow().parent.is_none()); + assert!(root.lookup("dir0").is_none()); + assert!(root.lookup("dir2").is_none()); + } +} diff --git a/fs/vfs/src/stat.rs b/fs/vfs/src/stat.rs new file mode 100644 index 0000000..35526aa --- /dev/null +++ b/fs/vfs/src/stat.rs @@ -0,0 +1,86 @@ +use core::fmt; + +#[derive(Clone, Copy, PartialEq)] +#[repr(transparent)] +pub struct FileMode(u16); + +impl FileMode { + const USER_READ: u16 = 1 << 8; + const USER_WRITE: u16 = 1 << 7; + const USER_EXEC: u16 = 1 << 6; + const GROUP_READ: u16 = 1 << 5; + const GROUP_WRITE: u16 = 1 << 4; + const GROUP_EXEC: u16 = 1 << 3; + const OTHER_READ: u16 = 1 << 2; + const OTHER_WRITE: u16 = 1 << 1; + const OTHER_EXEC: u16 = 1 << 0; + + pub const fn empty() -> Self { + Self(0) + } + + pub const fn default_dir() -> Self { + Self(0o755) + } + + pub const fn default_reg() -> Self { + Self(0o644) + } +} + +impl fmt::Debug for FileMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FileMode (")?; + + if self.0 & Self::USER_READ != 0 { + write!(f, "r")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::USER_WRITE != 0 { + write!(f, "w")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::USER_EXEC != 0 { + write!(f, "x")?; + } else { + write!(f, "-")?; + } + + if self.0 & Self::GROUP_READ != 0 { + write!(f, "r")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::GROUP_WRITE != 0 { + write!(f, "w")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::GROUP_EXEC != 0 { + write!(f, "x")?; + } else { + write!(f, "-")?; + } + + if self.0 & Self::OTHER_READ != 0 { + write!(f, "r")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::OTHER_WRITE != 0 { + write!(f, "w")?; + } else { + write!(f, "-")?; + } + if self.0 & Self::OTHER_EXEC != 0 { + write!(f, "x")?; + } else { + write!(f, "-")?; + } + + write!(f, ")")?; + Ok(()) + } +}