fs: path abi, ioctx impl
This commit is contained in:
parent
aadd97fc8e
commit
fbe05c1d4d
@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git", features = ["alloc"] }
|
||||
kernel-util = { path = "../kernel-util" }
|
||||
|
||||
log = "0.4.20"
|
||||
|
@ -16,7 +16,7 @@ pub trait BlockDevice {
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn size(&self) -> Result<usize, Error>;
|
||||
fn size(&self) -> Result<u64, Error>;
|
||||
}
|
||||
|
||||
pub trait CharDevice {
|
||||
@ -54,7 +54,7 @@ impl BlockDeviceWrapper {
|
||||
}
|
||||
|
||||
impl CommonImpl for BlockDeviceWrapper {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
self.0.size()
|
||||
}
|
||||
}
|
||||
@ -74,7 +74,7 @@ impl CharDeviceWrapper {
|
||||
}
|
||||
|
||||
impl CommonImpl for CharDeviceWrapper {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,15 @@ impl File {
|
||||
_ => Err(Error::NotADirectory),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node(&self) -> Option<&NodeRef> {
|
||||
match self {
|
||||
Self::Directory(file) => Some(&file.node),
|
||||
Self::Regular(file) => Some(&file.node),
|
||||
Self::Block(file) => Some(&file.node),
|
||||
Self::Char(file) => Some(&file.node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for File {
|
||||
@ -198,7 +207,7 @@ mod tests {
|
||||
use crate::{
|
||||
device::{BlockDevice, CharDevice},
|
||||
file::DirectoryOpenPosition,
|
||||
node::{CommonImpl, DirectoryImpl, Node, NodeRef, RegularImpl},
|
||||
node::{CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
|
||||
traits::{Read, Seek, Write},
|
||||
};
|
||||
|
||||
@ -244,13 +253,16 @@ mod tests {
|
||||
impl CommonImpl for F {}
|
||||
impl RegularImpl for F {}
|
||||
|
||||
let d = Node::directory(D {
|
||||
entries: Vec::from_iter([
|
||||
("f1".to_owned(), Node::regular(F)),
|
||||
("f2".to_owned(), Node::regular(F)),
|
||||
("f3".to_owned(), Node::regular(F)),
|
||||
]),
|
||||
});
|
||||
let d = Node::directory(
|
||||
D {
|
||||
entries: Vec::from_iter([
|
||||
("f1".to_owned(), Node::regular(F, NodeFlags::empty())),
|
||||
("f2".to_owned(), Node::regular(F, NodeFlags::empty())),
|
||||
("f3".to_owned(), Node::regular(F, NodeFlags::empty())),
|
||||
]),
|
||||
},
|
||||
NodeFlags::empty(),
|
||||
);
|
||||
|
||||
let f = d.open_directory().unwrap();
|
||||
|
||||
@ -293,8 +305,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let d = Node::directory(D);
|
||||
let child = Node::directory(D);
|
||||
let d = Node::directory(D, NodeFlags::empty());
|
||||
let child = Node::directory(D, NodeFlags::empty());
|
||||
|
||||
d.add_child("child1", child).unwrap();
|
||||
|
||||
@ -334,8 +346,8 @@ mod tests {
|
||||
}
|
||||
|
||||
impl CommonImpl for F {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(self.data.borrow().len())
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(self.data.borrow().len() as _)
|
||||
}
|
||||
}
|
||||
impl RegularImpl for F {
|
||||
@ -380,7 +392,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let data = Arc::new(RefCell::new(vec![]));
|
||||
let node = Node::regular(F { data: data.clone() });
|
||||
let node = Node::regular(F { data: data.clone() }, NodeFlags::empty());
|
||||
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
||||
let mut buf = [0; 512];
|
||||
|
||||
@ -435,8 +447,8 @@ mod tests {
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<usize, Error> {
|
||||
Ok(self.data.lock().len())
|
||||
fn size(&self) -> Result<u64, Error> {
|
||||
Ok(self.data.lock().len() as _)
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,7 +458,7 @@ mod tests {
|
||||
let dev = Box::leak(Box::new(B { data }));
|
||||
let mut buf = [0; 512];
|
||||
|
||||
let node = Node::block(dev);
|
||||
let node = Node::block(dev, NodeFlags::empty());
|
||||
|
||||
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
||||
|
||||
@ -480,7 +492,7 @@ mod tests {
|
||||
|
||||
static DEV: C = C;
|
||||
|
||||
let node = Node::char(&DEV);
|
||||
let node = Node::char(&DEV, NodeFlags::empty());
|
||||
let mut buf = [0; 512];
|
||||
|
||||
let err = node.open(OpenOptions::WRITE).unwrap_err();
|
||||
|
390
lib/vfs/src/ioctx.rs
Normal file
390
lib/vfs/src/ioctx.rs
Normal file
@ -0,0 +1,390 @@
|
||||
use alloc::borrow::ToOwned;
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{FileMode, FileType, GroupId, OpenOptions, UserId},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
node::{CreateInfo, SymlinkData},
|
||||
FileRef, NodeRef,
|
||||
};
|
||||
|
||||
pub struct IoContext {
|
||||
uid: UserId,
|
||||
gid: GroupId,
|
||||
umask: FileMode,
|
||||
cwd_node: NodeRef,
|
||||
cwd_path: PathBuf,
|
||||
root: NodeRef,
|
||||
}
|
||||
|
||||
impl IoContext {
|
||||
pub fn new(root: NodeRef) -> Self {
|
||||
Self {
|
||||
uid: UserId::root(),
|
||||
gid: GroupId::root(),
|
||||
umask: FileMode::new(0o022),
|
||||
cwd_node: root.clone(),
|
||||
cwd_path: PathBuf::new(),
|
||||
root,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root(&self) -> &NodeRef {
|
||||
&self.root
|
||||
}
|
||||
|
||||
pub fn cwd(&self) -> &NodeRef {
|
||||
&self.cwd_node
|
||||
}
|
||||
|
||||
pub fn set_cwd<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||
let path = path.as_ref();
|
||||
if !path.is_absolute() {
|
||||
todo!();
|
||||
}
|
||||
let node = Self::_find(self.root.clone(), path.trim_start_separators(), true, true)?;
|
||||
if !node.is_directory() {
|
||||
return Err(Error::NotADirectory);
|
||||
}
|
||||
self.cwd_node = node;
|
||||
self.cwd_path = path.to_owned();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cwd_path(&self) -> &Path {
|
||||
self.cwd_path.as_ref()
|
||||
}
|
||||
|
||||
pub fn mount<P: AsRef<Path>>(&mut self, target: P, fs_root: NodeRef) -> Result<(), Error> {
|
||||
let target = target.as_ref();
|
||||
if !target.is_absolute() {
|
||||
todo!();
|
||||
}
|
||||
let target_node = Self::_find(
|
||||
self.root.clone(),
|
||||
target.trim_start_separators(),
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
|
||||
target_node.set_mountpoint_target(fs_root)
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
at: Option<NodeRef>,
|
||||
path: P,
|
||||
opts: OpenOptions,
|
||||
mode: FileMode,
|
||||
) -> Result<FileRef, Error> {
|
||||
let path = path.as_ref();
|
||||
let node = match self.find(at.clone(), path, true, true) {
|
||||
Ok(node) => node,
|
||||
Err(Error::DoesNotExist) if opts.contains(OpenOptions::CREATE) => {
|
||||
// let create_mode = mode & !self.umask;
|
||||
let (parent, name) = path.split_right();
|
||||
let parent = self.find(at, parent, true, true)?;
|
||||
let create_info = CreateInfo {
|
||||
name: name.into(),
|
||||
mode: mode & !self.umask,
|
||||
uid: self.uid,
|
||||
gid: self.gid,
|
||||
ty: FileType::File,
|
||||
};
|
||||
parent.create(&create_info)?
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
node.open(opts)
|
||||
}
|
||||
|
||||
pub fn find<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
at: Option<NodeRef>,
|
||||
path: P,
|
||||
follow_links: bool,
|
||||
follow_mount: bool,
|
||||
) -> Result<NodeRef, Error> {
|
||||
let mut path = path.as_ref();
|
||||
let at = if path.is_absolute() {
|
||||
path = path.trim_start_separators();
|
||||
self.root.clone()
|
||||
} else if let Some(at) = at {
|
||||
at
|
||||
} else {
|
||||
self.cwd_node.clone()
|
||||
};
|
||||
|
||||
Self::_find(at, path, follow_links, follow_mount)
|
||||
}
|
||||
|
||||
fn _resolve_link(at: &NodeRef, link: &SymlinkData) -> Result<NodeRef, Error> {
|
||||
// If the filesystem itself implements direct target resolution, use that
|
||||
match link.imp.target(at) {
|
||||
Err(Error::NotImplemented) => {}
|
||||
other => return other,
|
||||
}
|
||||
|
||||
// If that call wasn't implemented, check if the target was cached
|
||||
if let Some((_, target)) = link.target.lock().as_ref() {
|
||||
return Ok(target.clone());
|
||||
}
|
||||
|
||||
// If no cache is present, read the link's data and resolve the path
|
||||
let _path = link.imp.read_to_string()?;
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn _resolve(mut at: NodeRef, follow_links: bool, follow_mount: bool) -> Result<NodeRef, Error> {
|
||||
loop {
|
||||
if follow_mount && let Some(target) = at.mountpoint_target() {
|
||||
at = target.clone();
|
||||
continue;
|
||||
}
|
||||
|
||||
if follow_links && let Ok(link) = at.as_symlink() {
|
||||
// Resolve the link
|
||||
at = Self::_resolve_link(&at, link)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
break Ok(at);
|
||||
}
|
||||
}
|
||||
|
||||
fn _find(
|
||||
mut at: NodeRef,
|
||||
path: &Path,
|
||||
follow_links: bool,
|
||||
follow_mount: bool,
|
||||
) -> Result<NodeRef, Error> {
|
||||
let mut element;
|
||||
let mut rest = path;
|
||||
|
||||
loop {
|
||||
(element, rest) = rest.split_left();
|
||||
|
||||
if !at.is_directory() {
|
||||
return Err(Error::NotADirectory);
|
||||
}
|
||||
|
||||
match element {
|
||||
Path::PARENT_NAME => {
|
||||
at = at.parent();
|
||||
}
|
||||
Path::SELF_NAME => {}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
at = Self::_resolve(at, follow_links, follow_mount)?;
|
||||
|
||||
if !at.is_directory() {
|
||||
return Err(Error::NotADirectory);
|
||||
}
|
||||
|
||||
if element.is_empty() && rest.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
|
||||
let node = at.lookup_or_load(element)?;
|
||||
let node = Self::_resolve(node, follow_links, follow_mount)?;
|
||||
|
||||
if rest.is_empty() {
|
||||
Ok(node)
|
||||
} else {
|
||||
Self::_find(node, rest, follow_links, follow_mount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::sync::Arc;
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{FileMode, OpenOptions},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
impls::{const_value_node, f_symlink, mdir},
|
||||
Read,
|
||||
};
|
||||
|
||||
use super::IoContext;
|
||||
|
||||
#[test]
|
||||
fn cwd() {
|
||||
let d1_f1 = const_value_node("dir1-file1");
|
||||
let d1 = mdir([("f1", d1_f1.clone())]);
|
||||
let f1 = const_value_node("file1");
|
||||
let root = mdir([("f1", f1.clone()), ("d1", d1.clone())]);
|
||||
|
||||
let mut ioctx = IoContext::new(root.clone());
|
||||
|
||||
assert_eq!(ioctx.cwd_path(), Path::empty());
|
||||
|
||||
let node = ioctx.find(None, "f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &f1));
|
||||
|
||||
let node = ioctx.find(None, "d1/f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &d1_f1));
|
||||
|
||||
let node = ioctx.find(None, "d1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &d1));
|
||||
|
||||
ioctx.set_cwd("/d1").unwrap();
|
||||
assert_eq!(ioctx.cwd_path(), Path::from_str("/d1"));
|
||||
|
||||
let err = ioctx.find(None, "d1", true, true).unwrap_err();
|
||||
assert_eq!(err, Error::DoesNotExist);
|
||||
|
||||
let node = ioctx.find(None, "f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &d1_f1));
|
||||
|
||||
let node = ioctx.find(None, "../d1/f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &d1_f1));
|
||||
|
||||
let node = ioctx.find(None, "/f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &f1));
|
||||
|
||||
let node = ioctx.find(None, "../f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &f1));
|
||||
|
||||
let node = ioctx.find(None, "..", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mount_resolution() {
|
||||
let root2_f1 = const_value_node("root2-file1");
|
||||
let root2 = mdir([("f1", root2_f1.clone())]);
|
||||
let root1_f1 = const_value_node("root1-file1");
|
||||
let root1 = mdir([("f1", root1_f1.clone())]);
|
||||
|
||||
let mut ioctx = IoContext::new(root1.clone());
|
||||
|
||||
let node = ioctx.find(None, "/f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root1_f1));
|
||||
|
||||
ioctx.mount("/", root2.clone()).unwrap();
|
||||
|
||||
let node = ioctx.find(None, "/f1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root2_f1));
|
||||
|
||||
let node = ioctx.find(None, "/f1", true, false).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root1_f1));
|
||||
|
||||
let node = ioctx.find(None, "/", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root2));
|
||||
|
||||
let node = ioctx.find(None, "/", true, false).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symlink_resolution() {
|
||||
let f1 = const_value_node("file1");
|
||||
let l1 = f_symlink(f1.clone());
|
||||
let root = mdir([("l1", l1.clone())]);
|
||||
|
||||
let mut ioctx = IoContext::new(root.clone());
|
||||
|
||||
// No follow
|
||||
let node = ioctx.find(None, "/l1", false, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &l1));
|
||||
|
||||
// Follow
|
||||
let node = ioctx.find(None, "/l1", true, true).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &f1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_or_create() {
|
||||
let root_d1_f1 = const_value_node("dir1/file1");
|
||||
let root_f1 = const_value_node("file1");
|
||||
let root_d1 = mdir([("f1", root_d1_f1.clone())]);
|
||||
let root = mdir([("f1", root_f1.clone()), ("d1", root_d1.clone())]);
|
||||
|
||||
let mut ioctx = IoContext::new(root.clone());
|
||||
let mut buf = [0; 512];
|
||||
|
||||
let file = ioctx
|
||||
.open(None, "/d1/f1", OpenOptions::READ, FileMode::empty())
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&file.node().unwrap(), &root_d1_f1));
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 10);
|
||||
assert_eq!(&buf[..10], b"dir1/file1");
|
||||
drop(file);
|
||||
|
||||
let file = ioctx
|
||||
.open(
|
||||
None,
|
||||
"/d1/f1",
|
||||
OpenOptions::READ | OpenOptions::CREATE,
|
||||
FileMode::empty(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&file.node().unwrap(), &root_d1_f1));
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 10);
|
||||
assert_eq!(&buf[..10], b"dir1/file1");
|
||||
drop(file);
|
||||
|
||||
let err = ioctx
|
||||
.open(
|
||||
None,
|
||||
"/d1/f2",
|
||||
OpenOptions::WRITE | OpenOptions::CREATE,
|
||||
FileMode::empty(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, Error::ReadOnly);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_tests() {
|
||||
let root_d1_f1 = const_value_node("dir1/file1");
|
||||
let root_f1 = const_value_node("file1");
|
||||
let root_d1 = mdir([("f1", root_d1_f1.clone())]);
|
||||
let root = mdir([("f1", root_f1.clone()), ("d1", root_d1.clone())]);
|
||||
|
||||
let mut ioctx = IoContext::new(root.clone());
|
||||
|
||||
// Ok paths
|
||||
let node = ioctx.find(None, Path::empty(), false, false).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
let node = ioctx.find(None, Path::from_str("/"), false, false).unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
|
||||
// Weird paths
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("////.////./"), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("/../.././//."), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("/f1"), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root_f1));
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("/d1/f1"), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root_d1_f1));
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("/d1/../d1/./f1"), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root_d1_f1));
|
||||
let node = ioctx
|
||||
.find(None, Path::from_str("/d1/.."), false, false)
|
||||
.unwrap();
|
||||
assert!(Arc::ptr_eq(&node, &root));
|
||||
}
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![feature(trait_upcasting, if_let_guard, maybe_uninit_slice, trait_alias)]
|
||||
#![feature(
|
||||
trait_upcasting,
|
||||
if_let_guard,
|
||||
maybe_uninit_slice,
|
||||
trait_alias,
|
||||
let_chains
|
||||
)]
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate hosted_tests;
|
||||
@ -8,7 +14,9 @@ extern crate alloc;
|
||||
|
||||
pub(crate) mod device;
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod ioctx;
|
||||
pub(crate) mod node;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod traits;
|
||||
|
||||
pub use device::{BlockDevice, CharDevice};
|
||||
|
@ -1,10 +1,19 @@
|
||||
use core::{any::Any, cell::RefCell, marker::PhantomData, str::FromStr};
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use yggdrasil_abi::{error::Error, io::OpenOptions};
|
||||
|
||||
use super::{CommonImpl, Node, NodeRef, RegularImpl};
|
||||
use crate::DirectoryOpenPosition;
|
||||
|
||||
use super::{
|
||||
CommonImpl, CreateInfo, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl, SymlinkImpl,
|
||||
};
|
||||
|
||||
pub trait SliceRead {
|
||||
fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize;
|
||||
@ -65,9 +74,17 @@ pub struct FnNode<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> {
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct MemoryDirectory;
|
||||
pub struct FixedSymlink {
|
||||
target: NodeRef,
|
||||
}
|
||||
|
||||
impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
|
||||
pub fn new(read: R) -> NodeRef {
|
||||
Node::regular(Self::new_impl(read))
|
||||
Node::regular(
|
||||
Self::new_impl(read),
|
||||
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn new_impl(read: R) -> Self {
|
||||
@ -79,7 +96,7 @@ impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
|
||||
}
|
||||
|
||||
impl<T: ToString, R: ReadFn<T>> CommonImpl for ReadOnlyFnNode<T, R> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
@ -149,7 +166,10 @@ impl<T: ToString + FromStr + 'static, R: ReadFn<T> + 'static, W: WriteFn<T> + 's
|
||||
FnNode<T, R, W>
|
||||
{
|
||||
pub fn new(read: R, write: W) -> NodeRef {
|
||||
Node::regular(Self::new_impl(read, write))
|
||||
Node::regular(
|
||||
Self::new_impl(read, write),
|
||||
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn new_impl(read: R, write: W) -> Self {
|
||||
@ -162,7 +182,7 @@ impl<T: ToString + FromStr + 'static, R: ReadFn<T> + 'static, W: WriteFn<T> + 's
|
||||
}
|
||||
|
||||
impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> CommonImpl for FnNode<T, R, W> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
@ -227,6 +247,28 @@ impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> RegularImpl for FnNode<
|
||||
}
|
||||
}
|
||||
|
||||
// In-memory directory
|
||||
|
||||
impl CommonImpl for MemoryDirectory {}
|
||||
impl DirectoryImpl for MemoryDirectory {
|
||||
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
||||
Ok(DirectoryOpenPosition::FromCache)
|
||||
}
|
||||
|
||||
fn create(&self, _parent: &NodeRef, _info: &CreateInfo) -> Result<NodeRef, Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
// In-memory fixed symlink
|
||||
|
||||
impl CommonImpl for FixedSymlink {}
|
||||
impl SymlinkImpl for FixedSymlink {
|
||||
fn target(&self, _node: &NodeRef) -> Result<NodeRef, Error> {
|
||||
Ok(self.target.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn const_value_node<T: ToString + Clone + 'static>(value: T) -> NodeRef {
|
||||
ReadOnlyFnNode::new(move || Ok(value.clone()))
|
||||
}
|
||||
@ -244,6 +286,21 @@ pub fn value_node<T: ToString + FromStr + Clone + 'static>(value: T) -> NodeRef
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mdir<S: Into<String>, I: IntoIterator<Item = (S, NodeRef)>>(it: I) -> NodeRef {
|
||||
let dir = Node::directory(
|
||||
MemoryDirectory,
|
||||
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
|
||||
);
|
||||
for (name, node) in it {
|
||||
dir.add_child(name, node).unwrap();
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub fn f_symlink(target: NodeRef) -> NodeRef {
|
||||
Node::symlink(FixedSymlink { target }, NodeFlags::empty())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yggdrasil_abi::io::OpenOptions;
|
||||
|
@ -1,8 +1,16 @@
|
||||
use core::any::Any;
|
||||
use core::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
fmt,
|
||||
};
|
||||
|
||||
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
|
||||
use kernel_util::sync::IrqSafeSpinlock;
|
||||
use yggdrasil_abi::{error::Error, io::FileType};
|
||||
use yggdrasil_abi::{
|
||||
bitflags,
|
||||
error::Error,
|
||||
io::{FileAttr, FileMode, FileType, GroupId, UserId},
|
||||
};
|
||||
|
||||
pub mod impls;
|
||||
mod traits;
|
||||
@ -11,60 +19,117 @@ mod traits;
|
||||
mod ops;
|
||||
mod tree;
|
||||
|
||||
pub use traits::{CommonImpl, DirectoryImpl, RegularImpl};
|
||||
pub use traits::{CommonImpl, DirectoryImpl, RegularImpl, SymlinkImpl};
|
||||
|
||||
use crate::device::{BlockDevice, BlockDeviceWrapper, CharDevice, CharDeviceWrapper};
|
||||
|
||||
pub type NodeRef = Arc<Node>;
|
||||
|
||||
bitflags! {
|
||||
pub struct NodeFlags: u32 {
|
||||
const IN_MEMORY_PROPS: bit 0;
|
||||
const IN_MEMORY_SIZE: bit 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CreateInfo {
|
||||
pub name: String,
|
||||
pub uid: UserId,
|
||||
pub gid: GroupId,
|
||||
pub mode: FileMode,
|
||||
pub ty: FileType,
|
||||
}
|
||||
|
||||
pub(crate) struct DirectoryData {
|
||||
pub(crate) imp: Box<dyn DirectoryImpl>,
|
||||
pub(crate) mountpoint: IrqSafeSpinlock<Option<NodeRef>>,
|
||||
pub(crate) children: IrqSafeSpinlock<Vec<(String, NodeRef)>>,
|
||||
}
|
||||
|
||||
pub(crate) struct SymlinkData {
|
||||
// Cached symlink target with the literal path
|
||||
pub(crate) target: IrqSafeSpinlock<Option<(String, NodeRef)>>,
|
||||
pub(crate) imp: Box<dyn SymlinkImpl>,
|
||||
}
|
||||
|
||||
enum NodeImpl {
|
||||
Regular(Box<dyn RegularImpl>),
|
||||
Directory(DirectoryData),
|
||||
Block(BlockDeviceWrapper),
|
||||
Char(CharDeviceWrapper),
|
||||
Symlink(SymlinkData),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Metadata {
|
||||
pub uid: UserId,
|
||||
pub gid: GroupId,
|
||||
pub mode: FileMode,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PropertyCache {
|
||||
metadata: Metadata,
|
||||
size: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
data: NodeImpl,
|
||||
flags: NodeFlags,
|
||||
props: IrqSafeSpinlock<PropertyCache>,
|
||||
parent: IrqSafeSpinlock<Option<NodeRef>>,
|
||||
}
|
||||
|
||||
impl Default for Metadata {
|
||||
fn default() -> Metadata {
|
||||
Metadata {
|
||||
uid: UserId::root(),
|
||||
gid: GroupId::root(),
|
||||
mode: FileMode::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn directory<T: DirectoryImpl + 'static>(data: T) -> NodeRef {
|
||||
let data = NodeImpl::Directory(DirectoryData {
|
||||
imp: Box::new(data),
|
||||
children: IrqSafeSpinlock::new(Vec::new()),
|
||||
});
|
||||
fn new(data: NodeImpl, flags: NodeFlags) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data,
|
||||
flags,
|
||||
props: IrqSafeSpinlock::new(PropertyCache::default()),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn regular<T: RegularImpl + 'static>(data: T) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data: NodeImpl::Regular(Box::new(data)),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
pub fn directory<T: DirectoryImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
|
||||
let data = NodeImpl::Directory(DirectoryData {
|
||||
imp: Box::new(data),
|
||||
mountpoint: IrqSafeSpinlock::new(None),
|
||||
children: IrqSafeSpinlock::new(Vec::new()),
|
||||
});
|
||||
Self::new(data, flags)
|
||||
}
|
||||
|
||||
pub fn block(device: &'static dyn BlockDevice) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data: NodeImpl::Block(BlockDeviceWrapper(device)),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
pub fn regular<T: RegularImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
|
||||
Self::new(NodeImpl::Regular(Box::new(data)), flags)
|
||||
}
|
||||
|
||||
pub fn char(device: &'static dyn CharDevice) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data: NodeImpl::Char(CharDeviceWrapper(device)),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
pub fn block(device: &'static dyn BlockDevice, flags: NodeFlags) -> NodeRef {
|
||||
Self::new(NodeImpl::Block(BlockDeviceWrapper(device)), flags)
|
||||
}
|
||||
|
||||
pub fn char(device: &'static dyn CharDevice, flags: NodeFlags) -> NodeRef {
|
||||
Self::new(NodeImpl::Char(CharDeviceWrapper(device)), flags)
|
||||
}
|
||||
|
||||
pub fn symlink<T: SymlinkImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
|
||||
Self::new(
|
||||
NodeImpl::Symlink(SymlinkData {
|
||||
target: IrqSafeSpinlock::new(None),
|
||||
imp: Box::new(data),
|
||||
}),
|
||||
flags,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn data_as_any(&self) -> &dyn Any {
|
||||
@ -73,6 +138,7 @@ impl Node {
|
||||
NodeImpl::Regular(imp) => imp.as_any(),
|
||||
NodeImpl::Block(w) => w.as_any(),
|
||||
NodeImpl::Char(w) => w.as_any(),
|
||||
NodeImpl::Symlink(w) => w.imp.as_any(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +148,7 @@ impl Node {
|
||||
NodeImpl::Regular(imp) => imp.as_ref(),
|
||||
NodeImpl::Block(w) => w,
|
||||
NodeImpl::Char(w) => w,
|
||||
NodeImpl::Symlink(w) => w.imp.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +162,7 @@ impl Node {
|
||||
NodeImpl::Directory(_) => FileType::Directory,
|
||||
NodeImpl::Block(_) => FileType::Block,
|
||||
NodeImpl::Char(_) => FileType::Char,
|
||||
NodeImpl::Symlink(_) => FileType::Symlink,
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,6 +178,36 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mountpoint_target(&self) -> Option<NodeRef> {
|
||||
match &self.data {
|
||||
NodeImpl::Directory(dir) => dir.mountpoint.lock().clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_mountpoint_target(self: &NodeRef, target: NodeRef) -> Result<(), Error> {
|
||||
let directory = self.as_directory()?;
|
||||
let mut mountpoint = directory.mountpoint.lock();
|
||||
let mut target_parent_lock = target.parent.lock();
|
||||
|
||||
if mountpoint.is_some() {
|
||||
// TODO Busy
|
||||
todo!();
|
||||
}
|
||||
if target_parent_lock.is_some() {
|
||||
// TODO mount a filesystem more than once?
|
||||
return Err(Error::AlreadyExists);
|
||||
}
|
||||
if !target.is_directory() {
|
||||
return Err(Error::NotADirectory);
|
||||
}
|
||||
|
||||
mountpoint.replace(target.clone());
|
||||
target_parent_lock.replace(self.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn as_directory(&self) -> Result<&DirectoryData, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Directory(dir) => Ok(dir),
|
||||
@ -123,6 +221,25 @@ impl Node {
|
||||
_ => Err(Error::InvalidFile),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_symlink(&self) -> Result<&SymlinkData, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Symlink(imp) => Ok(imp),
|
||||
_ => Err(Error::InvalidFile),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.data {
|
||||
NodeImpl::Directory(_) => f.debug_struct("DirectoryNode").finish_non_exhaustive(),
|
||||
NodeImpl::Regular(_) => f.debug_struct("RegularNode").finish_non_exhaustive(),
|
||||
NodeImpl::Char(_) => f.debug_struct("CharNode").finish_non_exhaustive(),
|
||||
NodeImpl::Block(_) => f.debug_struct("BlockNode").finish_non_exhaustive(),
|
||||
NodeImpl::Symlink(_) => f.debug_struct("SymlinkNode").finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -130,6 +247,8 @@ mod tests {
|
||||
use core::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::node::NodeFlags;
|
||||
|
||||
use super::{CommonImpl, DirectoryImpl, Node, RegularImpl};
|
||||
|
||||
struct DummyDirectory;
|
||||
@ -143,9 +262,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn dir_cache_add() {
|
||||
let d1 = Node::directory(DummyDirectory);
|
||||
let d2 = Node::directory(DummyDirectory);
|
||||
let f1 = Node::regular(DummyFile);
|
||||
let d1 = Node::directory(DummyDirectory, NodeFlags::empty());
|
||||
let d2 = Node::directory(DummyDirectory, NodeFlags::empty());
|
||||
let f1 = Node::regular(DummyFile, NodeFlags::empty());
|
||||
|
||||
assert!(Arc::ptr_eq(&f1.parent(), &f1));
|
||||
assert_eq!(d1.children_len().unwrap(), 0);
|
||||
@ -164,14 +283,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn in_mem_dir_size_coherence() {
|
||||
let d = Node::directory(DummyDirectory);
|
||||
let d = Node::directory(DummyDirectory, NodeFlags::IN_MEMORY_SIZE);
|
||||
|
||||
for i in 0..10 {
|
||||
let name = format!("f{}", i);
|
||||
let node = Node::regular(DummyFile);
|
||||
let node = Node::regular(DummyFile, NodeFlags::empty());
|
||||
|
||||
d.add_child(name, node).unwrap();
|
||||
assert_eq!(d.size().unwrap(), d.children_len().unwrap());
|
||||
assert_eq!(d.size().unwrap(), d.children_len().unwrap() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +307,7 @@ mod tests {
|
||||
}
|
||||
impl DirectoryImpl for AnyData {}
|
||||
|
||||
let d = Node::directory(AnyData { value: 1234 });
|
||||
let d = Node::directory(AnyData { value: 1234 }, NodeFlags::empty());
|
||||
let r = d.data_as_ref::<AnyData>();
|
||||
|
||||
assert_eq!(r.value, 1234);
|
||||
|
@ -7,7 +7,7 @@ use yggdrasil_abi::{
|
||||
|
||||
use crate::file::{File, FileRef};
|
||||
|
||||
use super::{Node, NodeImpl, NodeRef};
|
||||
use super::{CreateInfo, Metadata, Node, NodeFlags, NodeImpl, NodeRef};
|
||||
|
||||
impl Node {
|
||||
pub fn open(self: &NodeRef, opts: OpenOptions) -> Result<FileRef, Error> {
|
||||
@ -19,7 +19,7 @@ impl Node {
|
||||
NodeImpl::Block(dev) => File::block(dev.clone(), self.clone(), opts),
|
||||
NodeImpl::Char(dev) => File::char(dev.clone(), self.clone(), opts),
|
||||
// TODO: maybe merge open_directory and open?
|
||||
NodeImpl::Directory(_) => todo!(),
|
||||
NodeImpl::Directory(_) | NodeImpl::Symlink(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,9 +39,58 @@ impl Node {
|
||||
self.as_directory()?.imp.read_entries(self, pos, entries)
|
||||
}
|
||||
|
||||
pub fn lookup_or_load(self: &NodeRef, name: &str) -> Result<NodeRef, Error> {
|
||||
let dir = self.as_directory()?;
|
||||
let children = dir.children.lock();
|
||||
|
||||
if let Some((_, node)) = children.iter().find(|(name_, _)| name_ == name) {
|
||||
return Ok(node.clone());
|
||||
}
|
||||
|
||||
// TODO lookup in real FS
|
||||
|
||||
Err(Error::DoesNotExist)
|
||||
}
|
||||
|
||||
pub fn create(self: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
|
||||
let directory = self.as_directory()?;
|
||||
let node = directory.imp.create(self, info)?;
|
||||
// Attach the created node to the directory in memory cache
|
||||
self.add_child(&info.name, node.clone())?;
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
// Common
|
||||
|
||||
pub fn size(self: &NodeRef) -> Result<usize, Error> {
|
||||
self.data_as_common().size(self)
|
||||
pub fn metadata(self: &NodeRef) -> Result<Metadata, Error> {
|
||||
if self.flags.contains(NodeFlags::IN_MEMORY_PROPS) {
|
||||
let props = self.props.lock();
|
||||
return Ok(props.metadata);
|
||||
}
|
||||
|
||||
self.data_as_common().metadata(self)
|
||||
}
|
||||
|
||||
pub fn size(self: &NodeRef) -> Result<u64, Error> {
|
||||
// Try to fetch the size from the cache
|
||||
let mut props = self.props.lock();
|
||||
|
||||
if let Some(size) = props.size {
|
||||
return Ok(size);
|
||||
}
|
||||
|
||||
if self.flags.contains(NodeFlags::IN_MEMORY_SIZE) {
|
||||
if let Ok(dir) = self.as_directory() {
|
||||
return Ok(dir.children.lock().len() as _);
|
||||
}
|
||||
|
||||
Err(Error::NotImplemented)
|
||||
} else {
|
||||
// Fetch the size from the node
|
||||
let size = self.data_as_common().size(self)?;
|
||||
props.size = Some(size);
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
use alloc::boxed::Box;
|
||||
use alloc::{boxed::Box, string::String};
|
||||
use core::{any::Any, mem::MaybeUninit};
|
||||
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DirectoryEntry, FileAttr, OpenOptions},
|
||||
io::{DirectoryEntry, OpenOptions},
|
||||
};
|
||||
|
||||
use crate::file::DirectoryOpenPosition;
|
||||
|
||||
use super::NodeRef;
|
||||
use super::{CreateInfo, Metadata, NodeRef};
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait CommonImpl {
|
||||
@ -16,16 +16,12 @@ pub trait CommonImpl {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn metadata(&self, node: &NodeRef) -> Result<FileAttr, Error> {
|
||||
fn metadata(&self, node: &NodeRef) -> Result<Metadata, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
fn size(&self, node: &NodeRef) -> Result<usize, Error> {
|
||||
// TODO this only works for dirs
|
||||
if node.is_directory() {
|
||||
node.children_len()
|
||||
} else {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn size(&self, node: &NodeRef) -> Result<u64, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +74,10 @@ pub trait DirectoryImpl: CommonImpl {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn create(&self, parent: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn lookup(&self, node: &NodeRef, name: &str) -> Result<NodeRef, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
@ -86,3 +86,24 @@ pub trait DirectoryImpl: CommonImpl {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait SymlinkImpl: CommonImpl {
|
||||
fn target(&self, node: &NodeRef) -> Result<NodeRef, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn read_to_string(&self) -> Result<String, Error> {
|
||||
let mut data = Box::new([0; 512]);
|
||||
let len = self.read_link(data.as_mut())?;
|
||||
if len == data.len() {
|
||||
return Err(Error::InvalidFile);
|
||||
}
|
||||
let str = core::str::from_utf8(&data[..len]).map_err(|_| Error::InvalidFile)?;
|
||||
Ok(String::from(str))
|
||||
}
|
||||
|
||||
fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ impl Node {
|
||||
|
||||
assert!(child.parent.replace(Some(self.clone())).is_none());
|
||||
children.push((name, child));
|
||||
// children.insert(name, child);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
0
lib/vfs/src/path.rs
Normal file
0
lib/vfs/src/path.rs
Normal file
Loading…
x
Reference in New Issue
Block a user