AA
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "vfs"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
error = { path = "../../error" }
|
||||
spin = "*"
|
||||
@@ -0,0 +1,187 @@
|
||||
use crate::{path_element, Node, NodeRef};
|
||||
use alloc::rc::Rc;
|
||||
use error::Errno;
|
||||
|
||||
pub struct Ioctx {
|
||||
pub root: NodeRef,
|
||||
pub cwd: NodeRef,
|
||||
}
|
||||
|
||||
impl Ioctx {
|
||||
fn lookup_or_load(parent: NodeRef, name: &[u8]) -> Result<NodeRef, Errno> {
|
||||
let p = parent.borrow_mut();
|
||||
|
||||
if !p.is_directory() {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
|
||||
if let Some(node) = p.children().find(|&node| node.borrow().name() == name) {
|
||||
return Ok(node.clone());
|
||||
}
|
||||
|
||||
if let Some(ops) = p.ops.as_ref() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
Err(Errno::DoesNotExist)
|
||||
}
|
||||
|
||||
fn _find(&self, mut at: NodeRef, path: &[u8]) -> Result<NodeRef, Errno> {
|
||||
let mut child_path: &[u8];
|
||||
let mut element_name: &[u8];
|
||||
|
||||
if path.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
|
||||
child_path = path;
|
||||
loop {
|
||||
let r = path_element(child_path);
|
||||
element_name = r.0;
|
||||
child_path = r.1;
|
||||
|
||||
match element_name {
|
||||
b"." => {
|
||||
if child_path.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
}
|
||||
b".." => {
|
||||
let parent = at.borrow().parent().unwrap_or(at.clone());
|
||||
at = parent;
|
||||
if child_path.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
if element_name.is_empty() && child_path.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
assert!(!element_name.is_empty());
|
||||
let child = Self::lookup_or_load(at, element_name)?;
|
||||
|
||||
if child_path.is_empty() {
|
||||
Ok(child)
|
||||
} else {
|
||||
self._find(child, child_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&self, at: Option<NodeRef>, mut path: &[u8]) -> Result<NodeRef, Errno> {
|
||||
if path.is_empty() {
|
||||
return at.ok_or(Errno::DoesNotExist);
|
||||
}
|
||||
|
||||
let at = if path[0] == b'/' {
|
||||
let index = path
|
||||
.iter()
|
||||
.position(|&x| x != b'/')
|
||||
.unwrap_or(path.len() - 1);
|
||||
path = &path[index..];
|
||||
self.root.clone()
|
||||
} else if let Some(node) = at {
|
||||
node
|
||||
} else {
|
||||
self.cwd.clone()
|
||||
};
|
||||
|
||||
self._find(at, path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Node;
|
||||
|
||||
#[test]
|
||||
fn ioctx_find_at() {
|
||||
let r = Node::directory(b"");
|
||||
let d0 = Node::directory(b"dir0");
|
||||
let d1 = Node::directory(b"dir1");
|
||||
let d0d0 = Node::directory(b"dir0");
|
||||
let d0f0 = Node::file(b"file0");
|
||||
|
||||
Node::attach(r.clone(), d0.clone());
|
||||
Node::attach(r.clone(), d1.clone());
|
||||
Node::attach(d0.clone(), d0d0.clone());
|
||||
Node::attach(d0.clone(), d0f0.clone());
|
||||
|
||||
let ioctx = Ioctx {
|
||||
root: r.clone(),
|
||||
cwd: r.clone(),
|
||||
};
|
||||
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(r.clone()), b"dir0").as_ref().unwrap(),
|
||||
&d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"dir0").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(r.clone()), b"dir0/dir0").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"file0").as_ref().unwrap(),
|
||||
&d0f0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(r.clone()), b"dir0/file0").as_ref().unwrap(),
|
||||
&d0f0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"../dir1").as_ref().unwrap(),
|
||||
&d1
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"./dir0").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/.").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/./").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/").as_ref().unwrap(),
|
||||
&d0d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/..").as_ref().unwrap(),
|
||||
&d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/../..").as_ref().unwrap(),
|
||||
&r
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././dir0/../../../").as_ref().unwrap(),
|
||||
&r
|
||||
));
|
||||
|
||||
// TODO make these illegal
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././file0/..").as_ref().unwrap(),
|
||||
&d0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././file0/").as_ref().unwrap(),
|
||||
&d0f0
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
ioctx.find(Some(d0.clone()), b"././file0/.").as_ref().unwrap(),
|
||||
&d0f0
|
||||
));
|
||||
|
||||
ioctx.find(Some(r.clone()), b"dir0/dir1").expect_err("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#![no_std]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
use error::Errno;
|
||||
|
||||
pub mod node;
|
||||
mod util;
|
||||
pub use node::{Node, NodeOperations, NodeRef, NodeType};
|
||||
mod ioctx;
|
||||
pub use ioctx::Ioctx;
|
||||
|
||||
pub fn path_element(path: &[u8]) -> (&[u8], &[u8]) {
|
||||
if let Some(mut index) = path.iter().position(|&x| x == b'/') {
|
||||
let elem = &path[..index];
|
||||
while index < path.len() && path[index] == b'/' {
|
||||
index += 1;
|
||||
}
|
||||
(elem, &path[index..])
|
||||
} else {
|
||||
(path, b"")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
use crate::util::{iter::LockedIterator, FixedStr};
|
||||
use alloc::{boxed::Box, rc::Rc, vec::Vec};
|
||||
use core::any::Any;
|
||||
use core::cell::RefCell;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::ptr::null_mut;
|
||||
use error::Errno;
|
||||
use spin::Mutex;
|
||||
|
||||
pub type NodeRef = Rc<RefCell<Node>>;
|
||||
|
||||
pub enum NodeType {
|
||||
Regular,
|
||||
Directory { children: Vec<NodeRef> },
|
||||
}
|
||||
|
||||
pub const NODE_MEMORY: u32 = 1 << 0;
|
||||
|
||||
pub struct NodeOperations {
|
||||
lookup: Option<fn(parent: NodeRef, name: &[u8]) -> Result<NodeRef, Errno>>,
|
||||
drop: Option<fn(node: &mut Node)>,
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
name: FixedStr<64>,
|
||||
typ: NodeType,
|
||||
flags: u32,
|
||||
|
||||
pub(crate) ops: Option<&'static NodeOperations>,
|
||||
pub data: *mut c_void,
|
||||
|
||||
target: Option<NodeRef>,
|
||||
parent: Option<NodeRef>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new(name: &[u8], typ: NodeType) -> Self {
|
||||
let mut r = Self {
|
||||
name: FixedStr::empty(),
|
||||
typ,
|
||||
flags: 0,
|
||||
|
||||
ops: None,
|
||||
data: null_mut(),
|
||||
|
||||
parent: None,
|
||||
target: None,
|
||||
};
|
||||
r.name.copy_from_slice(name);
|
||||
r
|
||||
}
|
||||
|
||||
pub fn directory(name: &[u8]) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::new(
|
||||
name,
|
||||
NodeType::Directory { children: vec![] },
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn file(name: &[u8]) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::new(name, NodeType::Regular)))
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &FixedStr<64> {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<NodeRef> {
|
||||
self.parent.clone()
|
||||
}
|
||||
|
||||
pub fn is_directory(&self) -> bool {
|
||||
matches!(&self.typ, NodeType::Directory { .. })
|
||||
}
|
||||
|
||||
pub fn children(&self) -> impl Iterator<Item = &NodeRef> {
|
||||
let lock = TREE_MUTEX.lock();
|
||||
match &self.typ {
|
||||
NodeType::Directory { children } => LockedIterator::new(children.iter(), lock),
|
||||
_ => panic!("Not a directory"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn children_mut(&mut self) -> impl Iterator<Item = &mut NodeRef> {
|
||||
let lock = TREE_MUTEX.lock();
|
||||
match &mut self.typ {
|
||||
NodeType::Directory { children } => LockedIterator::new(children.iter_mut(), lock),
|
||||
_ => panic!("Not a directory"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach(parent: NodeRef, child: NodeRef) {
|
||||
let _lock = TREE_MUTEX.lock();
|
||||
assert!(child.borrow().parent.is_none());
|
||||
match &mut parent.borrow_mut().typ {
|
||||
NodeType::Directory { children } => children.push(child.clone()),
|
||||
_ => panic!("Not a directory"),
|
||||
}
|
||||
child.borrow_mut().parent.replace(parent.clone());
|
||||
}
|
||||
|
||||
pub fn detach(child: NodeRef) {
|
||||
let _lock = TREE_MUTEX.lock();
|
||||
assert!(child.borrow().parent.is_some());
|
||||
let parent = child.borrow_mut().parent.take().unwrap();
|
||||
match &mut parent.borrow_mut().typ {
|
||||
NodeType::Directory { children } => {
|
||||
children.remove(children.iter().position(|x| Rc::ptr_eq(x, &child)).unwrap());
|
||||
}
|
||||
_ => panic!("Not a directory"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Node {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ops) = self.ops.as_ref() {
|
||||
if let Some(do_drop) = ops.drop.as_ref() {
|
||||
do_drop(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Node {{ name={:?}, typ=??? }}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
static TREE_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use core::any::{type_name, TypeId};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[test]
|
||||
fn node_new() {
|
||||
let r = Node::directory(b"");
|
||||
let n = r.borrow();
|
||||
assert_eq!(n.name, b""[..]);
|
||||
assert!(matches!(n.typ, NodeType::Directory { .. }));
|
||||
assert!(n.ops.is_none());
|
||||
assert!(n.target.is_none());
|
||||
assert!(n.parent.is_none());
|
||||
|
||||
let r = Node::file(b"file1");
|
||||
let n = r.borrow();
|
||||
assert_eq!(n.name, b"file1"[..]);
|
||||
assert!(matches!(n.typ, NodeType::Regular));
|
||||
assert!(n.ops.is_none());
|
||||
assert!(n.target.is_none());
|
||||
assert!(n.parent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_attach() {
|
||||
let r0 = Node::directory(b"");
|
||||
let r1 = Node::directory(b"1234");
|
||||
|
||||
// Attach
|
||||
Node::attach(r0.clone(), r1.clone());
|
||||
|
||||
assert!(Rc::ptr_eq(r1.borrow().parent.as_ref().unwrap(), &r0));
|
||||
{
|
||||
let n0 = r0.borrow();
|
||||
let mut it = n0.children();
|
||||
assert!(Rc::ptr_eq(&it.next().unwrap(), &r1));
|
||||
assert!(it.next().is_none());
|
||||
}
|
||||
|
||||
// Detach
|
||||
Node::detach(r1.clone());
|
||||
|
||||
assert!(r1.borrow().parent.is_none());
|
||||
assert_eq!(r0.borrow().children().count(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use spin::MutexGuard;
|
||||
|
||||
pub struct LockedIterator<'a, T: ?Sized, I: Iterator<Item = T>> {
|
||||
inner: I,
|
||||
#[allow(dead_code)]
|
||||
lock: MutexGuard<'a, ()>,
|
||||
}
|
||||
|
||||
impl<'a, T, I: Iterator<Item = T>> Iterator for LockedIterator<'a, T, I> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, I: Iterator<Item = T>> LockedIterator<'a, T, I> {
|
||||
#[inline]
|
||||
pub fn new(inner: I, lock: MutexGuard<'a, ()>) -> Self {
|
||||
Self { inner, lock }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
|
||||
pub mod iter;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct FixedStr<const N: usize> {
|
||||
inner: [u8; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> FixedStr<N> {
|
||||
pub fn new(s: &[u8]) -> Self {
|
||||
let mut r = Self::empty();
|
||||
r.copy_from_slice(s);
|
||||
r
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self { inner: [0; N] }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.iter().position(|&n| n == 0).unwrap_or(N)
|
||||
}
|
||||
|
||||
pub fn copy_from_slice(&mut self, src: &[u8]) {
|
||||
let src_len = src.len();
|
||||
if src_len == 0 {
|
||||
self.inner[0] = 0;
|
||||
return;
|
||||
}
|
||||
if src_len >= N {
|
||||
panic!("String buffer overflow");
|
||||
}
|
||||
let dst = &mut self.inner[..src_len];
|
||||
dst.copy_from_slice(src);
|
||||
self.inner[src_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialEq<[u8]> for FixedStr<N> {
|
||||
fn eq(&self, other: &[u8]) -> bool {
|
||||
let self_len = self.len();
|
||||
if self_len != other.len() {
|
||||
return false;
|
||||
}
|
||||
&self.inner[..self_len] == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Display for FixedStr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for byte in self.inner {
|
||||
if byte == 0 {
|
||||
break;
|
||||
}
|
||||
write!(f, "{}", byte as char)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for FixedStr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fixed_str_display() {
|
||||
let s = FixedStr::<64>::new(b"test");
|
||||
assert_eq!(format!("{}", s), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_str_length() {
|
||||
assert_eq!(FixedStr::<64>::empty().len(), 0);
|
||||
assert_eq!(FixedStr::<64>::new(b"test1").len(), 5);
|
||||
assert_eq!(FixedStr::<6>::new(b"test1").len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_str_eq_slice() {
|
||||
assert_eq!(FixedStr::<64>::empty(), b""[..]);
|
||||
assert_eq!(FixedStr::<64>::new(b"1234"), b"1234"[..]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user