fs: initial rework
This commit is contained in:
parent
2d7568f829
commit
aadd97fc8e
8
lib/hosted-tests/Cargo.toml
Normal file
8
lib/hosted-tests/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "hosted-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
9
lib/hosted-tests/src/lib.rs
Normal file
9
lib/hosted-tests/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
#[no_mangle]
|
||||
fn __acquire_irq_guard() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn __release_irq_guard(_: bool) {}
|
@ -1,6 +1,7 @@
|
||||
//! Synchronization primitives
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
};
|
||||
@ -116,6 +117,12 @@ impl<T> IrqSafeSpinlock<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn replace(&self, value: T) -> T {
|
||||
let mut lock = self.lock();
|
||||
mem::replace(&mut lock, value)
|
||||
}
|
||||
|
||||
/// Attempts to acquire a lock. IRQs will be disabled until the lock is released.
|
||||
pub fn lock(&self) -> IrqSafeSpinlockGuard<T> {
|
||||
// Disable IRQs to avoid IRQ handler trying to acquire the same lock
|
||||
@ -141,6 +148,12 @@ impl<T> IrqSafeSpinlock<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> IrqSafeSpinlock<T> {
|
||||
pub fn cloned(&self) -> T {
|
||||
self.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for IrqSafeSpinlockGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
|
@ -6,3 +6,10 @@ 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" }
|
||||
kernel-util = { path = "../kernel-util" }
|
||||
|
||||
log = "0.4.20"
|
||||
|
||||
[dev-dependencies]
|
||||
hosted-tests = { path = "../hosted-tests" }
|
||||
|
80
lib/vfs/src/device.rs
Normal file
80
lib/vfs/src/device.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::node::{CommonImpl, NodeRef};
|
||||
|
||||
pub trait BlockDevice {
|
||||
fn read(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
fn write(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn is_readable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn size(&self) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
pub trait CharDevice {
|
||||
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn is_readable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn is_terminal(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockDeviceWrapper(pub(crate) &'static dyn BlockDevice);
|
||||
#[derive(Clone)]
|
||||
pub struct CharDeviceWrapper(pub(crate) &'static dyn CharDevice);
|
||||
|
||||
impl BlockDeviceWrapper {
|
||||
pub fn is_readable(&self) -> bool {
|
||||
self.0.is_readable()
|
||||
}
|
||||
|
||||
pub fn is_writable(&self) -> bool {
|
||||
self.0.is_writable()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonImpl for BlockDeviceWrapper {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
self.0.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl CharDeviceWrapper {
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
self.0.is_terminal()
|
||||
}
|
||||
|
||||
pub fn is_readable(&self) -> bool {
|
||||
self.0.is_readable()
|
||||
}
|
||||
|
||||
pub fn is_writable(&self) -> bool {
|
||||
self.0.is_writable()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonImpl for CharDeviceWrapper {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
84
lib/vfs/src/file/device.rs
Normal file
84
lib/vfs/src/file/device.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
use yggdrasil_abi::{error::Error, io::SeekFrom};
|
||||
|
||||
use crate::{
|
||||
device::{BlockDeviceWrapper, CharDeviceWrapper},
|
||||
node::NodeRef,
|
||||
};
|
||||
|
||||
pub struct BlockFile {
|
||||
pub(super) device: BlockDeviceWrapper,
|
||||
pub(super) node: NodeRef,
|
||||
pub(super) position: Cell<u64>,
|
||||
pub(super) read: bool,
|
||||
pub(super) write: bool,
|
||||
}
|
||||
|
||||
pub struct CharFile {
|
||||
pub(super) device: CharDeviceWrapper,
|
||||
pub(super) node: NodeRef,
|
||||
pub(super) read: bool,
|
||||
pub(super) write: bool,
|
||||
}
|
||||
|
||||
impl BlockFile {
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
let pos = self.position.get();
|
||||
let count = self.device.0.read(pos, buf)?;
|
||||
self.position.set(pos + count as u64);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
let pos = self.position.get();
|
||||
let count = self.device.0.write(pos, buf)?;
|
||||
self.position.set(pos + count as u64);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
||||
let pos = self.position.get();
|
||||
let newpos = match from {
|
||||
SeekFrom::Current(off) => {
|
||||
let newpos = i64::try_from(pos).unwrap() + off;
|
||||
if newpos < 0 {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
newpos as u64
|
||||
}
|
||||
SeekFrom::Start(pos) => pos,
|
||||
SeekFrom::End(off) => {
|
||||
let size = i64::try_from(self.device.0.size()?).unwrap();
|
||||
let newpos = size + off;
|
||||
|
||||
if newpos < 0 {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
newpos as u64
|
||||
}
|
||||
};
|
||||
|
||||
self.position.set(newpos);
|
||||
Ok(newpos)
|
||||
}
|
||||
}
|
||||
|
||||
impl CharFile {
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
if self.read {
|
||||
self.device.0.read(buf)
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
if self.write {
|
||||
self.device.0.write(buf)
|
||||
} else {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
}
|
||||
}
|
106
lib/vfs/src/file/directory.rs
Normal file
106
lib/vfs/src/file/directory.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use core::{cell::Cell, mem::MaybeUninit, str::FromStr};
|
||||
|
||||
use yggdrasil_abi::{error::Error, io::DirectoryEntry, util::FixedString};
|
||||
|
||||
use crate::node::NodeRef;
|
||||
|
||||
use super::DirectoryOpenPosition;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(super) enum DirectoryCachePosition {
|
||||
Dot,
|
||||
DotDot,
|
||||
Index(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(super) enum DirectoryPosition {
|
||||
Cache(DirectoryCachePosition),
|
||||
Physical(u64),
|
||||
}
|
||||
|
||||
pub struct DirectoryFile {
|
||||
pub(super) node: NodeRef,
|
||||
pub(super) position: Cell<DirectoryPosition>,
|
||||
}
|
||||
|
||||
impl DirectoryFile {
|
||||
pub(super) fn read_cached(
|
||||
node: &NodeRef,
|
||||
mut pos: DirectoryCachePosition,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, DirectoryCachePosition), Error> {
|
||||
let directory = node.as_directory()?;
|
||||
let children = directory.children.lock();
|
||||
let mut rem = entries.len();
|
||||
let mut off = 0;
|
||||
|
||||
while rem != 0 {
|
||||
let entry = match pos {
|
||||
DirectoryCachePosition::Dot => {
|
||||
pos = DirectoryCachePosition::DotDot;
|
||||
Some((FixedString::from_str(".").unwrap(), node.clone()))
|
||||
}
|
||||
DirectoryCachePosition::DotDot => {
|
||||
pos = DirectoryCachePosition::Index(0);
|
||||
Some((FixedString::from_str("..").unwrap(), node.parent()))
|
||||
}
|
||||
DirectoryCachePosition::Index(index) if let Some((name, node)) = children.get(index) => {
|
||||
pos = DirectoryCachePosition::Index(index + 1);
|
||||
Some((FixedString::from_str(name)?, node.clone()))
|
||||
}
|
||||
DirectoryCachePosition::Index(_) => None
|
||||
};
|
||||
|
||||
let Some((name, node)) = entry else {
|
||||
break;
|
||||
};
|
||||
|
||||
let ty = node.ty();
|
||||
|
||||
entries[off].write(DirectoryEntry { name, ty });
|
||||
|
||||
off += 1;
|
||||
rem -= 1;
|
||||
}
|
||||
|
||||
Ok((off, pos))
|
||||
}
|
||||
|
||||
pub(super) fn read_physical(
|
||||
node: &NodeRef,
|
||||
pos: u64,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
node.read_directory(pos, entries)
|
||||
}
|
||||
|
||||
pub(super) fn read_entries(
|
||||
&self,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<usize, Error> {
|
||||
let pos = self.position.get();
|
||||
let (count, pos) = match pos {
|
||||
DirectoryPosition::Cache(pos) => {
|
||||
let (count, pos) = DirectoryFile::read_cached(&self.node, pos, entries)?;
|
||||
(count, DirectoryPosition::Cache(pos))
|
||||
}
|
||||
DirectoryPosition::Physical(off) => {
|
||||
let (count, pos) = DirectoryFile::read_physical(&self.node, off, entries)?;
|
||||
(count, DirectoryPosition::Physical(pos))
|
||||
}
|
||||
};
|
||||
self.position.set(pos);
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DirectoryOpenPosition> for DirectoryPosition {
|
||||
fn from(value: DirectoryOpenPosition) -> Self {
|
||||
match value {
|
||||
DirectoryOpenPosition::FromCache => Self::Cache(DirectoryCachePosition::Dot),
|
||||
DirectoryOpenPosition::FromPhysical(off) => Self::Physical(off),
|
||||
}
|
||||
}
|
||||
}
|
500
lib/vfs/src/file/mod.rs
Normal file
500
lib/vfs/src/file/mod.rs
Normal file
@ -0,0 +1,500 @@
|
||||
use core::{any::Any, cell::Cell, fmt, mem::MaybeUninit};
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DirectoryEntry, OpenOptions, SeekFrom},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
device::{BlockDeviceWrapper, CharDeviceWrapper},
|
||||
node::NodeRef,
|
||||
traits::{Read, Seek, Write},
|
||||
};
|
||||
|
||||
use self::{
|
||||
device::{BlockFile, CharFile},
|
||||
directory::DirectoryFile,
|
||||
regular::RegularFile,
|
||||
};
|
||||
|
||||
mod device;
|
||||
mod directory;
|
||||
mod regular;
|
||||
|
||||
pub enum DirectoryOpenPosition {
|
||||
FromPhysical(u64),
|
||||
FromCache,
|
||||
}
|
||||
|
||||
pub type FileRef = Arc<File>;
|
||||
|
||||
// TODO some kind of a mutex instead?
|
||||
pub enum File {
|
||||
Directory(DirectoryFile),
|
||||
Regular(RegularFile),
|
||||
Block(BlockFile),
|
||||
Char(CharFile),
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
|
||||
let position = Cell::new(position.into());
|
||||
Arc::new(Self::Directory(DirectoryFile { node, position }))
|
||||
}
|
||||
|
||||
pub fn regular(
|
||||
node: NodeRef,
|
||||
position: u64,
|
||||
instance_data: Option<Box<dyn Any>>,
|
||||
opts: OpenOptions,
|
||||
) -> Arc<Self> {
|
||||
let read = opts.contains(OpenOptions::READ);
|
||||
let write = opts.contains(OpenOptions::WRITE);
|
||||
|
||||
Arc::new(Self::Regular(RegularFile {
|
||||
node,
|
||||
read,
|
||||
write,
|
||||
instance_data,
|
||||
position: Cell::new(position),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn block(
|
||||
device: BlockDeviceWrapper,
|
||||
node: NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<Arc<Self>, Error> {
|
||||
let read = opts.contains(OpenOptions::READ);
|
||||
let write = opts.contains(OpenOptions::WRITE);
|
||||
|
||||
if read && !device.is_readable() {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
if write && !device.is_writable() {
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self::Block(BlockFile {
|
||||
device,
|
||||
node,
|
||||
position: Cell::new(0),
|
||||
read,
|
||||
write,
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn char(
|
||||
device: CharDeviceWrapper,
|
||||
node: NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<Arc<Self>, Error> {
|
||||
let read = opts.contains(OpenOptions::READ);
|
||||
let write = opts.contains(OpenOptions::WRITE);
|
||||
|
||||
if read && !device.is_readable() {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
if write && !device.is_writable() {
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self::Char(CharFile {
|
||||
device,
|
||||
node,
|
||||
read,
|
||||
write,
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::Directory(dir) => dir.read_entries(entries),
|
||||
_ => Err(Error::NotADirectory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for File {
|
||||
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::Regular(file) => file.read(buf),
|
||||
Self::Block(file) => file.read(buf),
|
||||
Self::Char(file) => file.read(buf),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for File {
|
||||
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::Regular(file) => file.write(buf),
|
||||
Self::Block(file) => file.write(buf),
|
||||
Self::Char(file) => file.write(buf),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for File {
|
||||
fn tell(&self) -> Result<u64, Error> {
|
||||
match self {
|
||||
Self::Regular(file) => Ok(file.position.get()),
|
||||
Self::Block(file) => Ok(file.position.get()),
|
||||
Self::Char(_) => Err(Error::InvalidOperation),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
|
||||
fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
||||
match self {
|
||||
Self::Regular(file) => file.seek(from),
|
||||
Self::Block(file) => file.seek(from),
|
||||
Self::Char(_) => Err(Error::InvalidOperation),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for File {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Regular(file) => f
|
||||
.debug_struct("RegularFile")
|
||||
.field("position", &file.position.get())
|
||||
.field("read", &file.read)
|
||||
.field("write", &file.write)
|
||||
.finish_non_exhaustive(),
|
||||
Self::Block(file) => f
|
||||
.debug_struct("BlockFile")
|
||||
.field("position", &file.position.get())
|
||||
.field("read", &file.read)
|
||||
.field("write", &file.write)
|
||||
.finish_non_exhaustive(),
|
||||
Self::Char(file) => f
|
||||
.debug_struct("CharFile")
|
||||
.field("read", &file.read)
|
||||
.field("write", &file.write)
|
||||
.finish_non_exhaustive(),
|
||||
Self::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::{any::Any, cell::RefCell, mem::MaybeUninit, str::FromStr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use kernel_util::sync::IrqSafeSpinlock;
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
|
||||
util::FixedString,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
device::{BlockDevice, CharDevice},
|
||||
file::DirectoryOpenPosition,
|
||||
node::{CommonImpl, DirectoryImpl, Node, NodeRef, RegularImpl},
|
||||
traits::{Read, Seek, Write},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn physical_dir_read() {
|
||||
struct D {
|
||||
entries: Vec<(String, NodeRef)>,
|
||||
}
|
||||
struct F;
|
||||
|
||||
impl CommonImpl for D {}
|
||||
impl DirectoryImpl for D {
|
||||
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
||||
Ok(DirectoryOpenPosition::FromPhysical(0))
|
||||
}
|
||||
|
||||
fn read_entries(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
pos: u64,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
let pos = pos as usize;
|
||||
if pos == self.entries.len() {
|
||||
return Ok((0, pos as u64));
|
||||
}
|
||||
|
||||
let count = core::cmp::min(entries.len(), self.entries.len() - pos);
|
||||
for i in 0..count {
|
||||
let (name, node) = &self.entries[i];
|
||||
let entry = DirectoryEntry {
|
||||
name: FixedString::from_str(name)?,
|
||||
ty: node.ty(),
|
||||
};
|
||||
|
||||
entries[i].write(entry);
|
||||
}
|
||||
|
||||
Ok((count, (pos + count) as u64))
|
||||
}
|
||||
}
|
||||
|
||||
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 f = d.open_directory().unwrap();
|
||||
|
||||
let mut entries = [MaybeUninit::uninit(); 16];
|
||||
let count = f.read_dir(&mut entries).unwrap();
|
||||
assert_eq!(count, 3);
|
||||
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
||||
&[
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str("f1").unwrap(),
|
||||
ty: FileType::File,
|
||||
},
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str("f2").unwrap(),
|
||||
ty: FileType::File,
|
||||
},
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str("f3").unwrap(),
|
||||
ty: FileType::File
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
let count = f.read_dir(&mut entries).unwrap();
|
||||
assert_eq!(count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_dir_read() {
|
||||
struct D;
|
||||
|
||||
impl CommonImpl for D {}
|
||||
impl DirectoryImpl for D {
|
||||
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
||||
Ok(DirectoryOpenPosition::FromCache)
|
||||
}
|
||||
}
|
||||
|
||||
let d = Node::directory(D);
|
||||
let child = Node::directory(D);
|
||||
|
||||
d.add_child("child1", child).unwrap();
|
||||
|
||||
let f = d.open_directory().unwrap();
|
||||
|
||||
let mut entries = [MaybeUninit::uninit(); 16];
|
||||
let count = f.read_dir(&mut entries).unwrap();
|
||||
assert_eq!(count, 3);
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
||||
&[
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str(".").unwrap(),
|
||||
ty: FileType::Directory
|
||||
},
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str("..").unwrap(),
|
||||
ty: FileType::Directory
|
||||
},
|
||||
DirectoryEntry {
|
||||
name: FixedString::from_str("child1").unwrap(),
|
||||
ty: FileType::Directory
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
let count = f.read_dir(&mut entries).unwrap();
|
||||
assert_eq!(count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_read_write() {
|
||||
struct F {
|
||||
data: Arc<RefCell<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl CommonImpl for F {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(self.data.borrow().len())
|
||||
}
|
||||
}
|
||||
impl RegularImpl for F {
|
||||
fn open(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
_opts: OpenOptions,
|
||||
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
|
||||
Ok((0, None))
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
_instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
let pos = pos as usize;
|
||||
let data = self.data.borrow();
|
||||
if pos >= data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let count = core::cmp::min(data.len() - pos, buf.len());
|
||||
buf[..count].copy_from_slice(&data[pos..pos + count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn write(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
_instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
let pos = pos as usize;
|
||||
let mut data = self.data.borrow_mut();
|
||||
data.resize(pos + buf.len(), 0);
|
||||
data[pos..pos + buf.len()].copy_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
let data = Arc::new(RefCell::new(vec![]));
|
||||
let node = Node::regular(F { data: data.clone() });
|
||||
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
||||
let mut buf = [0; 512];
|
||||
|
||||
assert_eq!(&*data.borrow(), &[]);
|
||||
assert_eq!(file.tell().unwrap(), 0);
|
||||
|
||||
assert_eq!(file.write(b"Hello").unwrap(), 5);
|
||||
assert_eq!(file.tell().unwrap(), 5);
|
||||
assert_eq!(&*data.borrow(), b"Hello");
|
||||
|
||||
assert_eq!(file.seek(SeekFrom::End(-2)).unwrap(), 3);
|
||||
assert_eq!(file.tell().unwrap(), 3);
|
||||
|
||||
assert_eq!(file.write(b"123456").unwrap(), 6);
|
||||
assert_eq!(file.tell().unwrap(), 9);
|
||||
assert_eq!(&*data.borrow(), b"Hel123456");
|
||||
|
||||
assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 7);
|
||||
assert_eq!(file.tell().unwrap(), 9);
|
||||
assert_eq!(&buf[..7], b"l123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_device() {
|
||||
struct B {
|
||||
data: Arc<IrqSafeSpinlock<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl BlockDevice for B {
|
||||
fn read(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
let data = self.data.lock();
|
||||
let pos = pos as usize;
|
||||
if pos >= data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count = core::cmp::min(data.len() - pos, buf.len());
|
||||
buf[..count].copy_from_slice(&data[pos..pos + count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn write(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
|
||||
let mut data = self.data.lock();
|
||||
let pos = pos as usize;
|
||||
if pos >= data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let count = core::cmp::min(data.len() - pos, buf.len());
|
||||
data[pos..pos + count].copy_from_slice(&buf[..count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<usize, Error> {
|
||||
Ok(self.data.lock().len())
|
||||
}
|
||||
}
|
||||
|
||||
let vec = vec![0; 1024];
|
||||
let state = Arc::new(IrqSafeSpinlock::new(vec));
|
||||
let data = state.clone();
|
||||
let dev = Box::leak(Box::new(B { data }));
|
||||
let mut buf = [0; 512];
|
||||
|
||||
let node = Node::block(dev);
|
||||
|
||||
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
||||
|
||||
assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 1024);
|
||||
assert_eq!(file.write(b"12345").unwrap(), 0);
|
||||
assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
|
||||
assert_eq!(file.write(b"12345").unwrap(), 5);
|
||||
assert_eq!(&state.lock()[..6], b"12345\0");
|
||||
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 512);
|
||||
assert_eq!(buf, [0; 512]);
|
||||
assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
|
||||
assert_eq!(file.read(&mut buf[..8]).unwrap(), 8);
|
||||
assert_eq!(&buf[..8], b"345\0\0\0\0\0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_device() {
|
||||
struct C;
|
||||
|
||||
impl CharDevice for C {
|
||||
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
buf.fill(b'@');
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn is_writable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
static DEV: C = C;
|
||||
|
||||
let node = Node::char(&DEV);
|
||||
let mut buf = [0; 512];
|
||||
|
||||
let err = node.open(OpenOptions::WRITE).unwrap_err();
|
||||
assert_eq!(err, Error::ReadOnly);
|
||||
|
||||
let file = node.open(OpenOptions::READ).unwrap();
|
||||
assert_eq!(file.tell().unwrap_err(), Error::InvalidOperation);
|
||||
assert_eq!(
|
||||
file.seek(SeekFrom::Start(10)).unwrap_err(),
|
||||
Error::InvalidOperation
|
||||
);
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 512);
|
||||
assert_eq!(buf, [b'@'; 512]);
|
||||
|
||||
assert_eq!(file.write(b"1234").unwrap_err(), Error::ReadOnly);
|
||||
}
|
||||
}
|
85
lib/vfs/src/file/regular.rs
Normal file
85
lib/vfs/src/file/regular.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{any::Any, cell::Cell};
|
||||
use yggdrasil_abi::{error::Error, io::SeekFrom};
|
||||
|
||||
use crate::node::NodeRef;
|
||||
|
||||
pub struct RegularFile {
|
||||
pub(super) node: NodeRef,
|
||||
pub(super) read: bool,
|
||||
pub(super) write: bool,
|
||||
pub(super) instance_data: Option<Box<dyn Any>>,
|
||||
pub(super) position: Cell<u64>,
|
||||
}
|
||||
|
||||
impl RegularFile {
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
if !self.read {
|
||||
return Err(Error::InvalidFile);
|
||||
}
|
||||
let reg = self.node.as_regular()?;
|
||||
let pos = self.position.get();
|
||||
let count = reg.read(&self.node, self.instance_data.as_ref(), pos, buf)?;
|
||||
self.position.set(pos + count as u64);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
if !self.write {
|
||||
return Err(Error::InvalidFile);
|
||||
}
|
||||
let reg = self.node.as_regular()?;
|
||||
let pos = self.position.get();
|
||||
let count = reg.write(&self.node, self.instance_data.as_ref(), pos, buf)?;
|
||||
self.position.set(pos + count as u64);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
// TODO should seek beyond the end of a read-only file be allowed?
|
||||
pub fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
||||
let pos = self.position.get();
|
||||
let newpos = match from {
|
||||
SeekFrom::Current(off) => {
|
||||
let newpos = i64::try_from(pos).unwrap() + off;
|
||||
if newpos < 0 {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
newpos as u64
|
||||
}
|
||||
SeekFrom::Start(pos) => pos,
|
||||
SeekFrom::End(off) => {
|
||||
let reg = self.node.as_regular()?;
|
||||
let size = i64::try_from(reg.size(&self.node)?).unwrap();
|
||||
let newpos = size + off;
|
||||
|
||||
if newpos < 0 {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
newpos as u64
|
||||
}
|
||||
};
|
||||
|
||||
self.position.set(newpos);
|
||||
Ok(newpos)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RegularFile {
|
||||
fn drop(&mut self) {
|
||||
let reg = match self.node.as_regular() {
|
||||
Ok(reg) => reg,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"RegularFile::Drop: self.node.as_regular() failed: {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = reg.close(&self.node, self.instance_data.as_ref()) {
|
||||
log::warn!("RegularFile::Drop: close() failed: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,17 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![feature(trait_upcasting, if_let_guard, maybe_uninit_slice, trait_alias)]
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate hosted_tests;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) mod device;
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod node;
|
||||
pub(crate) mod traits;
|
||||
|
||||
pub use device::{BlockDevice, CharDevice};
|
||||
pub use file::{DirectoryOpenPosition, File, FileRef};
|
||||
pub use node::{impls, Node, NodeRef};
|
||||
pub use traits::{Read, Seek, Write};
|
||||
|
294
lib/vfs/src/node/impls.rs
Normal file
294
lib/vfs/src/node/impls.rs
Normal file
@ -0,0 +1,294 @@
|
||||
use core::{any::Any, cell::RefCell, marker::PhantomData, str::FromStr};
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
|
||||
use yggdrasil_abi::{error::Error, io::OpenOptions};
|
||||
|
||||
use super::{CommonImpl, Node, NodeRef, RegularImpl};
|
||||
|
||||
pub trait SliceRead {
|
||||
fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize;
|
||||
}
|
||||
|
||||
pub trait SliceWrite {
|
||||
fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize;
|
||||
}
|
||||
|
||||
trait IntoInstanceData {
|
||||
fn into_instance_data(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> SliceRead for T {
|
||||
fn read_slice(&self, pos: usize, buf: &mut [u8]) -> usize {
|
||||
let value = self.as_ref();
|
||||
if pos >= value.len() {
|
||||
return 0;
|
||||
}
|
||||
let count = core::cmp::min(value.len() - pos, buf.len());
|
||||
buf[..count].copy_from_slice(&value[pos..pos + count]);
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
impl SliceWrite for Vec<u8> {
|
||||
fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize {
|
||||
if offset + buf.len() > self.len() {
|
||||
self.resize(offset + buf.len(), 0);
|
||||
}
|
||||
self[offset..offset + buf.len()].copy_from_slice(buf);
|
||||
buf.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString> IntoInstanceData for T {
|
||||
fn into_instance_data(&self) -> Vec<u8> {
|
||||
self.to_string().as_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadFn<T> = Fn() -> Result<T, Error>;
|
||||
pub trait WriteFn<T> = Fn(T) -> Result<(), Error>;
|
||||
|
||||
enum FnNodeData {
|
||||
Read(Vec<u8>),
|
||||
Write(RefCell<Vec<u8>>),
|
||||
}
|
||||
|
||||
pub struct ReadOnlyFnNode<T: ToString, R: ReadFn<T>> {
|
||||
read: R,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct FnNode<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> {
|
||||
read: R,
|
||||
write: W,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
|
||||
pub fn new(read: R) -> NodeRef {
|
||||
Node::regular(Self::new_impl(read))
|
||||
}
|
||||
|
||||
pub const fn new_impl(read: R) -> Self {
|
||||
Self {
|
||||
read,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString, R: ReadFn<T>> CommonImpl for ReadOnlyFnNode<T, R> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString + 'static, R: ReadFn<T>> RegularImpl for ReadOnlyFnNode<T, R> {
|
||||
fn open(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
|
||||
if opts.contains(OpenOptions::WRITE) {
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
let t = (self.read)()?;
|
||||
Ok((0, Some(Box::new(t.into_instance_data()))))
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
let value = instance.unwrap().downcast_ref::<Vec<u8>>().unwrap();
|
||||
Ok(value.read_slice(pos as usize, buf))
|
||||
}
|
||||
|
||||
fn write(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
_instance: Option<&Box<dyn Any>>,
|
||||
_pos: u64,
|
||||
_buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
// Read-write FnNode
|
||||
|
||||
impl FnNodeData {
|
||||
fn write() -> Self {
|
||||
Self::Write(RefCell::new(Vec::new()))
|
||||
}
|
||||
|
||||
fn read<T: IntoInstanceData>(value: T) -> Self {
|
||||
Self::Read(value.into_instance_data())
|
||||
}
|
||||
|
||||
fn as_read(&self) -> Result<&Vec<u8>, Error> {
|
||||
match self {
|
||||
Self::Read(r) => Ok(r),
|
||||
Self::Write(_) => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_write(&self) -> Result<&RefCell<Vec<u8>>, Error> {
|
||||
match self {
|
||||
Self::Write(w) => Ok(w),
|
||||
Self::Read(_) => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString + FromStr + 'static, R: ReadFn<T> + 'static, W: WriteFn<T> + 'static>
|
||||
FnNode<T, R, W>
|
||||
{
|
||||
pub fn new(read: R, write: W) -> NodeRef {
|
||||
Node::regular(Self::new_impl(read, write))
|
||||
}
|
||||
|
||||
pub const fn new_impl(read: R, write: W) -> Self {
|
||||
Self {
|
||||
read,
|
||||
write,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> CommonImpl for FnNode<T, R, W> {
|
||||
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> RegularImpl for FnNode<T, R, W> {
|
||||
fn open(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
|
||||
if opts.contains(OpenOptions::READ | OpenOptions::WRITE) {
|
||||
Err(Error::InvalidOperation)
|
||||
} else if opts.contains(OpenOptions::WRITE) {
|
||||
Ok((0, Some(Box::new(FnNodeData::write()))))
|
||||
} else if opts.contains(OpenOptions::READ) {
|
||||
let t = (self.read)()?;
|
||||
Ok((0, Some(Box::new(FnNodeData::read(t)))))
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&self, _node: &NodeRef, instance: Option<&Box<dyn Any>>) -> Result<(), Error> {
|
||||
if let Ok(write) = instance
|
||||
.unwrap()
|
||||
.downcast_ref::<FnNodeData>()
|
||||
.unwrap()
|
||||
.as_write()
|
||||
{
|
||||
let write = write.borrow();
|
||||
// Flush write
|
||||
let str = core::str::from_utf8(write.as_ref())
|
||||
.map_err(|_| Error::InvalidArgument)?
|
||||
.trim();
|
||||
let t = T::from_str(str).map_err(|_| Error::InvalidArgument)?;
|
||||
|
||||
(self.write)(t)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
let instance = instance.unwrap().downcast_ref::<FnNodeData>().unwrap();
|
||||
Ok(instance.as_read()?.read_slice(pos as usize, buf))
|
||||
}
|
||||
|
||||
fn write(
|
||||
&self,
|
||||
_node: &NodeRef,
|
||||
instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
let instance = instance.unwrap().downcast_ref::<FnNodeData>().unwrap();
|
||||
Ok(instance.as_write()?.borrow_mut().write_slice(pos as _, buf))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn const_value_node<T: ToString + Clone + 'static>(value: T) -> NodeRef {
|
||||
ReadOnlyFnNode::new(move || Ok(value.clone()))
|
||||
}
|
||||
|
||||
pub fn value_node<T: ToString + FromStr + Clone + 'static>(value: T) -> NodeRef {
|
||||
let rd_state = Arc::new(RefCell::new(value));
|
||||
let wr_state = rd_state.clone();
|
||||
|
||||
FnNode::new(
|
||||
move || Ok(rd_state.borrow().clone()),
|
||||
move |t| {
|
||||
*wr_state.borrow_mut() = t;
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yggdrasil_abi::io::OpenOptions;
|
||||
|
||||
use crate::{
|
||||
node::impls::{const_value_node, value_node},
|
||||
traits::{Read, Seek, Write},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn read_only_fn_node() {
|
||||
let node = const_value_node("abcdef");
|
||||
let file = node.open(OpenOptions::READ).unwrap();
|
||||
let mut buf = [0; 512];
|
||||
|
||||
assert_eq!(file.tell().unwrap(), 0);
|
||||
|
||||
assert_eq!(file.read(&mut buf[..3]).unwrap(), 3);
|
||||
assert_eq!(&buf[..3], b"abc");
|
||||
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 3);
|
||||
assert_eq!(&buf[..3], b"def");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_node() {
|
||||
let node = value_node(1234);
|
||||
let mut buf = [0; 512];
|
||||
|
||||
// Try read the value
|
||||
let file = node.open(OpenOptions::READ).unwrap();
|
||||
assert_eq!(file.tell().unwrap(), 0);
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 4);
|
||||
assert_eq!(&buf[..4], b"1234");
|
||||
|
||||
// Try write the value
|
||||
let file = node.open(OpenOptions::WRITE).unwrap();
|
||||
assert_eq!(file.tell().unwrap(), 0);
|
||||
assert_eq!(file.write(b"654321").unwrap(), 6);
|
||||
drop(file);
|
||||
|
||||
// Try read the value again
|
||||
let file = node.open(OpenOptions::READ).unwrap();
|
||||
assert_eq!(file.tell().unwrap(), 0);
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 6);
|
||||
assert_eq!(&buf[..6], b"654321");
|
||||
}
|
||||
}
|
196
lib/vfs/src/node/mod.rs
Normal file
196
lib/vfs/src/node/mod.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use core::any::Any;
|
||||
|
||||
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
|
||||
use kernel_util::sync::IrqSafeSpinlock;
|
||||
use yggdrasil_abi::{error::Error, io::FileType};
|
||||
|
||||
pub mod impls;
|
||||
mod traits;
|
||||
|
||||
// Node is implemented in the following modules
|
||||
mod ops;
|
||||
mod tree;
|
||||
|
||||
pub use traits::{CommonImpl, DirectoryImpl, RegularImpl};
|
||||
|
||||
use crate::device::{BlockDevice, BlockDeviceWrapper, CharDevice, CharDeviceWrapper};
|
||||
|
||||
pub type NodeRef = Arc<Node>;
|
||||
|
||||
pub(crate) struct DirectoryData {
|
||||
pub(crate) imp: Box<dyn DirectoryImpl>,
|
||||
pub(crate) children: IrqSafeSpinlock<Vec<(String, NodeRef)>>,
|
||||
}
|
||||
|
||||
enum NodeImpl {
|
||||
Regular(Box<dyn RegularImpl>),
|
||||
Directory(DirectoryData),
|
||||
Block(BlockDeviceWrapper),
|
||||
Char(CharDeviceWrapper),
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
data: NodeImpl,
|
||||
parent: IrqSafeSpinlock<Option<NodeRef>>,
|
||||
}
|
||||
|
||||
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()),
|
||||
});
|
||||
Arc::new(Self {
|
||||
data,
|
||||
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 block(device: &'static dyn BlockDevice) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data: NodeImpl::Block(BlockDeviceWrapper(device)),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn char(device: &'static dyn CharDevice) -> NodeRef {
|
||||
Arc::new(Self {
|
||||
data: NodeImpl::Char(CharDeviceWrapper(device)),
|
||||
parent: IrqSafeSpinlock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn data_as_any(&self) -> &dyn Any {
|
||||
match &self.data {
|
||||
NodeImpl::Directory(dir) => dir.imp.as_any(),
|
||||
NodeImpl::Regular(imp) => imp.as_any(),
|
||||
NodeImpl::Block(w) => w.as_any(),
|
||||
NodeImpl::Char(w) => w.as_any(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_as_common(&self) -> &dyn CommonImpl {
|
||||
match &self.data {
|
||||
NodeImpl::Directory(dir) => dir.imp.as_ref(),
|
||||
NodeImpl::Regular(imp) => imp.as_ref(),
|
||||
NodeImpl::Block(w) => w,
|
||||
NodeImpl::Char(w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_as_ref<T: 'static>(&self) -> &T {
|
||||
self.data_as_any().downcast_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> FileType {
|
||||
match &self.data {
|
||||
NodeImpl::Regular(_) => FileType::File,
|
||||
NodeImpl::Directory(_) => FileType::Directory,
|
||||
NodeImpl::Block(_) => FileType::Block,
|
||||
NodeImpl::Char(_) => FileType::Char,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_directory(&self) -> bool {
|
||||
matches!(&self.data, NodeImpl::Directory(_))
|
||||
}
|
||||
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
if let NodeImpl::Char(dev) = &self.data {
|
||||
dev.is_terminal()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_directory(&self) -> Result<&DirectoryData, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Directory(dir) => Ok(dir),
|
||||
_ => Err(Error::InvalidFile),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_regular(&self) -> Result<&dyn RegularImpl, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Regular(imp) => Ok(imp.as_ref()),
|
||||
_ => Err(Error::InvalidFile),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{CommonImpl, DirectoryImpl, Node, RegularImpl};
|
||||
|
||||
struct DummyDirectory;
|
||||
struct DummyFile;
|
||||
|
||||
impl CommonImpl for DummyDirectory {}
|
||||
impl DirectoryImpl for DummyDirectory {}
|
||||
|
||||
impl CommonImpl for DummyFile {}
|
||||
impl RegularImpl for DummyFile {}
|
||||
|
||||
#[test]
|
||||
fn dir_cache_add() {
|
||||
let d1 = Node::directory(DummyDirectory);
|
||||
let d2 = Node::directory(DummyDirectory);
|
||||
let f1 = Node::regular(DummyFile);
|
||||
|
||||
assert!(Arc::ptr_eq(&f1.parent(), &f1));
|
||||
assert_eq!(d1.children_len().unwrap(), 0);
|
||||
|
||||
d1.add_child("f1", f1.clone()).unwrap();
|
||||
assert!(Arc::ptr_eq(&f1.parent(), &d1));
|
||||
assert_eq!(d1.children_len().unwrap(), 1);
|
||||
|
||||
assert!(Arc::ptr_eq(&d2.parent(), &d2));
|
||||
d2.add_child("d1", d1.clone()).unwrap();
|
||||
assert!(Arc::ptr_eq(&f1.parent(), &d1));
|
||||
assert!(Arc::ptr_eq(&d1.parent(), &d2));
|
||||
assert_eq!(d1.children_len().unwrap(), 1);
|
||||
assert_eq!(d2.children_len().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_mem_dir_size_coherence() {
|
||||
let d = Node::directory(DummyDirectory);
|
||||
|
||||
for i in 0..10 {
|
||||
let name = format!("f{}", i);
|
||||
let node = Node::regular(DummyFile);
|
||||
|
||||
d.add_child(name, node).unwrap();
|
||||
assert_eq!(d.size().unwrap(), d.children_len().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_any() {
|
||||
struct AnyData {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl CommonImpl for AnyData {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl DirectoryImpl for AnyData {}
|
||||
|
||||
let d = Node::directory(AnyData { value: 1234 });
|
||||
let r = d.data_as_ref::<AnyData>();
|
||||
|
||||
assert_eq!(r.value, 1234);
|
||||
}
|
||||
}
|
47
lib/vfs/src/node/ops.rs
Normal file
47
lib/vfs/src/node/ops.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DirectoryEntry, OpenOptions},
|
||||
};
|
||||
|
||||
use crate::file::{File, FileRef};
|
||||
|
||||
use super::{Node, NodeImpl, NodeRef};
|
||||
|
||||
impl Node {
|
||||
pub fn open(self: &NodeRef, opts: OpenOptions) -> Result<FileRef, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Regular(imp) => {
|
||||
let (pos, instance) = imp.open(self, opts)?;
|
||||
Ok(File::regular(self.clone(), pos, instance, opts))
|
||||
}
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Directory
|
||||
|
||||
pub fn open_directory(self: &NodeRef) -> Result<FileRef, Error> {
|
||||
let dir = self.as_directory()?;
|
||||
let pos = dir.imp.open(self)?;
|
||||
Ok(File::directory(self.clone(), pos))
|
||||
}
|
||||
|
||||
pub fn read_directory(
|
||||
self: &NodeRef,
|
||||
pos: u64,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
self.as_directory()?.imp.read_entries(self, pos, entries)
|
||||
}
|
||||
|
||||
// Common
|
||||
|
||||
pub fn size(self: &NodeRef) -> Result<usize, Error> {
|
||||
self.data_as_common().size(self)
|
||||
}
|
||||
}
|
88
lib/vfs/src/node/traits.rs
Normal file
88
lib/vfs/src/node/traits.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{any::Any, mem::MaybeUninit};
|
||||
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DirectoryEntry, FileAttr, OpenOptions},
|
||||
};
|
||||
|
||||
use crate::file::DirectoryOpenPosition;
|
||||
|
||||
use super::NodeRef;
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait CommonImpl {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn metadata(&self, node: &NodeRef) -> Result<FileAttr, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait RegularImpl: CommonImpl {
|
||||
fn open(
|
||||
&self,
|
||||
node: &NodeRef,
|
||||
opts: OpenOptions,
|
||||
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn close(&self, node: &NodeRef, instance: Option<&Box<dyn Any>>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(
|
||||
&self,
|
||||
node: &NodeRef,
|
||||
instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
fn write(
|
||||
&self,
|
||||
node: &NodeRef,
|
||||
instance: Option<&Box<dyn Any>>,
|
||||
pos: u64,
|
||||
buf: &[u8],
|
||||
) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait DirectoryImpl: CommonImpl {
|
||||
fn open(&self, node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn read_entries(
|
||||
&self,
|
||||
node: &NodeRef,
|
||||
pos: u64,
|
||||
entries: &mut [MaybeUninit<DirectoryEntry>],
|
||||
) -> Result<(usize, u64), Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn lookup(&self, node: &NodeRef, name: &str) -> Result<NodeRef, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
|
||||
fn len(&self, node: &NodeRef) -> Result<usize, Error> {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
39
lib/vfs/src/node/tree.rs
Normal file
39
lib/vfs/src/node/tree.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
use super::{Node, NodeRef};
|
||||
|
||||
impl Node {
|
||||
pub fn parent(self: &NodeRef) -> NodeRef {
|
||||
self.parent.lock().as_ref().unwrap_or(self).clone()
|
||||
}
|
||||
|
||||
pub fn children_len(&self) -> Result<usize, Error> {
|
||||
let directory = self.as_directory()?;
|
||||
Ok(directory.children.lock().len())
|
||||
}
|
||||
|
||||
pub fn add_child<S: Into<String>>(
|
||||
self: &NodeRef,
|
||||
name: S,
|
||||
child: NodeRef,
|
||||
) -> Result<(), Error> {
|
||||
let name = name.into();
|
||||
let directory = self.as_directory()?;
|
||||
let mut children = directory.children.lock();
|
||||
|
||||
// TODO check if an entry already exists with such name
|
||||
|
||||
// if children.contains_key(&name) {
|
||||
// log::warn!("Directory cache already contains an entry: {:?}", name);
|
||||
// return Err(Error::AlreadyExists);
|
||||
// }
|
||||
|
||||
assert!(child.parent.replace(Some(self.clone())).is_none());
|
||||
children.push((name, child));
|
||||
// children.insert(name, child);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
17
lib/vfs/src/traits.rs
Normal file
17
lib/vfs/src/traits.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use yggdrasil_abi::{error::Error, io::SeekFrom};
|
||||
|
||||
pub trait Read {
|
||||
fn read(&self, buf: &mut [u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
pub trait Write {
|
||||
fn write(&self, buf: &[u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
pub trait Seek {
|
||||
fn seek(&self, from: SeekFrom) -> Result<u64, Error>;
|
||||
|
||||
fn tell(&self) -> Result<u64, Error> {
|
||||
self.seek(SeekFrom::Current(0))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user