feat(vfs): read functionality

This commit is contained in:
2021-10-25 13:34:27 +03:00
parent 65fc4bc4fe
commit 0dbadd52d0
6 changed files with 438 additions and 3 deletions
+1
View File
@@ -5,6 +5,7 @@ pub enum Errno {
InvalidArgument,
DoesNotExist,
NotADirectory,
IsADirectory,
OutOfMemory,
WouldBlock,
AlreadyExists,
+125
View File
@@ -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<usize, Errno> {
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<usize, Errno> {
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<usize, Errno> {
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<usize, Errno> {
Err(Errno::NotImplemented)
}
}
impl Filesystem for DummyFs {
fn root(self: Rc<Self>) -> Result<VnodeRef, Errno> {
todo!()
}
fn create_node(self: Rc<Self>, name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno> {
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]);
}
}
}
+221
View File
@@ -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<VnodeRef, Errno> {
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<VnodeRef>, mut path: &str) -> Result<VnodeRef, Errno> {
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<VnodeRef>, 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<usize, Errno> {
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<usize, Errno> {
Err(Errno::NotImplemented)
}
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
Err(Errno::NotImplemented)
}
}
impl Filesystem for DummyFs {
fn root(self: Rc<Self>) -> Result<VnodeRef, Errno> {
todo!()
}
fn create_node(self: Rc<Self>, name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno> {
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
);
}
}
+7 -1
View File
@@ -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;
+69 -2
View File
@@ -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<Vnode>;
@@ -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<usize, Errno>;
fn close(&mut self, node: VnodeRef) -> Result<(), Errno>;
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno>;
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>;
}
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<File, Errno> {
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<usize, Errno> {
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<usize, Errno> {
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<usize, Errno> {
Err(Errno::NotImplemented)
}
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
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());
+15
View File
@@ -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, "")
}
}