feat(vfs): read functionality
This commit is contained in:
@@ -5,6 +5,7 @@ pub enum Errno {
|
||||
InvalidArgument,
|
||||
DoesNotExist,
|
||||
NotADirectory,
|
||||
IsADirectory,
|
||||
OutOfMemory,
|
||||
WouldBlock,
|
||||
AlreadyExists,
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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());
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user