fs: initial rework

This commit is contained in:
Mark Poliakov 2023-12-02 20:28:43 +02:00
parent 2d7568f829
commit aadd97fc8e
16 changed files with 1590 additions and 1 deletions

View 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]

View File

@ -0,0 +1,9 @@
#![no_std]
#[no_mangle]
fn __acquire_irq_guard() -> bool {
false
}
#[no_mangle]
fn __release_irq_guard(_: bool) {}

View File

@ -1,6 +1,7 @@
//! Synchronization primitives //! Synchronization primitives
use core::{ use core::{
cell::UnsafeCell, cell::UnsafeCell,
mem,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::atomic::{AtomicBool, AtomicUsize, Ordering}, 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. /// Attempts to acquire a lock. IRQs will be disabled until the lock is released.
pub fn lock(&self) -> IrqSafeSpinlockGuard<T> { pub fn lock(&self) -> IrqSafeSpinlockGuard<T> {
// Disable IRQs to avoid IRQ handler trying to acquire the same lock // 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> { impl<'a, T> Deref for IrqSafeSpinlockGuard<'a, T> {
type Target = T; type Target = T;

View File

@ -6,3 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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
View 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)
}
}

View 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)
}
}
}

View 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
View 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);
}
}

View 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);
}
}
}

View File

@ -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
View 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
View 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
View 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)
}
}

View 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
View 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
View 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))
}
}