fs: integrate the new vfs with the kernel

This commit is contained in:
Mark Poliakov 2023-12-05 10:27:56 +02:00
parent fbe05c1d4d
commit e431d49ffb
39 changed files with 1620 additions and 929 deletions

View File

@ -8,7 +8,9 @@ edition = "2021"
[dependencies]
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
vfs = { path = "../vfs" }
static_assertions = "1.1.0"
log = "0.4.20"
[features]
default = []

View File

@ -1,76 +1,35 @@
use core::{any::Any, cell::RefCell, marker::PhantomData};
use core::marker::PhantomData;
use alloc::boxed::Box;
use vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Node, NodeFlags, NodeRef};
use yggdrasil_abi::{error::Error, io::FileType};
use vfs::{CreateInfo, Vnode, VnodeImpl, VnodeKind, VnodeRef, DIR_POSITION_FROM_CACHE};
use yggdrasil_abi::{
error::Error,
io::{FileAttr, FileMode, FileType, OpenOptions},
};
use crate::{block::BlockAllocator, bvec::BVec, file::FileNode};
use crate::{block::BlockAllocator, file::FileNode};
pub(crate) struct DirectoryNode<A: BlockAllocator> {
uid: u32,
gid: u32,
mode: FileMode,
_pd: PhantomData<A>,
}
impl<A: BlockAllocator> VnodeImpl for DirectoryNode<A> {
fn create(&self, at: &VnodeRef, info: &CreateInfo) -> Result<VnodeRef, Error> {
let child = Vnode::new(info.name, info.kind);
match info.kind {
VnodeKind::Directory => child.set_data(Box::new(Self {
uid: info.uid,
gid: info.gid,
mode: info.mode,
_pd: PhantomData,
})),
VnodeKind::Regular => child.set_data(Box::new(FileNode {
data: RefCell::new(BVec::<A>::new()),
uid: info.uid,
gid: info.gid,
mode: info.mode,
})),
impl<A: BlockAllocator> DirectoryNode<A> {
pub fn new() -> NodeRef {
Node::directory(
Self { _pd: PhantomData },
NodeFlags::IN_MEMORY_SIZE | NodeFlags::IN_MEMORY_PROPS,
)
}
}
impl<A: BlockAllocator> CommonImpl for DirectoryNode<A> {}
impl<A: BlockAllocator> DirectoryImpl for DirectoryNode<A> {
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
Ok(DirectoryOpenPosition::FromCache)
}
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
match ty {
FileType::File => Ok(FileNode::<A>::new()),
FileType::Directory => Ok(DirectoryNode::<A>::new()),
_ => todo!(),
}
child.set_fs(at.fs().unwrap());
Ok(child)
}
fn open(
&self,
_node: &VnodeRef,
_opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
Ok((DIR_POSITION_FROM_CACHE, None))
}
fn remove(&self, _at: &VnodeRef, _name: &str) -> Result<(), Error> {
Ok(())
}
fn size(&self, node: &VnodeRef) -> Result<u64, Error> {
Ok(node.children().len() as u64)
}
fn metadata(&self, _node: &VnodeRef) -> Result<FileAttr, Error> {
Ok(FileAttr {
size: 0,
mode: self.mode,
ty: FileType::Directory,
})
}
}
impl<A: BlockAllocator> DirectoryNode<A> {
pub fn new(uid: u32, gid: u32, mode: FileMode) -> Self {
Self {
uid,
gid,
mode,
_pd: PhantomData,
}
}
}

View File

@ -1,27 +1,44 @@
use core::{any::Any, cell::RefCell};
use alloc::boxed::Box;
use vfs::{VnodeImpl, VnodeRef};
use yggdrasil_abi::{
error::Error,
io::{FileAttr, FileMode, FileType, OpenOptions},
};
use vfs::{CommonImpl, Node, NodeFlags, NodeRef, RegularImpl};
use yggdrasil_abi::{error::Error, io::OpenOptions};
use crate::{block::BlockAllocator, bvec::BVec};
pub(crate) struct FileNode<A: BlockAllocator> {
// TODO IrqSafeSpinlock
pub(crate) data: RefCell<BVec<'static, A>>,
pub(crate) uid: u32,
pub(crate) gid: u32,
pub(crate) mode: FileMode,
}
impl<A: BlockAllocator> VnodeImpl for FileNode<A> {
impl<A: BlockAllocator> FileNode<A> {
pub fn new() -> NodeRef {
Node::regular(
Self {
data: RefCell::new(BVec::new()),
},
NodeFlags::IN_MEMORY_PROPS,
)
}
}
impl<A: BlockAllocator> CommonImpl for FileNode<A> {
fn as_any(&self) -> &dyn Any {
self
}
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
Ok(self.data.borrow().size() as u64)
}
}
impl<A: BlockAllocator> RegularImpl for FileNode<A> {
fn open(
&self,
_node: &VnodeRef,
_node: &NodeRef,
opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
// TODO provide APPEND by vfs driver instead
if opts.contains(OpenOptions::APPEND) {
Ok((self.data.borrow().size() as u64, None))
} else {
@ -29,33 +46,27 @@ impl<A: BlockAllocator> VnodeImpl for FileNode<A> {
}
}
fn close(&self, _node: &VnodeRef) -> Result<(), Error> {
Ok(())
}
fn read(
&self,
_node: &VnodeRef,
_node: &NodeRef,
_instance: Option<&Box<dyn Any>>,
pos: u64,
_inner: Option<&mut Box<dyn Any>>,
data: &mut [u8],
buf: &mut [u8],
) -> Result<usize, Error> {
self.data.borrow().read(pos, data)
self.data.borrow().read(pos, buf)
}
fn write(&self, _node: &VnodeRef, pos: u64, data: &[u8]) -> Result<usize, Error> {
self.data.borrow_mut().write(pos, data)
fn write(
&self,
_node: &NodeRef,
_instance: Option<&Box<dyn Any>>,
pos: u64,
buf: &[u8],
) -> Result<usize, Error> {
self.data.borrow_mut().write(pos, buf)
}
fn size(&self, _node: &VnodeRef) -> Result<u64, Error> {
Ok(self.data.borrow().size() as u64)
}
fn metadata(&self, _node: &VnodeRef) -> Result<FileAttr, Error> {
Ok(FileAttr {
size: self.data.borrow().size() as u64,
mode: self.mode,
ty: FileType::File,
})
fn close(&self, _node: &NodeRef, _instance: Option<&Box<dyn Any>>) -> Result<(), Error> {
Ok(())
}
}

View File

@ -1,348 +1,192 @@
//! In-memory filesystem driver
#![no_std]
// #![warn(missing_docs)]
// #![allow(clippy::new_without_default)]
// #![feature(
// const_mut_refs,
// maybe_uninit_uninit_array,
// const_maybe_uninit_uninit_array,
// maybe_uninit_array_assume_init
// )]
//
// use core::{
// any::Any,
// cell::{Ref, RefCell},
// marker::PhantomData,
// };
//
// use alloc::{boxed::Box, rc::Rc};
// use block::BlockAllocator;
// use vfs::{BlockDevice, CreateInfo, Filesystem, Vnode, VnodeKind, VnodeRef};
// use yggdrasil_abi::{error::Error, io::FileMode, path};
//
// use crate::{bvec::BVec, dir::DirectoryNode, file::FileNode, tar::TarIterator};
//
// #[cfg(test)]
// extern crate std;
//
// extern crate alloc;
//
// #[cfg(test)]
// macro_rules! test_allocator_with_counter {
// ($counter:ident, $allocator:ident) => {
// static $counter: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
//
// struct $allocator;
//
// unsafe impl $crate::block::BlockAllocator for $allocator {
// fn alloc() -> Result<core::ptr::NonNull<u8>, yggdrasil_abi::error::Error> {
// let b = std::boxed::Box::into_raw(std::boxed::Box::new([0; $crate::block::SIZE]));
// $counter.fetch_add(1, core::sync::atomic::Ordering::Release);
// Ok(unsafe { core::ptr::NonNull::new_unchecked(b as _) })
// }
//
// unsafe fn dealloc(block: core::ptr::NonNull<u8>) {
// $counter.fetch_sub(1, core::sync::atomic::Ordering::Release);
// drop(std::boxed::Box::from_raw(
// block.as_ptr() as *mut [u8; $crate::block::SIZE]
// ));
// }
// }
// };
// }
//
// pub mod block;
// pub mod bvec;
//
// mod dir;
// mod file;
// mod tar;
//
// const DEFAULT_FILE_MODE: FileMode = FileMode::new(0o644);
// const DEFAULT_DIR_MODE: FileMode = FileMode::new(0o755);
//
// /// In-memory read/write filesystem
// pub struct MemoryFilesystem<A: BlockAllocator> {
// root: RefCell<Option<VnodeRef>>,
// _pd: PhantomData<A>,
// }
//
// impl<A: BlockAllocator> Filesystem for MemoryFilesystem<A> {
// fn dev(self: Rc<Self>) -> Option<&'static dyn BlockDevice> {
// todo!()
// }
//
// fn data(&self) -> Option<Ref<dyn Any>> {
// todo!()
// }
//
// fn root(self: Rc<Self>) -> Result<VnodeRef, Error> {
// Ok(self.root.borrow().clone().unwrap())
// }
// }
//
// impl<A: BlockAllocator> MemoryFilesystem<A> {
// fn make_path(
// self: &Rc<Self>,
// at: &VnodeRef,
// path: &str,
// kind: VnodeKind,
// create: bool,
// ) -> Result<VnodeRef, Error> {
// if path.is_empty() {
// return Ok(at.clone());
// }
// let (element, rest) = path::split_left(path);
// assert!(!element.is_empty());
//
// let node = at.lookup(element);
// let node = match node {
// Some(node) => node,
// None => {
// if !create {
// return Err(Error::DoesNotExist);
// }
//
// let node = self.create_node_initial(element, kind);
// // TODO require .tar's to have all the directories present to extract their metadata?
// if kind == VnodeKind::Directory {
// node.set_data(Box::new(DirectoryNode::<A>::new(0, 0, DEFAULT_DIR_MODE)));
// }
// at.add_child(node.clone());
//
// node
// }
// };
//
// if rest.is_empty() {
// Ok(node)
// } else {
// assert!(node.is_directory());
// self.make_path(&node, rest, kind, create)
// }
// }
//
// fn create_node_initial(self: &Rc<Self>, name: &str, kind: VnodeKind) -> VnodeRef {
// assert!(!name.is_empty());
// assert!(!name.contains('/'));
//
// let node = Vnode::new(name, kind);
// node.set_fs(self.clone());
//
// // match kind {
// // VnodeKind::Directory => node.set_data(Box::new(DirectoryNode::<A>::new(
// // info.uid, info.gid, info.mode,
// // ))),
// // VnodeKind::Regular => {}
// // _ => todo!(),
// // }
//
// node
// }
//
// fn from_slice_internal(self: &Rc<Self>, tar_data: &'static [u8]) -> Result<VnodeRef, Error> {
// let root = Vnode::new("", VnodeKind::Directory);
// root.set_fs(self.clone());
// root.set_data(Box::new(DirectoryNode::<A>::new(0, 0, DEFAULT_DIR_MODE)));
//
// // 1. Create paths in tar
// for item in TarIterator::new(tar_data) {
// let Ok((hdr, _)) = item else {
// return Err(Error::InvalidArgument);
// };
//
// let path = hdr.name.as_str()?.trim_matches('/');
// let (dirname, filename) = path::split_right(path);
// let parent = self.make_path(&root, dirname, VnodeKind::Directory, true)?;
// let node = self.create_node_initial(filename, hdr.node_kind());
//
// parent.add_child(node);
// }
//
// // 2. Associate files with their data
// for item in TarIterator::new(tar_data) {
// let Ok((hdr, data)) = item else {
// panic!("Unreachable");
// };
//
// let path = hdr.name.as_str()?.trim_matches('/');
// let node = self.make_path(&root, path, VnodeKind::Directory, false)?;
// assert_eq!(node.kind(), hdr.node_kind());
//
// if hdr.node_kind() == VnodeKind::Regular {
// let uid = usize::from(&hdr.uid).try_into().unwrap();
// let gid = usize::from(&hdr.gid).try_into().unwrap();
// let mode = convert_mode(usize::from(&hdr.mode))?;
//
// let data = data.unwrap();
// let bvec = BVec::<A>::try_from(data)?;
// assert_eq!(bvec.size(), data.len());
// node.set_data(Box::new(FileNode {
// uid,
// gid,
// mode,
// data: RefCell::new(bvec),
// }));
// }
// }
//
// Ok(root)
// }
//
// /// Constructs a filesystem tree from a tar image in memory
// pub fn from_slice(tar_data: &'static [u8]) -> Result<Rc<Self>, Error> {
// let fs = Rc::new(Self {
// root: RefCell::new(None),
// _pd: PhantomData,
// });
// let root = fs.from_slice_internal(tar_data)?;
// fs.root.replace(Some(root));
//
// Ok(fs)
// }
//
// /// Constructs an empty memory filesystem
// pub fn empty() -> Rc<Self> {
// let fs = Rc::new(Self {
// root: RefCell::new(None),
// _pd: PhantomData,
// });
// let root = Vnode::new("", VnodeKind::Directory);
// root.set_data(Box::new(DirectoryNode::<A>::new(0, 0, DEFAULT_DIR_MODE)));
// root.set_fs(fs.clone());
// fs.root.replace(Some(root));
// fs
// }
// }
//
// fn convert_mode(mode: usize) -> Result<FileMode, Error> {
// Ok(FileMode::new(mode as u32 & 0o777))
// }
//
// #[cfg(test)]
// mod tests {
// use core::sync::atomic::Ordering;
// use std::rc::Rc;
// use vfs::{Filesystem, IoContext, Read, Seek, VnodeKind, Write};
// use yggdrasil_abi::io::{FileMode, OpenOptions, SeekFrom};
//
// use crate::MemoryFilesystem;
//
// #[repr(C)]
// struct AlignedTo<Align, Bytes: ?Sized> {
// _align: [Align; 0],
// bytes: Bytes,
// }
//
// static TEST_IMAGE: &'static AlignedTo<u64, [u8]> = &AlignedTo {
// _align: [],
// bytes: *include_bytes!("../test/test_image.tar"),
// };
//
// #[test]
// fn test_memfs_construction() {
// fn check_file(ioctx: &IoContext, path: &str, expected_data: &str) {
// let node = ioctx.find(None, path, false, false).unwrap();
//
// assert_eq!(node.kind(), VnodeKind::Regular);
// assert_eq!(node.size().unwrap(), expected_data.len() as u64);
//
// let file = node.open(OpenOptions::READ).unwrap();
// let mut buf = [0; 512];
//
// assert_eq!(
// file.borrow_mut().read(&mut buf).unwrap(),
// expected_data.len()
// );
//
// assert_eq!(&buf[..expected_data.len()], expected_data.as_bytes());
// }
//
// test_allocator_with_counter!(A_COUNTER, A);
//
// let fs = MemoryFilesystem::<A>::from_slice(&TEST_IMAGE.bytes).unwrap();
// let root = fs.root().unwrap();
//
// // Files are small, so no indirect blocks allocated
// assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
//
// let ioctx = IoContext::new(root.clone());
//
// assert!(Rc::ptr_eq(
// &root,
// &ioctx.find(None, "/", false, false).unwrap()
// ));
//
// let old_data = include_str!("../test/test1.txt");
// check_file(&ioctx, "/test1.txt", old_data);
//
// // Write to the file
// {
// let node = ioctx.find(None, "/test1.txt", false, false).unwrap();
// let file = node.open(OpenOptions::WRITE).unwrap();
//
// assert_eq!(file.borrow_mut().write(b"Hello").unwrap(), 5);
// }
//
// assert_eq!(A_COUNTER.load(Ordering::Acquire), 1);
//
// // Read back
// {
// let node = ioctx.find(None, "/test1.txt", false, false).unwrap();
// let file = node.open(OpenOptions::READ).unwrap();
//
// let mut buf = [0; 512];
// assert_eq!(file.borrow_mut().read(&mut buf).unwrap(), old_data.len());
//
// assert_eq!(&buf[..5], b"Hello");
// assert_eq!(&buf[5..old_data.len()], &old_data.as_bytes()[5..]);
// }
// }
//
// // #[test]
// // fn test_memfs_create_and_write() {
// // test_allocator_with_counter!(A_COUNTER, A);
//
// // let fs = MemoryFilesystem::<A>::empty();
// // let root = fs.root().unwrap();
//
// // let ioctx = IoContext::new(root.clone());
//
// // // Create, write, seek and read file
// // {
// // // TODO CREATE option handling
// // root.create("test1.txt", VnodeKind::Regular).unwrap();
//
// // let file = ioctx
// // .open(
// // None,
// // "/test1.txt",
// // OpenOptions::WRITE | OpenOptions::READ,
// // FileMode::empty(),
// // )
// // .unwrap();
//
// // let write_data = [1, 2, 3, 4];
// // let mut read_data = [0; 512];
//
// // let mut file = file.borrow_mut();
// // assert_eq!(file.write(&write_data).unwrap(), write_data.len());
// // assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
// // assert_eq!(file.read(&mut read_data).unwrap(), write_data.len());
// // assert_eq!(&read_data[..write_data.len()], &write_data[..]);
// // }
//
// // // Create a directory
// // {
// // // TODO read directory
// // root.create("dir1", VnodeKind::Directory).unwrap();
//
// // let dir1 = ioctx.find(None, "/dir1", false, false).unwrap();
// // let node = dir1.create("file1.txt", VnodeKind::Regular).unwrap();
// // assert!(Rc::ptr_eq(
// // &ioctx.find(None, "/dir1/file1.txt", false, false).unwrap(),
// // &node
// // ));
// // }
// // }
// }
#![deny(missing_docs)]
#![allow(clippy::new_without_default)]
#![feature(
const_mut_refs,
maybe_uninit_uninit_array,
const_maybe_uninit_uninit_array,
maybe_uninit_array_assume_init
)]
use core::{cell::RefCell, marker::PhantomData};
use alloc::rc::Rc;
use block::BlockAllocator;
use dir::DirectoryNode;
use file::FileNode;
use vfs::{AccessToken, NodeRef};
use yggdrasil_abi::{
error::Error,
io::{FileMode, FileType, GroupId, UserId},
path::Path,
};
use crate::tar::TarIterator;
#[cfg(test)]
extern crate std;
extern crate alloc;
#[cfg(test)]
macro_rules! test_allocator_with_counter {
($counter:ident, $allocator:ident) => {
static $counter: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
struct $allocator;
unsafe impl $crate::block::BlockAllocator for $allocator {
fn alloc() -> Result<core::ptr::NonNull<u8>, yggdrasil_abi::error::Error> {
let b = std::boxed::Box::into_raw(std::boxed::Box::new([0; $crate::block::SIZE]));
$counter.fetch_add(1, core::sync::atomic::Ordering::Release);
Ok(unsafe { core::ptr::NonNull::new_unchecked(b as _) })
}
unsafe fn dealloc(block: core::ptr::NonNull<u8>) {
$counter.fetch_sub(1, core::sync::atomic::Ordering::Release);
drop(std::boxed::Box::from_raw(
block.as_ptr() as *mut [u8; $crate::block::SIZE]
));
}
}
};
}
pub mod block;
pub mod bvec;
mod dir;
mod file;
mod tar;
/// In-memory read/write filesystem
pub struct MemoryFilesystem<A: BlockAllocator> {
root: RefCell<Option<NodeRef>>,
_pd: PhantomData<A>,
}
impl<A: BlockAllocator> MemoryFilesystem<A> {
fn make_path(
self: &Rc<Self>,
at: &NodeRef,
path: &Path,
kind: FileType,
create: bool,
) -> Result<NodeRef, Error> {
let access = unsafe { AccessToken::authorized() };
if path.is_empty() {
return Ok(at.clone());
}
let (element, rest) = path.split_left();
// let (element, rest) = path::split_left(path);
assert!(!element.is_empty());
assert!(!element.contains('/'));
// let node = at.lookup(element);
let node = at.lookup_or_load(element, access);
let node = match node {
Ok(node) => node,
Err(Error::DoesNotExist) => {
if !create {
return Err(Error::DoesNotExist);
}
let node = self.create_node_initial(kind);
at.add_child(element, node.clone())?;
node
}
Err(err) => {
log::warn!("{:?}: lookup failed: {:?}", path, err);
return Err(err);
}
};
if rest.is_empty() {
Ok(node)
} else {
assert!(node.is_directory());
self.make_path(&node, rest, kind, create)
}
}
fn create_node_initial(self: &Rc<Self>, kind: FileType) -> NodeRef {
match kind {
FileType::File => FileNode::<A>::new(),
FileType::Directory => DirectoryNode::<A>::new(),
_ => todo!(),
}
}
fn from_slice_internal(self: &Rc<Self>, tar_data: &'static [u8]) -> Result<NodeRef, Error> {
let root = DirectoryNode::<A>::new();
// 1. Create paths in tar
for item in TarIterator::new(tar_data) {
let Ok((hdr, _)) = item else {
return Err(Error::InvalidArgument);
};
let path = Path::from_str(hdr.name.as_str()?.trim_matches('/'));
log::debug!("Make path {:?}", path);
let (dirname, filename) = path.split_right();
let parent = self.make_path(&root, dirname, FileType::Directory, true)?;
let node = self.create_node_initial(hdr.node_kind());
parent.add_child(filename, node)?;
}
// 2. Associate files with their data
for item in TarIterator::new(tar_data) {
let Ok((hdr, data)) = item else {
panic!("Unreachable");
};
let path = Path::from_str(hdr.name.as_str()?.trim_matches('/'));
let node = self.make_path(&root, path, FileType::Directory, false)?;
assert_eq!(node.ty(), hdr.node_kind());
let uid = UserId::from(usize::from(&hdr.uid) as u32);
let gid = GroupId::from(usize::from(&hdr.gid) as u32);
let mode = convert_mode(usize::from(&hdr.mode))?;
let access = unsafe { AccessToken::authorized() };
node.set_access(Some(uid), Some(gid), Some(mode), access)?;
if hdr.node_kind() == FileType::File {
let data = data.unwrap();
let node_data = node.data_as_ref::<FileNode<A>>();
let mut bvec = node_data.data.borrow_mut();
bvec.init_with_cow(data)?;
assert_eq!(bvec.size(), data.len());
}
}
Ok(root)
}
/// Constructs a filesystem tree from a tar image in memory
pub fn from_slice(tar_data: &'static [u8]) -> Result<Rc<Self>, Error> {
let fs = Rc::new(Self {
root: RefCell::new(None),
_pd: PhantomData,
});
let root = fs.from_slice_internal(tar_data)?;
fs.root.replace(Some(root));
Ok(fs)
}
// TODO Filesystem trait?
/// Returns the root node of the memory filesystem
pub fn root(&self) -> Result<NodeRef, Error> {
Ok(self.root.borrow().clone().unwrap())
}
}
fn convert_mode(mode: usize) -> Result<FileMode, Error> {
Ok(FileMode::new(mode as u32 & 0o777))
}

View File

@ -1,5 +1,4 @@
use vfs::VnodeKind;
use yggdrasil_abi::error::Error;
use yggdrasil_abi::{error::Error, io::FileType};
#[repr(C)]
pub(crate) struct OctalField<const N: usize> {
@ -126,10 +125,10 @@ impl TarEntry {
self.name.data[0] == 0
}
pub fn node_kind(&self) -> VnodeKind {
pub fn node_kind(&self) -> FileType {
match self.type_ {
0 | b'0' => VnodeKind::Regular,
b'5' => VnodeKind::Directory,
0 | b'0' => FileType::File,
b'5' => FileType::Directory,
_ => todo!(),
}
}

View File

@ -1,47 +1,74 @@
use yggdrasil_abi::error::Error;
use yggdrasil_abi::{error::Error, io::DeviceRequest};
use crate::node::{CommonImpl, NodeRef};
/// Block device interface
#[allow(unused)]
pub trait BlockDevice {
fn read(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
/// Reads data frmo the given offset of the device
fn read(&'static self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
fn write(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
/// Writes the data to the given offset of the device
fn write(&'static self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
/// Returns the size of the block device in bytes
fn size(&self) -> Result<u64, Error> {
Err(Error::NotImplemented)
}
/// Returns `true` if the device can be read from
fn is_readable(&self) -> bool {
true
}
/// Returns `true` if the device can be written to
fn is_writable(&self) -> bool {
true
}
fn size(&self) -> Result<u64, Error>;
/// Performs a device-specific function
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
Err(Error::NotImplemented)
}
}
/// Character device interface
#[allow(unused)]
pub trait CharDevice {
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
/// Reads data from the device
fn read(&'static self, buf: &mut [u8]) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
/// Writes the data to the device
fn write(&'static self, buf: &[u8]) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
/// Returns `true` if the device can be read from
fn is_readable(&self) -> bool {
true
}
/// Returns `true` if the device can be written to
fn is_writable(&self) -> bool {
true
}
/// Returns `true` if the given device is a terminal
fn is_terminal(&self) -> bool {
false
}
/// Performs a device-specific function
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
Err(Error::NotImplemented)
}
}
#[derive(Clone)]
pub struct BlockDeviceWrapper(pub(crate) &'static dyn BlockDevice);
pub(crate) struct BlockDeviceWrapper(pub(crate) &'static dyn BlockDevice);
#[derive(Clone)]
pub struct CharDeviceWrapper(pub(crate) &'static dyn CharDevice);
pub(crate) struct CharDeviceWrapper(pub(crate) &'static dyn CharDevice);
impl BlockDeviceWrapper {
pub fn is_readable(&self) -> bool {

View File

@ -22,14 +22,20 @@ mod device;
mod directory;
mod regular;
/// Describes the starting position of the directory
pub enum DirectoryOpenPosition {
/// Contents should be fetched from the directory impl with given offset
FromPhysical(u64),
/// Contents should be fetched from the tree cache
FromCache,
}
/// Wrapper type for a [File] shared reference
pub type FileRef = Arc<File>;
// TODO some kind of a mutex instead?
/// Describes an open file
#[allow(missing_docs)]
pub enum File {
Directory(DirectoryFile),
Regular(RegularFile),
@ -38,12 +44,12 @@ pub enum File {
}
impl File {
pub fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
let position = Cell::new(position.into());
Arc::new(Self::Directory(DirectoryFile { node, position }))
}
pub fn regular(
pub(crate) fn regular(
node: NodeRef,
position: u64,
instance_data: Option<Box<dyn Any>>,
@ -61,7 +67,7 @@ impl File {
}))
}
pub fn block(
pub(crate) fn block(
device: BlockDeviceWrapper,
node: NodeRef,
opts: OpenOptions,
@ -85,7 +91,7 @@ impl File {
})))
}
pub fn char(
pub(crate) fn char(
device: CharDeviceWrapper,
node: NodeRef,
opts: OpenOptions,
@ -108,6 +114,7 @@ impl File {
})))
}
/// Reads entries from the directory
pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
match self {
Self::Directory(dir) => dir.read_entries(entries),
@ -115,6 +122,7 @@ impl File {
}
}
/// Returns the underlying [Node] the file contains
pub fn node(&self) -> Option<&NodeRef> {
match self {
Self::Directory(file) => Some(&file.node),
@ -207,7 +215,7 @@ mod tests {
use crate::{
device::{BlockDevice, CharDevice},
file::DirectoryOpenPosition,
node::{CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
traits::{Read, Seek, Write},
};
@ -264,7 +272,7 @@ mod tests {
NodeFlags::empty(),
);
let f = d.open_directory().unwrap();
let f = d.open_directory(AccessToken::test_authorized()).unwrap();
let mut entries = [MaybeUninit::uninit(); 16];
let count = f.read_dir(&mut entries).unwrap();
@ -310,7 +318,7 @@ mod tests {
d.add_child("child1", child).unwrap();
let f = d.open_directory().unwrap();
let f = d.open_directory(AccessToken::test_authorized()).unwrap();
let mut entries = [MaybeUninit::uninit(); 16];
let count = f.read_dir(&mut entries).unwrap();
@ -393,7 +401,12 @@ mod tests {
let data = Arc::new(RefCell::new(vec![]));
let node = Node::regular(F { data: data.clone() }, NodeFlags::empty());
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
let file = node
.open(
OpenOptions::READ | OpenOptions::WRITE,
AccessToken::test_authorized(),
)
.unwrap();
let mut buf = [0; 512];
assert_eq!(&*data.borrow(), &[]);
@ -460,7 +473,12 @@ mod tests {
let node = Node::block(dev, NodeFlags::empty());
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
let file = node
.open(
OpenOptions::READ | OpenOptions::WRITE,
AccessToken::test_authorized(),
)
.unwrap();
assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 1024);
assert_eq!(file.write(b"12345").unwrap(), 0);
@ -495,10 +513,14 @@ mod tests {
let node = Node::char(&DEV, NodeFlags::empty());
let mut buf = [0; 512];
let err = node.open(OpenOptions::WRITE).unwrap_err();
let err = node
.open(OpenOptions::WRITE, AccessToken::test_authorized())
.unwrap_err();
assert_eq!(err, Error::ReadOnly);
let file = node.open(OpenOptions::READ).unwrap();
let file = node
.open(OpenOptions::READ, AccessToken::test_authorized())
.unwrap();
assert_eq!(file.tell().unwrap_err(), Error::InvalidOperation);
assert_eq!(
file.seek(SeekFrom::Start(10)).unwrap_err(),

View File

@ -6,10 +6,22 @@ use yggdrasil_abi::{
};
use crate::{
node::{CreateInfo, SymlinkData},
node::{AccessToken, CreateInfo, SymlinkData},
FileRef, NodeRef,
};
/// Describes a general filesystem access
pub enum Action {
/// Access involves reading data without modification
Read,
/// Access involves writing file data or modifying directory content
Write,
/// Access involves traversing a path or opening a file for execution
Execute,
}
/// Contains the state of program's I/O context: current working directory, UID, GID, umask, etc.
#[derive(Clone)]
pub struct IoContext {
uid: UserId,
gid: GroupId,
@ -20,6 +32,8 @@ pub struct IoContext {
}
impl IoContext {
/// Constructs a new [IoContext] with given root node (which also becomes the cwd). By default,
/// the root user/group is used. Default umask is 0o022.
pub fn new(root: NodeRef) -> Self {
Self {
uid: UserId::root(),
@ -31,20 +45,91 @@ impl IoContext {
}
}
/// "Clones" an I/O context the way it is inherited by a newly spawned child process
pub fn inherit(other: &IoContext) -> Self {
other.clone()
}
/// Returns the root node of the [IoContext]
pub fn root(&self) -> &NodeRef {
&self.root
}
/// Returns the current working directory node of the [IoContext]
pub fn cwd(&self) -> &NodeRef {
&self.cwd_node
}
/// Sets the current user ID of the context. Returns [Error::PermissionDenied] if current user
/// is not root.
pub fn set_uid(&mut self, uid: UserId) -> Result<(), Error> {
if uid != self.uid && !self.uid.is_root() {
Err(Error::PermissionDenied)
} else {
self.uid = uid;
Ok(())
}
}
#[cfg(test)]
fn set_uid_unchecked(&mut self, uid: UserId) {
self.uid = uid;
}
/// Sets the current group ID of the context. Returns [Error::PermissionDenied] if current user
/// is not root.
pub fn set_gid(&mut self, gid: GroupId) -> Result<(), Error> {
if gid != self.gid && !self.uid.is_root() {
Err(Error::PermissionDenied)
} else {
self.gid = gid;
Ok(())
}
}
#[cfg(test)]
fn set_gid_unchecked(&mut self, gid: GroupId) {
self.gid = gid;
}
/// Checks if the current user can access given [crate::Node] and, if so, returns an
/// [AccessToken].
pub fn check_access(&self, action: Action, node: &NodeRef) -> Result<AccessToken, Error> {
let metadata = node.metadata()?;
let allow = match action {
Action::Read => {
self.uid.is_root()
| metadata.user_read(self.uid)
| metadata.group_read(self.gid)
| metadata.other_read()
}
Action::Write => {
self.uid.is_root()
| metadata.user_write(self.uid)
| metadata.group_write(self.gid)
| metadata.other_write()
}
Action::Execute => {
metadata.user_exec(self.uid) | metadata.group_exec(self.gid) | metadata.other_exec()
}
};
if allow {
Ok(unsafe { AccessToken::authorized() })
} else {
Err(Error::PermissionDenied)
}
}
/// Changes current working directory to `path`. Will fail if access is denied or the path does
/// not point to a directory.
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)?;
let node = self._find(self.root.clone(), path.trim_start_separators(), true, true)?;
if !node.is_directory() {
return Err(Error::NotADirectory);
}
@ -53,16 +138,24 @@ impl IoContext {
Ok(())
}
/// Returns the current working directory path
pub fn cwd_path(&self) -> &Path {
self.cwd_path.as_ref()
}
/// Makes a directory at given path become a "mountpoint" for the given filesystem root.
/// When accessed, the target directory will return contents of the filesystem root instead of
/// its own. Both the `target` path and `fs_root` Node must be directories.
pub fn mount<P: AsRef<Path>>(&mut self, target: P, fs_root: NodeRef) -> Result<(), Error> {
if !self.uid.is_root() {
return Err(Error::PermissionDenied);
}
let target = target.as_ref();
if !target.is_absolute() {
todo!();
}
let target_node = Self::_find(
let target_node = self._find(
self.root.clone(),
target.trim_start_separators(),
true,
@ -72,6 +165,9 @@ impl IoContext {
target_node.set_mountpoint_target(fs_root)
}
/// Locates a [crate::Node] at given path and opens it with requested access options. If no
/// such node exists and `OpenOptions::CREATE` is specified, will attempt to create a regular
/// file node at given path and then open it.
pub fn open<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
@ -93,14 +189,93 @@ impl IoContext {
gid: self.gid,
ty: FileType::File,
};
parent.create(&create_info)?
let access = self.check_access(Action::Write, &parent)?;
parent.create(&create_info, access)?
}
Err(err) => return Err(err),
};
node.open(opts)
// If not read/write is requested, access is granted
let mut access = unsafe { AccessToken::authorized() };
if opts.contains(OpenOptions::READ) {
access += self.check_access(Action::Read, &node)?;
}
if opts.contains(OpenOptions::WRITE) {
access += self.check_access(Action::Write, &node)?;
}
node.open(opts, access)
}
/// Opens a file at given path for execution
pub fn open_exec<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
path: P,
) -> Result<FileRef, Error> {
let node = self.find(at, path, true, true)?;
let access = self.check_access(Action::Execute, &node)?;
node.open(OpenOptions::READ, access)
}
/// Creates a directory a given path
pub fn create_directory<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
path: P,
mode: FileMode,
) -> Result<NodeRef, Error> {
let path = path.as_ref();
let (parent, name) = path.split_right();
let parent = self.find(at, parent, true, true)?;
let access = self.check_access(Action::Write, &parent)?;
let create_info = CreateInfo {
name: name.into(),
ty: FileType::Directory,
uid: self.uid,
gid: self.gid,
mode: mode & !self.umask,
};
parent.create(&create_info, access)
}
fn remove_entry<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
path: P,
directory: bool,
) -> Result<(), Error> {
let path = path.as_ref();
let (parent, name) = path.trim_end_separators().split_right();
if name.is_empty() {
log::warn!("Tried to remove weird path: {:?}", path);
return Err(Error::DoesNotExist);
}
let parent = self.find(at, parent, false, false)?;
let access = self.check_access(Action::Write, &parent)?;
if directory {
// parent.remove_directory(name, access)
todo!()
} else {
parent.remove_file(name, access)
}
}
/// Removes a device or regular file node at given path
pub fn remove_file<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
path: P,
) -> Result<(), Error> {
self.remove_entry(at, path, false)
}
/// Locates a [crate::Node] pointed to by given [Path]
pub fn find<P: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
@ -118,10 +293,12 @@ impl IoContext {
self.cwd_node.clone()
};
Self::_find(at, path, follow_links, follow_mount)
self._find(at, path, follow_links, follow_mount)
}
fn _resolve_link(at: &NodeRef, link: &SymlinkData) -> Result<NodeRef, Error> {
fn _resolve_link(&self, at: &NodeRef, link: &SymlinkData) -> Result<NodeRef, Error> {
self.check_access(Action::Read, at)?;
// If the filesystem itself implements direct target resolution, use that
match link.imp.target(at) {
Err(Error::NotImplemented) => {}
@ -138,7 +315,12 @@ impl IoContext {
todo!()
}
fn _resolve(mut at: NodeRef, follow_links: bool, follow_mount: bool) -> Result<NodeRef, Error> {
fn _resolve(
&self,
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();
@ -147,7 +329,7 @@ impl IoContext {
if follow_links && let Ok(link) = at.as_symlink() {
// Resolve the link
at = Self::_resolve_link(&at, link)?;
at = self._resolve_link(&at, link)?;
continue;
}
@ -156,6 +338,7 @@ impl IoContext {
}
fn _find(
&self,
mut at: NodeRef,
path: &Path,
follow_links: bool,
@ -180,7 +363,7 @@ impl IoContext {
}
}
at = Self::_resolve(at, follow_links, follow_mount)?;
at = self._resolve(at, follow_links, follow_mount)?;
if !at.is_directory() {
return Err(Error::NotADirectory);
@ -190,13 +373,14 @@ impl IoContext {
return Ok(at);
}
let node = at.lookup_or_load(element)?;
let node = Self::_resolve(node, follow_links, follow_mount)?;
let access = self.check_access(Action::Execute, &at)?;
let node = at.lookup_or_load(element, access)?;
let node = self._resolve(node, follow_links, follow_mount)?;
if rest.is_empty() {
Ok(node)
} else {
Self::_find(node, rest, follow_links, follow_mount)
self._find(node, rest, follow_links, follow_mount)
}
}
}
@ -206,17 +390,81 @@ mod tests {
use alloc::sync::Arc;
use yggdrasil_abi::{
error::Error,
io::{FileMode, OpenOptions},
io::{FileMode, GroupId, OpenOptions, UserId},
path::Path,
};
use crate::{
impls::{const_value_node, f_symlink, mdir},
impls::{const_value_node, f_symlink, mdir, value_node},
node::AccessToken,
Read,
};
use super::IoContext;
#[test]
fn access() {
let f1 = const_value_node("file1");
let f2 = const_value_node("file2");
let f3 = value_node("file3".to_owned());
let root = mdir([("f1", f1.clone()), ("f2", f2.clone()), ("f3", f3.clone())]);
let mut ioctx = IoContext::new(root.clone());
let uid = UserId::from(1);
let gid = GroupId::from(1);
// 1:1
ioctx.set_uid_unchecked(uid);
ioctx.set_gid_unchecked(gid);
// 1:0, 0444
f1.set_access(
Some(uid),
None,
Some(FileMode::new(0o444)),
AccessToken::test_authorized(),
)
.unwrap();
// 0:1, 0644
f2.set_access(None, Some(gid), None, AccessToken::test_authorized())
.unwrap();
// 1:1, 0644
f3.set_access(Some(uid), Some(gid), None, AccessToken::test_authorized())
.unwrap();
// f1, read-only
ioctx
.open(None, "/f1", OpenOptions::READ, FileMode::empty())
.unwrap();
// f1, write
let err = ioctx
.open(None, "/f1", OpenOptions::WRITE, FileMode::empty())
.unwrap_err();
assert_eq!(err, Error::PermissionDenied);
// f2, read-only
ioctx
.open(None, "/f2", OpenOptions::READ, FileMode::empty())
.unwrap();
// f2, write
let err = ioctx
.open(None, "/f2", OpenOptions::WRITE, FileMode::empty())
.unwrap_err();
assert_eq!(err, Error::PermissionDenied);
// f3, read-only
ioctx
.open(None, "/f3", OpenOptions::READ, FileMode::empty())
.unwrap();
// f3, write
ioctx
.open(None, "/f3", OpenOptions::WRITE, FileMode::empty())
.unwrap();
}
#[test]
fn cwd() {
let d1_f1 = const_value_node("dir1-file1");

View File

@ -1,4 +1,7 @@
//! Virtual filesystem interfaces and driver implementation
#![cfg_attr(not(test), no_std)]
#![deny(missing_docs)]
#![feature(
trait_upcasting,
if_let_guard,
@ -21,5 +24,9 @@ pub(crate) mod traits;
pub use device::{BlockDevice, CharDevice};
pub use file::{DirectoryOpenPosition, File, FileRef};
pub use node::{impls, Node, NodeRef};
pub use ioctx::{Action, IoContext};
pub use node::{
impls, AccessToken, CommonImpl, CreateInfo, DirectoryImpl, Metadata, Node, NodeFlags, NodeRef,
RegularImpl, SymlinkImpl,
};
pub use traits::{Read, Seek, Write};

View File

@ -0,0 +1,78 @@
use core::{
marker::PhantomData,
ops::{Add, AddAssign},
};
use yggdrasil_abi::io::{FileMode, GroupId, UserId};
use super::Metadata;
/// Zero-sized token type used to ensure checked access to node functions
pub struct AccessToken(PhantomData<()>);
#[allow(missing_docs)]
impl Metadata {
pub fn user_read(&self, uid: UserId) -> bool {
self.uid == uid && self.mode.contains(FileMode::USER_READ)
}
pub fn user_write(&self, uid: UserId) -> bool {
self.uid == uid && self.mode.contains(FileMode::USER_WRITE)
}
pub fn user_exec(&self, uid: UserId) -> bool {
self.uid == uid && self.mode.contains(FileMode::USER_EXEC)
}
pub fn group_read(&self, gid: GroupId) -> bool {
self.gid == gid && self.mode.contains(FileMode::GROUP_READ)
}
pub fn group_write(&self, gid: GroupId) -> bool {
self.gid == gid && self.mode.contains(FileMode::GROUP_WRITE)
}
pub fn group_exec(&self, gid: GroupId) -> bool {
self.gid == gid && self.mode.contains(FileMode::GROUP_EXEC)
}
pub fn other_read(&self) -> bool {
self.mode.contains(FileMode::OTHER_READ)
}
pub fn other_write(&self) -> bool {
self.mode.contains(FileMode::OTHER_WRITE)
}
pub fn other_exec(&self) -> bool {
self.mode.contains(FileMode::OTHER_EXEC)
}
}
impl AccessToken {
/// Creates an "authorized" [AccessToken].
///
/// # Safety
///
/// Unsafe: allows for unchecked authorization of any node actions.
pub const unsafe fn authorized() -> Self {
Self(PhantomData)
}
#[cfg(test)]
pub const fn test_authorized() -> Self {
unsafe { Self::authorized() }
}
}
impl Add for AccessToken {
type Output = Self;
fn add(self, _rhs: Self) -> Self::Output {
self
}
}
impl AddAssign for AccessToken {
fn add_assign(&mut self, _rhs: Self) {}
}

View File

@ -1,3 +1,4 @@
//! Various helper node implementations for convenience
use core::{any::Any, cell::RefCell, marker::PhantomData, str::FromStr};
use alloc::{
@ -11,15 +12,13 @@ use yggdrasil_abi::{error::Error, io::OpenOptions};
use crate::DirectoryOpenPosition;
use super::{
CommonImpl, CreateInfo, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl, SymlinkImpl,
};
use super::{CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl, SymlinkImpl};
pub trait SliceRead {
trait SliceRead {
fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize;
}
pub trait SliceWrite {
trait SliceWrite {
fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize;
}
@ -27,6 +26,15 @@ trait IntoInstanceData {
fn into_instance_data(&self) -> Vec<u8>;
}
/// Closure interface for reading a single value
pub trait ValueReadFn<T> = Fn() -> Result<T, Error>;
/// Closure interface for writing a single value
pub trait ValueWriteFn<T> = Fn(T) -> Result<(), Error>;
/// Closure interface for reading bytes
pub trait ReadFn = Fn(u64, &mut [u8]) -> Result<usize, Error>;
/// Closure interface for writing bytes
pub trait WriteFn = Fn(u64, &[u8]) -> Result<usize, Error>;
impl<T: AsRef<[u8]>> SliceRead for T {
fn read_slice(&self, pos: usize, buf: &mut [u8]) -> usize {
let value = self.as_ref();
@ -55,31 +63,40 @@ impl<T: ToString> IntoInstanceData for T {
}
}
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>> {
/// Allows read-only access to a value. The value is converted to a string representation when it's
/// read.
pub struct ReadOnlyFnValueNode<T: ToString, R: ValueReadFn<T>> {
read: R,
_pd: PhantomData<T>,
}
pub struct FnNode<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> {
/// Allows read-write access to a value (but not both at the same time). The value is converted
/// to/from a string representation when it's read/written.
pub struct FnValueNode<T: ToString + FromStr, R: ValueReadFn<T>, W: ValueWriteFn<T>> {
read: R,
write: W,
_pd: PhantomData<T>,
}
/// Allows read-only access to a "functional file"
pub struct ReadOnlyFnNode<R: ReadFn> {
read: R,
}
/// In-memory directory using tree cache
pub struct MemoryDirectory;
/// In-memory symlink pointing to a fixed [Node]
pub struct FixedSymlink {
target: NodeRef,
}
impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
impl<T: ToString + 'static, R: ValueReadFn<T> + 'static> ReadOnlyFnValueNode<T, R> {
/// Creates a new [ReadOnlyFnValueNode] with given read function
pub fn new(read: R) -> NodeRef {
Node::regular(
Self::new_impl(read),
@ -87,7 +104,7 @@ impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
)
}
pub const fn new_impl(read: R) -> Self {
const fn new_impl(read: R) -> Self {
Self {
read,
_pd: PhantomData,
@ -95,13 +112,13 @@ impl<T: ToString + 'static, R: ReadFn<T> + 'static> ReadOnlyFnNode<T, R> {
}
}
impl<T: ToString, R: ReadFn<T>> CommonImpl for ReadOnlyFnNode<T, R> {
impl<T: ToString, R: ValueReadFn<T>> CommonImpl for ReadOnlyFnValueNode<T, R> {
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
Ok(0)
}
}
impl<T: ToString + 'static, R: ReadFn<T>> RegularImpl for ReadOnlyFnNode<T, R> {
impl<T: ToString + 'static, R: ValueReadFn<T>> RegularImpl for ReadOnlyFnValueNode<T, R> {
fn open(
&self,
_node: &NodeRef,
@ -162,9 +179,13 @@ impl FnNodeData {
}
}
impl<T: ToString + FromStr + 'static, R: ReadFn<T> + 'static, W: WriteFn<T> + 'static>
FnNode<T, R, W>
impl<T, R, W> FnValueNode<T, R, W>
where
T: ToString + FromStr + 'static,
R: ValueReadFn<T> + 'static,
W: ValueWriteFn<T> + 'static,
{
/// Creates a new [FnValueNode] with given read and write functions
pub fn new(read: R, write: W) -> NodeRef {
Node::regular(
Self::new_impl(read, write),
@ -172,7 +193,7 @@ impl<T: ToString + FromStr + 'static, R: ReadFn<T> + 'static, W: WriteFn<T> + 's
)
}
pub const fn new_impl(read: R, write: W) -> Self {
const fn new_impl(read: R, write: W) -> Self {
Self {
read,
write,
@ -181,13 +202,17 @@ 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> {
impl<T: ToString + FromStr, R: ValueReadFn<T>, W: ValueWriteFn<T>> CommonImpl
for FnValueNode<T, R, W>
{
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
Ok(0)
}
}
impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> RegularImpl for FnNode<T, R, W> {
impl<T: ToString + FromStr, R: ValueReadFn<T>, W: ValueWriteFn<T>> RegularImpl
for FnValueNode<T, R, W>
{
fn open(
&self,
_node: &NodeRef,
@ -247,17 +272,70 @@ impl<T: ToString + FromStr, R: ReadFn<T>, W: WriteFn<T>> RegularImpl for FnNode<
}
}
// Byte read-only node
impl<R> ReadOnlyFnNode<R>
where
R: ReadFn + 'static,
{
/// Creates a new [ReadOnlyFnNode] with given read function
pub fn new(read: R) -> NodeRef {
Node::regular(Self { read }, NodeFlags::IN_MEMORY_PROPS)
}
}
impl<R> CommonImpl for ReadOnlyFnNode<R>
where
R: ReadFn,
{
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
Ok(0)
}
}
impl<R> RegularImpl for ReadOnlyFnNode<R>
where
R: ReadFn,
{
fn open(
&self,
_node: &NodeRef,
opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
if opts.contains(OpenOptions::WRITE) {
return Err(Error::ReadOnly);
}
Ok((0, None))
}
fn read(
&self,
_node: &NodeRef,
_instance: Option<&Box<dyn Any>>,
pos: u64,
buf: &mut [u8],
) -> Result<usize, Error> {
(self.read)(pos, buf)
}
}
// In-memory directory
impl MemoryDirectory {
/// Creates a [MemoryDirectory] with no children
pub fn empty() -> NodeRef {
Node::directory(
MemoryDirectory,
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
)
}
}
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
@ -269,15 +347,17 @@ impl SymlinkImpl for FixedSymlink {
}
}
/// Creates a read-only value node with given `value`
pub fn const_value_node<T: ToString + Clone + 'static>(value: T) -> NodeRef {
ReadOnlyFnNode::new(move || Ok(value.clone()))
ReadOnlyFnValueNode::new(move || Ok(value.clone()))
}
/// Creates a read-write value node with given `value`
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(
FnValueNode::new(
move || Ok(rd_state.borrow().clone()),
move |t| {
*wr_state.borrow_mut() = t;
@ -286,6 +366,12 @@ pub fn value_node<T: ToString + FromStr + Clone + 'static>(value: T) -> NodeRef
)
}
/// Creates a read-only node with given read function
pub fn read_fn_node<R: ReadFn + 'static>(read: R) -> NodeRef {
ReadOnlyFnNode::new(read)
}
/// Creates an in-memory directory from the iterator
pub fn mdir<S: Into<String>, I: IntoIterator<Item = (S, NodeRef)>>(it: I) -> NodeRef {
let dir = Node::directory(
MemoryDirectory,
@ -297,8 +383,9 @@ pub fn mdir<S: Into<String>, I: IntoIterator<Item = (S, NodeRef)>>(it: I) -> Nod
dir
}
/// Creates a static symlink pointing to given node
pub fn f_symlink(target: NodeRef) -> NodeRef {
Node::symlink(FixedSymlink { target }, NodeFlags::empty())
Node::symlink(FixedSymlink { target }, NodeFlags::IN_MEMORY_PROPS)
}
#[cfg(test)]
@ -306,14 +393,19 @@ mod tests {
use yggdrasil_abi::io::OpenOptions;
use crate::{
node::impls::{const_value_node, value_node},
node::{
impls::{const_value_node, value_node},
AccessToken,
},
traits::{Read, Seek, Write},
};
#[test]
fn read_only_fn_node() {
let node = const_value_node("abcdef");
let file = node.open(OpenOptions::READ).unwrap();
let file = node
.open(OpenOptions::READ, AccessToken::test_authorized())
.unwrap();
let mut buf = [0; 512];
assert_eq!(file.tell().unwrap(), 0);
@ -331,19 +423,25 @@ mod tests {
let mut buf = [0; 512];
// Try read the value
let file = node.open(OpenOptions::READ).unwrap();
let file = node
.open(OpenOptions::READ, AccessToken::test_authorized())
.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();
let file = node
.open(OpenOptions::WRITE, AccessToken::test_authorized())
.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();
let file = node
.open(OpenOptions::READ, AccessToken::test_authorized())
.unwrap();
assert_eq!(file.tell().unwrap(), 0);
assert_eq!(file.read(&mut buf).unwrap(), 6);
assert_eq!(&buf[..6], b"654321");

View File

@ -1,17 +1,14 @@
use core::{
any::Any,
cell::{Cell, RefCell},
fmt,
};
use core::{any::Any, fmt};
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use kernel_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{
bitflags,
error::Error,
io::{FileAttr, FileMode, FileType, GroupId, UserId},
io::{FileMode, FileType, GroupId, UserId},
};
mod access;
pub mod impls;
mod traits;
@ -19,25 +16,36 @@ mod traits;
mod ops;
mod tree;
pub use access::AccessToken;
pub use traits::{CommonImpl, DirectoryImpl, RegularImpl, SymlinkImpl};
use crate::device::{BlockDevice, BlockDeviceWrapper, CharDevice, CharDeviceWrapper};
/// Wrapper type for a [Node] shared reference
pub type NodeRef = Arc<Node>;
bitflags! {
#[doc = "Describes additional flags for the node"]
pub struct NodeFlags: u32 {
#[doc = "Node's metadata only exists within the VFS cache and should not be fetched"]
const IN_MEMORY_PROPS: bit 0;
#[doc = "Node's size only exists within the VFS cache"]
const IN_MEMORY_SIZE: bit 1;
}
}
/// Information used when creating an entry within a directory
#[derive(Debug, Clone)]
pub struct CreateInfo {
/// New entry name
pub name: String,
/// User ID of the entry
pub uid: UserId,
/// Group ID of the entry
pub gid: GroupId,
/// Access mode of the entry
pub mode: FileMode,
/// Entry type
pub ty: FileType,
}
@ -61,19 +69,23 @@ enum NodeImpl {
Symlink(SymlinkData),
}
/// Metadata of the node
#[derive(Clone, Copy)]
pub struct Metadata {
/// User ID of the node
pub uid: UserId,
/// Group ID of the node
pub gid: GroupId,
/// Access mode of the node
pub mode: FileMode,
}
#[derive(Default)]
struct PropertyCache {
metadata: Metadata,
size: Option<u64>,
}
/// Decribes a single entry within a filesytem
pub struct Node {
data: NodeImpl,
flags: NodeFlags,
@ -81,47 +93,75 @@ pub struct Node {
parent: IrqSafeSpinlock<Option<NodeRef>>,
}
impl Default for Metadata {
fn default() -> Metadata {
impl Metadata {
pub(crate) const fn default_dir() -> Metadata {
Metadata {
uid: UserId::root(),
gid: GroupId::root(),
mode: FileMode::new(0),
mode: FileMode::new(0o755),
}
}
pub(crate) const fn default_file() -> Metadata {
Metadata {
uid: UserId::root(),
gid: GroupId::root(),
mode: FileMode::new(0o644),
}
}
}
impl Node {
fn new(data: NodeImpl, flags: NodeFlags) -> NodeRef {
fn new(data: NodeImpl, flags: NodeFlags, metadata: Metadata) -> NodeRef {
Arc::new(Self {
data,
flags,
props: IrqSafeSpinlock::new(PropertyCache::default()),
props: IrqSafeSpinlock::new(PropertyCache {
metadata,
size: None,
}),
parent: IrqSafeSpinlock::new(None),
})
}
/// Creates a new directory node with given [DirectoryImpl]
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)
Self::new(data, flags, Metadata::default_dir())
}
/// Creates a new file node with given [RegularImpl]
pub fn regular<T: RegularImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
Self::new(NodeImpl::Regular(Box::new(data)), flags)
Self::new(
NodeImpl::Regular(Box::new(data)),
flags,
Metadata::default_file(),
)
}
/// Creates a new block device node with given [BlockDevice]
pub fn block(device: &'static dyn BlockDevice, flags: NodeFlags) -> NodeRef {
Self::new(NodeImpl::Block(BlockDeviceWrapper(device)), flags)
Self::new(
NodeImpl::Block(BlockDeviceWrapper(device)),
flags,
Metadata::default_file(),
)
}
/// Creates a new character device node with given [CharDevice]
pub fn char(device: &'static dyn CharDevice, flags: NodeFlags) -> NodeRef {
Self::new(NodeImpl::Char(CharDeviceWrapper(device)), flags)
Self::new(
NodeImpl::Char(CharDeviceWrapper(device)),
flags,
Metadata::default_file(),
)
}
/// Creates a new symbolic link node with given [SymlinkImpl]
pub fn symlink<T: SymlinkImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
Self::new(
NodeImpl::Symlink(SymlinkData {
@ -129,9 +169,11 @@ impl Node {
imp: Box::new(data),
}),
flags,
Metadata::default_file(),
)
}
/// Returns the impl data of the node as `dyn Any`
pub fn data_as_any(&self) -> &dyn Any {
match &self.data {
NodeImpl::Directory(dir) => dir.imp.as_any(),
@ -142,6 +184,7 @@ impl Node {
}
}
/// Returns the impl data of the node as `dyn CommonImpl`
pub fn data_as_common(&self) -> &dyn CommonImpl {
match &self.data {
NodeImpl::Directory(dir) => dir.imp.as_ref(),
@ -152,10 +195,12 @@ impl Node {
}
}
/// Attempts to cast the impl data of the node to `&T`
pub fn data_as_ref<T: 'static>(&self) -> &T {
self.data_as_any().downcast_ref().unwrap()
}
/// Returns the type of the node
pub fn ty(&self) -> FileType {
match &self.data {
NodeImpl::Regular(_) => FileType::File,
@ -166,10 +211,13 @@ impl Node {
}
}
/// Returns `true` if the node represents a directory
pub fn is_directory(&self) -> bool {
matches!(&self.data, NodeImpl::Directory(_))
}
/// Returns `true` if the node represents a character device and the character device is a
/// terminal
pub fn is_terminal(&self) -> bool {
if let NodeImpl::Char(dev) = &self.data {
dev.is_terminal()

View File

@ -2,15 +2,30 @@ use core::mem::MaybeUninit;
use yggdrasil_abi::{
error::Error,
io::{DirectoryEntry, OpenOptions},
io::{DeviceRequest, DirectoryEntry, FileMode, GroupId, OpenOptions, UserId},
};
use crate::file::{File, FileRef};
use super::{CreateInfo, Metadata, Node, NodeFlags, NodeImpl, NodeRef};
use super::{AccessToken, CreateInfo, Metadata, Node, NodeFlags, NodeImpl, NodeRef};
impl Node {
pub fn open(self: &NodeRef, opts: OpenOptions) -> Result<FileRef, Error> {
// Devices
/// Performs a device-specific function on a the device node
pub fn device_request(self: &NodeRef, req: &mut DeviceRequest) -> Result<(), Error> {
match &self.data {
NodeImpl::Block(dev) => dev.0.device_request(req),
NodeImpl::Char(dev) => dev.0.device_request(req),
_ => Err(Error::InvalidOperation),
}
}
// Devices + files
/// Opens the node with given [OpenOptions]. Only works for regular files and devices. For
/// directories, use [Node::open_directory].
pub fn open(self: &NodeRef, opts: OpenOptions, _check: AccessToken) -> Result<FileRef, Error> {
match &self.data {
NodeImpl::Regular(imp) => {
let (pos, instance) = imp.open(self, opts)?;
@ -25,12 +40,14 @@ impl Node {
// Directory
pub fn open_directory(self: &NodeRef) -> Result<FileRef, Error> {
/// Opens the node as a directory for reading its entries
pub fn open_directory(self: &NodeRef, _check: AccessToken) -> Result<FileRef, Error> {
let dir = self.as_directory()?;
let pos = dir.imp.open(self)?;
Ok(File::directory(self.clone(), pos))
}
/// Reads entries from the directory
pub fn read_directory(
self: &NodeRef,
pos: u64,
@ -39,7 +56,14 @@ impl Node {
self.as_directory()?.imp.read_entries(self, pos, entries)
}
pub fn lookup_or_load(self: &NodeRef, name: &str) -> Result<NodeRef, Error> {
/// Attempts to look up a child node with given name inside the directory node in the tree
/// cache. If no such node is present there, will attempt to fetch it from the underlying
/// filesystem.
pub fn lookup_or_load(
self: &NodeRef,
name: &str,
_check: AccessToken,
) -> Result<NodeRef, Error> {
let dir = self.as_directory()?;
let children = dir.children.lock();
@ -52,16 +76,87 @@ impl Node {
Err(Error::DoesNotExist)
}
pub fn create(self: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
/// Creates an entry within a directory with given [CreateInfo].
pub fn create(self: &NodeRef, info: &CreateInfo, check: AccessToken) -> Result<NodeRef, Error> {
let directory = self.as_directory()?;
let node = directory.imp.create(self, info)?;
let node = directory.imp.create_node(self, info.ty)?;
// Fill out the node info
node.set_access(Some(info.uid), Some(info.gid), Some(info.mode), check)?;
match directory.imp.attach_node(self, &node, &info.name) {
Ok(_) | Err(Error::NotImplemented) => (),
Err(err) => return Err(err),
}
// Attach the created node to the directory in memory cache
self.add_child(&info.name, node.clone())?;
Ok(node)
}
/// Removes a regular file, device or symlink from the directory
pub fn remove_file(self: &NodeRef, name: &str, check: AccessToken) -> Result<(), Error> {
let directory = self.as_directory()?;
let child = self.lookup_or_load(name, check)?;
if child.is_directory() {
return Err(Error::IsADirectory);
}
// Detach the node in the real filesystem
match directory.imp.unlink_node(self, name) {
Ok(_) | Err(Error::NotImplemented) => (),
Err(err) => return Err(err),
}
// Detach the node in the tree cache
{
let mut children = directory.children.lock();
children.retain(|(name_, _)| name != name_);
}
// TODO child.destroy() or something?
Ok(())
}
// Common
/// Changes user/group ID or access mode of the node
pub fn set_access(
self: &NodeRef,
uid: Option<UserId>,
gid: Option<GroupId>,
mode: Option<FileMode>,
_check: AccessToken,
) -> Result<(), Error> {
if uid.is_none() && gid.is_none() && mode.is_none() {
return Err(Error::InvalidOperation);
}
let mut metadata = self.metadata()?;
if let Some(uid) = uid {
metadata.uid = uid;
}
if let Some(gid) = gid {
metadata.gid = gid;
}
if let Some(mode) = mode {
metadata.mode = mode;
}
// Update cached props
self.props.lock().metadata = metadata;
if !self.flags.contains(NodeFlags::IN_MEMORY_PROPS) {
// Update permissions in the real node
todo!();
}
Ok(())
}
/// Returns the "metadata" of the file: uid, gid, access mode
pub fn metadata(self: &NodeRef) -> Result<Metadata, Error> {
if self.flags.contains(NodeFlags::IN_MEMORY_PROPS) {
let props = self.props.lock();
@ -71,6 +166,8 @@ impl Node {
self.data_as_common().metadata(self)
}
// TODO clarify directory size
/// Returns the size in bytes of the node
pub fn size(self: &NodeRef) -> Result<u64, Error> {
// Try to fetch the size from the cache
let mut props = self.props.lock();

View File

@ -3,30 +3,36 @@ use core::{any::Any, mem::MaybeUninit};
use yggdrasil_abi::{
error::Error,
io::{DirectoryEntry, OpenOptions},
io::{DirectoryEntry, FileType, OpenOptions},
};
use crate::file::DirectoryOpenPosition;
use super::{CreateInfo, Metadata, NodeRef};
use super::{Metadata, NodeRef};
/// Common interface shared by all filesystem nodes
#[allow(unused)]
pub trait CommonImpl {
/// Returns `&self` as a reference to `dyn Any`
fn as_any(&self) -> &dyn Any {
unimplemented!();
}
/// Fetches the metadata of the file from underlying storage
fn metadata(&self, node: &NodeRef) -> Result<Metadata, Error> {
Err(Error::NotImplemented)
}
/// Fetches the size of the file from underlying storage
fn size(&self, node: &NodeRef) -> Result<u64, Error> {
Err(Error::NotImplemented)
}
}
/// Regular file interface
#[allow(unused)]
pub trait RegularImpl: CommonImpl {
/// Opens the file for reading/writing and returns a `(start position, instance data)` tuple
fn open(
&self,
node: &NodeRef,
@ -35,10 +41,12 @@ pub trait RegularImpl: CommonImpl {
Err(Error::NotImplemented)
}
/// Closes a file
fn close(&self, node: &NodeRef, instance: Option<&Box<dyn Any>>) -> Result<(), Error> {
Ok(())
}
/// Reads data from the file into given buffer
fn read(
&self,
node: &NodeRef,
@ -48,6 +56,7 @@ pub trait RegularImpl: CommonImpl {
) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
/// Writes data to the file from given buffer
fn write(
&self,
node: &NodeRef,
@ -59,12 +68,16 @@ pub trait RegularImpl: CommonImpl {
}
}
/// Directory implementation
#[allow(unused)]
pub trait DirectoryImpl: CommonImpl {
/// Opens a directory for reading its entries. Returns [DirectoryOpenPosition] to specify the
/// starting position.
fn open(&self, node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
Err(Error::NotImplemented)
}
/// Fetches entries from a directory into given buffer
fn read_entries(
&self,
node: &NodeRef,
@ -74,25 +87,41 @@ pub trait DirectoryImpl: CommonImpl {
Err(Error::NotImplemented)
}
fn create(&self, parent: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
/// Creates a child node, but does not associate it with the directory yet
fn create_node(&self, parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
Err(Error::ReadOnly)
}
/// Associates the given node with the directory, creating an entry for it inside
fn attach_node(&self, parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> {
Err(Error::NotImplemented)
}
/// Removes an entry of the directory with given name
fn unlink_node(&self, parent: &NodeRef, name: &str) -> Result<(), Error> {
Err(Error::NotImplemented)
}
/// Fetches the child of the directory with given name
fn lookup(&self, node: &NodeRef, name: &str) -> Result<NodeRef, Error> {
Err(Error::NotImplemented)
}
/// Returns the "length" of the directory in entries
fn len(&self, node: &NodeRef) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
}
/// Symbolic link interface
#[allow(unused)]
pub trait SymlinkImpl: CommonImpl {
/// Returns the target node (if such is available directly) of the link
fn target(&self, node: &NodeRef) -> Result<NodeRef, Error> {
Err(Error::NotImplemented)
}
/// Fetches the contents of the symlink into a [String]
fn read_to_string(&self) -> Result<String, Error> {
let mut data = Box::new([0; 512]);
let len = self.read_link(data.as_mut())?;
@ -103,6 +132,7 @@ pub trait SymlinkImpl: CommonImpl {
Ok(String::from(str))
}
/// Fetches the contents of the symlink into a buffer
fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
Err(Error::NotImplemented)
}

View File

@ -5,15 +5,23 @@ use alloc::string::String;
use super::{Node, NodeRef};
impl Node {
/// Returns the parent node of this node, or itself if no parent is present
pub fn parent(self: &NodeRef) -> NodeRef {
self.parent.lock().as_ref().unwrap_or(self).clone()
}
/// Returns `true` if this node is a root
pub fn is_root(self: &NodeRef) -> bool {
self.parent.lock().is_none()
}
/// Returns the count of entries in the directory
pub fn children_len(&self) -> Result<usize, Error> {
let directory = self.as_directory()?;
Ok(directory.children.lock().len())
}
/// Adds an entry to the directory tree cache
pub fn add_child<S: Into<String>>(
self: &NodeRef,
name: S,

View File

@ -1,16 +1,36 @@
use yggdrasil_abi::{error::Error, io::SeekFrom};
/// Immutable read interface for VFS objects
pub trait Read {
/// Reads bytes into the given buffer
fn read(&self, buf: &mut [u8]) -> Result<usize, Error>;
/// Reads exactly `buf.len()` bytes into the given buffer, fails if such amount is not
/// available
fn read_exact(&self, buf: &mut [u8]) -> Result<(), Error> {
match self.read(buf) {
Ok(count) if count == buf.len() => Ok(()),
Ok(_) => Err(Error::InvalidFile),
Err(err) => Err(err),
}
}
}
/// Immutable write interface for VFS objects
pub trait Write {
/// Writes bytes from the given buffer
fn write(&self, buf: &[u8]) -> Result<usize, Error>;
}
/// Immutable file positioning interface for VFS objects
pub trait Seek {
/// Changes position inside the file to a requested one. Fails if the file does not support
/// seeking.
fn seek(&self, from: SeekFrom) -> Result<u64, Error>;
/// Returns the position within the file, determined as an offset in bytes from the beginning
/// of the file. Fails if the file does not support seeking or the "offset" is not defined for
/// such type of nodes.
fn tell(&self) -> Result<u64, Error> {
self.seek(SeekFrom::Current(0))
}

View File

@ -12,6 +12,7 @@ use super::{
};
use crate::{
arch::{aarch64::mem::table::L3, Architecture},
fs::devfs,
kernel_main, kernel_secondary_main,
mem::{address::IntoRaw, phys, table::EntryLevel, PhysicalAddress, KERNEL_VIRT_OFFSET},
task::runtime,
@ -99,11 +100,7 @@ unsafe extern "C" fn __aarch64_bsp_upper_entry(dtb: PhysicalAddress) -> ! {
exception::init_exceptions();
// // Setup initrd
// super::setup_initrd();
// XXX
// devfs::init();
devfs::init();
runtime::init_task_queue();

View File

@ -27,6 +27,7 @@ use crate::{
devtree::{self, DevTreeIndexPropExt, DevTreeNodeInfo, DeviceTree, FdtMemoryRegionIter},
power::arm_psci::Psci,
},
fs::{Initrd, INITRD_DATA},
mem::{
address::{FromRaw, IntoRaw},
device::RawDeviceMemoryMapping,
@ -314,12 +315,11 @@ impl AArch64 {
let data = unsafe { PhysicalRef::map_slice(initrd_start, len) };
self.initrd.init(data);
// XXX
// INITRD_DATA.init(Initrd {
// phys_page_start: aligned_start,
// phys_page_len: aligned_end - aligned_start,
// data: self.initrd.get().as_ref(),
// });
INITRD_DATA.init(Initrd {
phys_page_start: aligned_start,
phys_page_len: aligned_end - aligned_start,
data: self.initrd.get().as_ref(),
});
}
Ok(())

View File

@ -15,7 +15,7 @@ use device_api::{
interrupt::{InterruptHandler, IpiDeliveryTarget},
Device,
};
use kernel_util::util::OneTimeInit;
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use yggdrasil_abi::error::Error;
use crate::{
@ -27,7 +27,6 @@ use crate::{
address::FromRaw, heap::GLOBAL_HEAP, pointer::PhysicalRef, read_memory, write_memory,
PhysicalAddress,
},
sync::IrqSafeSpinlock,
};
use super::intrinsics;

View File

@ -8,6 +8,7 @@ use device_api::{
},
Device,
};
use kernel_util::sync::IrqSafeSpinlock;
use tock_registers::{
interfaces::{Readable, Writeable},
register_structs,
@ -17,7 +18,6 @@ use tock_registers::{
use crate::{
arch::x86_64::{acpi::AcpiAllocator, apic::local::BSP_APIC_ID, IrqNumber},
mem::{address::FromRaw, device::DeviceMemoryIo, PhysicalAddress},
sync::IrqSafeSpinlock,
};
use super::{APIC_EXTERNAL_OFFSET, MAX_EXTERNAL_VECTORS};

View File

@ -2,7 +2,7 @@
use core::{ptr::null_mut, sync::atomic::Ordering};
use alloc::{boxed::Box, vec::Vec};
use kernel_util::util::OneTimeInit;
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use tock_registers::interfaces::Writeable;
use crate::{
@ -10,7 +10,6 @@ use crate::{
x86_64::{cpuid, gdt, registers::MSR_IA32_KERNEL_GS_BASE, syscall},
CpuMessage,
},
sync::IrqSafeSpinlock,
task::sched::CpuQueue,
};

View File

@ -9,7 +9,7 @@ use device_api::{
timer::MonotonicTimestampProviderDevice, Device,
};
use git_version::git_version;
use kernel_util::util::OneTimeInit;
use kernel_util::{sync::SpinFence, util::OneTimeInit};
use yboot_proto::{v1::AvailableMemoryRegion, LoadProtocolV1};
mod acpi;
@ -55,7 +55,6 @@ use crate::{
table::EntryLevel,
PhysicalAddress,
},
sync::SpinFence,
};
use self::{

View File

@ -2,6 +2,7 @@ use core::time::Duration;
use abi::error::Error;
use device_api::{interrupt::InterruptHandler, timer::MonotonicTimestampProviderDevice, Device};
use kernel_util::sync::IrqSafeSpinlock;
use crate::{
arch::{
@ -11,7 +12,6 @@ use crate::{
},
Architecture, ARCHITECTURE,
},
sync::IrqSafeSpinlock,
task::runtime,
};

View File

@ -5,16 +5,14 @@ use device_api::{
interrupt::InterruptHandler,
Device,
};
use kernel_util::sync::IrqSafeSpinlock;
use crate::{
arch::{
x86_64::{
intrinsics::{IoPort, IoPortAccess},
IrqNumber,
},
Architecture, ARCHITECTURE,
use crate::arch::{
x86_64::{
intrinsics::{IoPort, IoPortAccess},
IrqNumber,
},
sync::IrqSafeSpinlock,
Architecture, ARCHITECTURE,
};
use self::codeset::{CODE_SET_1_00, CODE_SET_1_00_SHIFT};

View File

@ -1,6 +1,7 @@
//! Driver for x86 COM ports
use abi::error::Error;
use device_api::{serial::SerialDevice, Device};
use kernel_util::sync::IrqSafeSpinlock;
use crate::{
arch::x86_64::{
@ -8,7 +9,6 @@ use crate::{
IrqNumber,
},
debug::DebugSink,
sync::IrqSafeSpinlock,
};
// Single port

View File

@ -5,6 +5,7 @@ use acpi_lib::mcfg::McfgEntry;
use alloc::{rc::Rc, vec::Vec};
use bitflags::bitflags;
use device_api::Device;
use kernel_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::error::Error;
mod space;
@ -13,10 +14,7 @@ pub use space::{
ecam::PciEcam, PciConfigSpace, PciConfigurationSpace, PciLegacyConfigurationSpace,
};
use crate::{
mem::{address::FromRaw, PhysicalAddress},
sync::IrqSafeSpinlock,
};
use crate::mem::{address::FromRaw, PhysicalAddress};
bitflags! {
/// Command register of the PCI configuration space

View File

@ -8,6 +8,7 @@ use tock_registers::{
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
};
use vfs::CharDevice;
use crate::{
arch::{aarch64::IrqNumber, Architecture, ARCHITECTURE},
@ -18,6 +19,7 @@ use crate::{
tty::{TtyContext, TtyDevice},
},
device_tree_driver,
fs::devfs::{self, CharDeviceType},
mem::{address::FromRaw, device::DeviceMemoryIo, PhysicalAddress},
task::process::ProcessId,
};
@ -122,22 +124,38 @@ impl TtyDevice for Pl011 {
// }
// }
//
// fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
// match req {
// &mut DeviceRequest::SetTerminalGroup(id) => {
// self.set_signal_group(ProcessId::from(id));
// Ok(())
// }
// DeviceRequest::SetTerminalOptions(config) => self.context.set_config(config),
// DeviceRequest::GetTerminalOptions(config) => {
// config.write(self.context.config());
// Ok(())
// }
// _ => Err(Error::InvalidArgument),
// }
// }
// }
impl CharDevice for Pl011 {
fn write(&self, data: &[u8]) -> Result<usize, Error> {
self.line_write(data)
}
fn read(&self, data: &mut [u8]) -> Result<usize, Error> {
match block! {
self.line_read(data).await
} {
Ok(res) => res,
Err(err) => Err(err),
}
}
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
&mut DeviceRequest::SetTerminalGroup(id) => {
self.set_signal_group(ProcessId::from(id));
Ok(())
}
DeviceRequest::SetTerminalOptions(config) => self.context.set_config(config),
DeviceRequest::GetTerminalOptions(config) => {
config.write(self.context.config());
Ok(())
}
_ => Err(Error::InvalidArgument),
}
}
}
impl SerialDevice for Pl011 {
fn send(&self, byte: u8) -> Result<(), Error> {
self.inner.get().lock().send_byte(byte)
@ -190,7 +208,7 @@ impl Device for Pl011 {
self.inner.init(IrqSafeSpinlock::new(inner));
debug::add_sink(self, LogLevel::Debug);
// devfs::add_char_device(self, CharDeviceType::TtySerial)?;
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
Ok(())
}

View File

@ -21,6 +21,7 @@ pub mod combined {
io::{DeviceRequest, TerminalSize},
};
use device_api::{input::KeyboardConsumer, serial::SerialDevice};
use vfs::CharDevice;
// use vfs::CharDevice;
use crate::device::{
@ -73,43 +74,40 @@ pub mod combined {
}
}
// impl CharDevice for CombinedTerminal {
// fn read(&'static self, blocking: bool, data: &mut [u8]) -> Result<usize, Error> {
// assert!(blocking);
// match block! {
// self.line_read(data).await
// } {
// Ok(res) => res,
// Err(err) => Err(err),
// }
// // self.line_read(data)
// }
impl CharDevice for CombinedTerminal {
fn read(&'static self, data: &mut [u8]) -> Result<usize, Error> {
match block! {
self.line_read(data).await
} {
Ok(res) => res,
Err(err) => Err(err),
}
}
// fn write(&self, blocking: bool, data: &[u8]) -> Result<usize, Error> {
// assert!(blocking);
// self.line_write(data)
// }
fn write(&self, data: &[u8]) -> Result<usize, Error> {
self.line_write(data)
}
// fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
// match req {
// &mut DeviceRequest::SetTerminalGroup(id) => {
// self.set_signal_group(ProcessId::from(id));
// Ok(())
// }
// DeviceRequest::SetTerminalOptions(config) => self.context.set_config(config),
// DeviceRequest::GetTerminalOptions(config) => {
// config.write(self.context.config());
// Ok(())
// }
// DeviceRequest::GetTerminalSize(out) => {
// let (rows, columns) = self.output.text_dimensions();
// out.write(TerminalSize { rows, columns });
// Ok(())
// }
// _ => Err(Error::InvalidArgument),
// }
// }
// }
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
&mut DeviceRequest::SetTerminalGroup(id) => {
self.set_signal_group(ProcessId::from(id));
Ok(())
}
DeviceRequest::SetTerminalOptions(config) => self.context.set_config(config),
DeviceRequest::GetTerminalOptions(config) => {
config.write(self.context.config());
Ok(())
}
DeviceRequest::GetTerminalSize(out) => {
let (rows, columns) = self.output.text_dimensions();
out.write(TerminalSize { rows, columns });
Ok(())
}
_ => Err(Error::InvalidArgument),
}
}
}
}
#[cfg(feature = "fb_console")]

View File

@ -1,18 +1,15 @@
//! Device virtual file system
use core::{
any::Any,
sync::atomic::{AtomicUsize, Ordering},
use core::sync::atomic::{AtomicU32, AtomicU8, AtomicUsize, Ordering};
use abi::error::Error;
use alloc::{format, string::String, sync::Arc};
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use vfs::{
impls::{mdir, read_fn_node, MemoryDirectory},
CharDevice, Node, NodeFlags, NodeRef,
};
use abi::{
error::Error,
io::{FileAttr, FileMode, FileType, OpenOptions},
};
use alloc::{boxed::Box, format, string::String};
use kernel_util::util::OneTimeInit;
use vfs::{
CharDevice, CharDeviceWrapper, Vnode, VnodeImpl, VnodeKind, VnodeRef, DIR_POSITION_FROM_CACHE,
};
use crate::arch::{Architecture, ARCHITECTURE};
/// Describes the kind of a character device
#[derive(Debug)]
@ -23,34 +20,63 @@ pub enum CharDeviceType {
TtySerial,
}
struct DevfsDirectory;
static DEVFS_ROOT: OneTimeInit<NodeRef> = OneTimeInit::new();
impl VnodeImpl for DevfsDirectory {
fn open(
&self,
_node: &VnodeRef,
_opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
Ok((DIR_POSITION_FROM_CACHE, None))
struct RandomState {
data: [u8; 1024],
pos: usize,
last_state: u32,
}
impl RandomState {
fn new(seed: u32) -> Self {
Self {
data: [0; 1024],
pos: 1024,
last_state: seed,
}
}
fn metadata(&self, _node: &VnodeRef) -> Result<FileAttr, Error> {
Ok(FileAttr {
size: 0,
// TODO mutable directory mode
mode: FileMode::from(0o755),
ty: FileType::Directory,
})
fn next(&mut self) -> u32 {
let mut x = self.last_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.last_state = x;
x
}
fn fill_buf(&mut self) {
self.pos = 0;
for i in (0..self.data.len()).step_by(4) {
let v = self.next();
self.data[i..i + 4].copy_from_slice(&v.to_ne_bytes());
}
}
fn read_buf(&mut self, buf: &mut [u8]) {
let mut rem = buf.len();
let mut pos = 0;
while rem != 0 {
if self.pos == 1024 {
self.fill_buf();
}
let count = core::cmp::min(rem, 1024 - self.pos);
buf[pos..pos + count].copy_from_slice(&self.data[self.pos..self.pos + count]);
self.pos += count;
rem -= count;
pos += count;
}
}
}
static DEVFS_ROOT: OneTimeInit<VnodeRef> = OneTimeInit::new();
/// Sets up the device filesystem
pub fn init() {
let node = Vnode::new("", VnodeKind::Directory);
node.set_data(Box::new(DevfsDirectory));
DEVFS_ROOT.init(node);
let root = MemoryDirectory::empty();
DEVFS_ROOT.init(root);
}
/// Returns the root of the devfs.
@ -58,19 +84,16 @@ pub fn init() {
/// # Panics
///
/// Will panic if the devfs hasn't yet been initialized.
pub fn root() -> &'static VnodeRef {
pub fn root() -> &'static NodeRef {
DEVFS_ROOT.get()
}
fn _add_char_device(dev: &'static dyn CharDevice, name: String) -> Result<(), Error> {
infoln!("Add char device: {}", name);
let node = Vnode::new(name, VnodeKind::Char);
node.set_data(Box::new(CharDeviceWrapper::new(dev)));
let node = Node::char(dev, NodeFlags::IN_MEMORY_PROPS);
DEVFS_ROOT.get().add_child(node);
Ok(())
DEVFS_ROOT.get().add_child(name, node)
}
/// Adds a character device to the devfs
@ -88,3 +111,24 @@ pub fn add_char_device(dev: &'static dyn CharDevice, kind: CharDeviceType) -> Re
_add_char_device(dev, name)
}
pub fn add_pseudo_devices() -> Result<(), Error> {
let now = ARCHITECTURE
.monotonic_timer()
.monotonic_timestamp()
.unwrap();
let random_seed = now.subsec_millis();
let mut random_state = RandomState::new(random_seed);
random_state.fill_buf();
let random_state = IrqSafeSpinlock::new(random_state);
let random = read_fn_node(move |_, buf| {
random_state.lock().read_buf(buf);
Ok(buf.len())
});
let root = root();
root.add_child("random", random)?;
Ok(())
}

View File

@ -4,7 +4,7 @@ use core::ptr::NonNull;
use kernel_util::util::OneTimeInit;
use memfs::block::{self, BlockAllocator};
use vfs::VnodeRef;
use vfs::NodeRef;
use yggdrasil_abi::{error::Error, io::MountOptions};
use crate::mem::{phys, PhysicalAddress};
@ -44,7 +44,7 @@ unsafe impl BlockAllocator for FileBlockAllocator {
}
/// Constructs an instance of a filesystem for given set of [MountOptions]
pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Error> {
pub fn create_filesystem(options: &MountOptions) -> Result<NodeRef, Error> {
let fs_name = options.filesystem.unwrap();
match fs_name {

View File

@ -10,161 +10,16 @@ use alloc::{
};
use git_version::git_version;
use kernel_util::util::OneTimeInit;
use vfs::{Vnode, VnodeImpl, VnodeKind, VnodeRef, DIR_POSITION_FROM_CACHE};
use vfs::{
impls::{const_value_node, mdir, read_fn_node},
NodeRef,
};
use crate::{debug, util};
trait GetterFn<T: ToString> = Fn() -> Result<T, Error>;
trait ReaderFn = Fn(u64, &mut [u8]) -> Result<usize, Error>;
static ROOT: OneTimeInit<NodeRef> = OneTimeInit::new();
struct SysfsDirectory;
struct SysfsGetterNode<T: ToString, R: GetterFn<T>> {
getter: R,
_pd: PhantomData<T>,
}
struct SysfsReaderNode<R: ReaderFn> {
reader: R,
}
impl<T: ToString, R: GetterFn<T>> SysfsGetterNode<T, R> {
pub const fn new(getter: R) -> Self {
Self {
getter,
_pd: PhantomData,
}
}
}
impl<R: ReaderFn> SysfsReaderNode<R> {
pub const fn new(reader: R) -> Self {
Self { reader }
}
}
impl VnodeImpl for SysfsDirectory {
fn open(
&self,
_node: &VnodeRef,
_opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
Ok((DIR_POSITION_FROM_CACHE, None))
}
fn metadata(&self, _node: &VnodeRef) -> Result<FileAttr, Error> {
Ok(FileAttr {
size: 0,
// TODO mutable directory mode
mode: FileMode::from(0o755),
ty: FileType::Directory,
})
}
}
impl<T: ToString, R: GetterFn<T>> VnodeImpl for SysfsGetterNode<T, R> {
fn open(
&self,
_node: &VnodeRef,
_opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
let value = (self.getter)()?;
let inner = Box::new(value.to_string());
Ok((0, Some(inner)))
}
fn read(
&self,
node: &VnodeRef,
pos: u64,
inner: Option<&mut Box<dyn Any>>,
data: &mut [u8],
) -> Result<usize, Error> {
let string = inner.unwrap().downcast_ref::<String>().unwrap();
let pos = pos as usize;
let bytes = string.as_bytes();
if pos >= bytes.len() {
return Ok(0);
}
let len = core::cmp::min(bytes.len() - pos, data.len());
data[..len].copy_from_slice(&bytes[pos..pos + len]);
Ok(len)
}
fn size(&self, node: &VnodeRef) -> Result<u64, Error> {
Ok(0)
}
fn metadata(&self, node: &VnodeRef) -> Result<FileAttr, Error> {
Ok(FileAttr {
size: 0,
mode: FileMode::from(0o444),
ty: FileType::File,
})
}
}
impl<R: ReaderFn> VnodeImpl for SysfsReaderNode<R> {
fn open(
&self,
node: &VnodeRef,
opts: OpenOptions,
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
Ok((0, None))
}
fn read(
&self,
node: &VnodeRef,
pos: u64,
_inner: Option<&mut Box<dyn Any>>,
data: &mut [u8],
) -> Result<usize, Error> {
(self.reader)(pos, data)
}
fn size(&self, node: &VnodeRef) -> Result<u64, Error> {
todo!()
}
fn metadata(&self, node: &VnodeRef) -> Result<FileAttr, Error> {
todo!()
}
}
static ROOT: OneTimeInit<VnodeRef> = OneTimeInit::new();
fn getter<S: Into<String>, T: ToString + 'static, R: GetterFn<T> + 'static>(
name: S,
get: R,
) -> VnodeRef {
let data = Box::new(SysfsGetterNode::new(get));
let node = Vnode::new(name, VnodeKind::Regular);
node.set_data(data);
node
}
fn reader<S: Into<String>, R: ReaderFn + 'static>(name: S, read: R) -> VnodeRef {
let data = Box::new(SysfsReaderNode::new(read));
let node = Vnode::new(name, VnodeKind::Regular);
node.set_data(data);
node
}
fn dir<S: Into<String>, I: IntoIterator<Item = VnodeRef>>(name: S, items: I) -> VnodeRef {
let node = Vnode::new(name, VnodeKind::Directory);
node.set_data(Box::new(SysfsDirectory));
for item in items {
node.add_child(item);
}
node
}
pub fn root() -> &'static VnodeRef {
pub fn root() -> &'static NodeRef {
ROOT.get()
}
@ -173,15 +28,15 @@ fn read_kernel_log(pos: u64, buffer: &mut [u8]) -> Result<usize, Error> {
}
pub fn init() {
let kernel = dir(
"kernel",
[
getter("version", || Ok(env!("CARGO_PKG_VERSION"))),
getter("rev", || Ok(git_version!())),
reader("log", read_kernel_log),
],
);
let root = dir("", [kernel, getter("arch", || Ok(util::arch_str()))]);
let d_kernel = mdir([
("version", const_value_node(env!("CARGO_PKG_VERSION"))),
("rev", const_value_node(git_version!())),
("log", read_fn_node(read_kernel_log)),
]);
let root = mdir([
("kernel", d_kernel),
("arch", const_value_node(util::arch_str())),
]);
ROOT.init(root);
}

View File

@ -3,8 +3,19 @@ use abi::{
error::Error,
io::{OpenOptions, RawFd},
};
use memfs::MemoryFilesystem;
use vfs::{Action, IoContext, NodeRef};
use crate::proc;
use crate::{
fs::{devfs, FileBlockAllocator, INITRD_DATA},
proc,
};
fn setup_root() -> Result<NodeRef, Error> {
let initrd_data = INITRD_DATA.get();
let fs = MemoryFilesystem::<FileBlockAllocator>::from_slice(initrd_data.data).unwrap();
fs.root()
}
/// Kernel's "main" process function.
///
@ -14,47 +25,50 @@ use crate::proc;
/// initialization has finished.
pub fn kinit() -> Result<(), Error> {
infoln!("In main");
loop {}
// #[cfg(feature = "fb_console")]
// {
// use crate::{device::display::console::update_consoles_task, task::runtime};
#[cfg(feature = "fb_console")]
{
use crate::{device::display::console::update_consoles_task, task::runtime};
// runtime::spawn(async move {
// update_consoles_task().await;
// })?;
// }
runtime::spawn(async move {
update_consoles_task().await;
})?;
}
// let root = setup_root()?;
let root = setup_root()?;
// let ioctx = IoContext::new(root);
// let node = ioctx.find(None, "/init", true, true)?;
// let file = node.open(OpenOptions::READ)?;
let mut ioctx = IoContext::new(root);
let file = ioctx.open_exec(None, "/init")?;
// let devfs = devfs::root();
// #[cfg(target_arch = "x86_64")]
// let console = ioctx.find(Some(devfs.clone()), "tty0", true, true)?;
// #[cfg(target_arch = "aarch64")]
// let console = ioctx.find(Some(devfs.clone()), "ttyS0", true, true)?;
// let stdin = console.open(OpenOptions::READ)?;
// let stdout = console.open(OpenOptions::WRITE)?;
// let stderr = stdout.clone();
let devfs = devfs::root();
// {
// // XXX
// let (user_init, user_init_main) =
// proc::exec::load_elf("init", file, &["/init", "xxx"], &[])?;
// let mut io = user_init.io.lock();
// io.set_ioctx(ioctx);
// io.set_file(RawFd::STDIN, stdin)?;
// io.set_file(RawFd::STDOUT, stdout)?;
// io.set_file(RawFd::STDERR, stderr)?;
// drop(io);
#[cfg(target_arch = "x86_64")]
let console = ioctx.find(Some(devfs.clone()), "tty0", true, true)?;
#[cfg(target_arch = "aarch64")]
let console = ioctx.find(Some(devfs.clone()), "ttyS0", true, true)?;
// user_init.set_session_terminal(console);
let access = ioctx.check_access(Action::Read, &console)?;
let stdin = console.open(OpenOptions::READ, access)?;
let access = ioctx.check_access(Action::Write, &console)?;
let stdout = console.open(OpenOptions::WRITE, access)?;
let stderr = stdout.clone();
// user_init_main.enqueue_somewhere();
// }
{
// XXX
let (user_init, user_init_main) =
proc::exec::load_elf("init", file, &["/init", "xxx"], &[])?;
// Ok(())
let mut io = user_init.io.lock();
io.set_ioctx(ioctx);
io.set_file(RawFd::STDIN, stdin)?;
io.set_file(RawFd::STDOUT, stdout)?;
io.set_file(RawFd::STDERR, stderr)?;
drop(io);
user_init.set_session_terminal(console);
user_init_main.enqueue_somewhere();
}
Ok(())
}

View File

@ -35,6 +35,7 @@ use kernel_util::sync::SpinFence;
use crate::{
arch::{ArchitectureImpl, ARCHITECTURE},
fs::{devfs, sysfs},
mem::heap,
task::{spawn_kernel_closure, Cpu},
};
@ -50,7 +51,7 @@ pub mod debug;
pub mod arch;
pub mod device;
// pub mod fs;
pub mod fs;
pub mod init;
pub mod mem;
pub mod panic;
@ -87,7 +88,8 @@ pub fn kernel_main() -> ! {
debugln!("Heap: {:#x?}", heap::heap_range());
// Setup the sysfs
// sysfs::init();
sysfs::init();
devfs::add_pseudo_devices().unwrap();
unsafe {
ARCHITECTURE.start_application_processors();

View File

@ -27,15 +27,11 @@ struct FileReader<'a> {
impl elf::io_traits::InputStream for FileReader<'_> {
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), elf::io_traits::StreamError> {
self.file
.borrow_mut()
.read_exact(buf)
.map_err(conv_stream_error)
self.file.read_exact(buf).map_err(conv_stream_error)
}
fn seek(&mut self, pos: elf::io_traits::SeekFrom) -> Result<u64, elf::io_traits::StreamError> {
self.file
.borrow_mut()
.seek(conv_seek_from(pos))
.map_err(conv_stream_error)
}
@ -179,9 +175,9 @@ fn load_segment(
space,
phdr.p_vaddr as usize,
|off, mut dst| {
let mut source = file.file.borrow_mut();
source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
source.read_exact(dst.deref_mut())
file.file
.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
file.file.read_exact(dst.deref_mut())
},
phdr.p_filesz as usize,
)?;
@ -247,9 +243,9 @@ fn tls_segment(
space,
base_address + data_offset,
|off, mut dst| {
let mut source = file.file.borrow_mut();
source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
source.read_exact(dst.deref_mut())
file.file
.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
file.file.read_exact(dst.deref_mut())
},
data_size,
)?;

View File

@ -7,6 +7,7 @@ use abi::{
process::ProgramArgumentInner,
};
use alloc::{string::String, sync::Arc};
use vfs::FileRef;
use crate::{
mem::{
@ -145,8 +146,7 @@ fn setup_binary<S: Into<String>>(
}
}
// XXX
let tls_address = 0; // proc::elf::clone_tls(&space, &image)?;
let tls_address = proc::elf::clone_tls(&space, &image)?;
let context = TaskContext::user(
image.entry,
@ -165,15 +165,15 @@ fn setup_binary<S: Into<String>>(
// Ok(Process::new_with_context(name, Some(space), context))
}
// /// Loads an ELF bianary from `file` and sets up all the necessary data/argument memory
// pub fn load_elf<S: Into<String>>(
// name: S,
// file: FileRef,
// args: &[&str],
// envs: &[&str],
// ) -> Result<(Arc<Process>, Arc<Thread>), Error> {
// let space = ProcessAddressSpace::new()?;
// let image = proc::elf::load_elf_from_file(&space, file)?;
//
// setup_binary(name, space, image, args, envs)
// }
/// Loads an ELF bianary from `file` and sets up all the necessary data/argument memory
pub fn load_elf<S: Into<String>>(
name: S,
file: FileRef,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
let space = ProcessAddressSpace::new()?;
let image = proc::elf::load_elf_from_file(&space, file)?;
setup_binary(name, space, image, args, envs)
}

View File

@ -1,12 +1,67 @@
//! Process I/O management
use abi::{error::Error, io::RawFd};
use alloc::collections::{btree_map::Entry, BTreeMap};
use kernel_util::sync::IrqSafeSpinlock;
use vfs::{FileRef, IoContext};
/// I/O context of a process, contains information like root, current directory and file
/// descriptor table
pub struct ProcessIo {}
pub struct ProcessIo {
ioctx: Option<IoContext>,
files: BTreeMap<RawFd, FileRef>,
}
impl ProcessIo {
/// Constructs an uninitialized I/O context
pub fn new() -> Self {
todo!()
Self {
ioctx: None,
files: BTreeMap::new(),
}
}
pub fn file(&self, fd: RawFd) -> Result<&FileRef, Error> {
self.files.get(&fd).ok_or(Error::InvalidFile)
}
pub fn ioctx(&mut self) -> &mut IoContext {
self.ioctx.as_mut().unwrap()
}
pub fn set_ioctx(&mut self, ioctx: IoContext) {
self.ioctx = Some(ioctx);
}
pub fn set_file(&mut self, fd: RawFd, file: FileRef) -> Result<(), Error> {
if self.files.contains_key(&fd) {
return Err(Error::AlreadyExists);
}
self.files.insert(fd, file);
Ok(())
}
pub fn place_file(&mut self, file: FileRef) -> Result<RawFd, Error> {
for idx in 0..64 {
let fd = RawFd::from(idx);
if let Entry::Vacant(e) = self.files.entry(fd) {
e.insert(file);
return Ok(fd);
}
}
// TODO OutOfFiles
Err(Error::OutOfMemory)
}
pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> {
// Do nothing, file will be dropped and closed
self.files.remove(&fd).ok_or(Error::InvalidFile)?;
Ok(())
}
pub fn retain<F: Fn(&RawFd, &mut FileRef) -> bool>(&mut self, predicate: F) {
self.files.retain(predicate);
}
}

View File

@ -1,5 +1,5 @@
//! Internal management for processes
// pub mod elf;
pub mod elf;
pub mod exec;
pub mod io;

View File

@ -4,11 +4,13 @@ use core::{mem::MaybeUninit, time::Duration};
use abi::{
error::Error,
io::{DeviceRequest, DirectoryEntry, FileAttr, FileMode, OpenOptions, RawFd, SeekFrom},
path::Path,
process::{ExitCode, MutexOperation, Signal, SpawnOption, SpawnOptions, ThreadSpawnOptions},
syscall::SyscallFunction,
};
use alloc::rc::Rc;
use alloc::{rc::Rc, sync::Arc};
use kernel_util::sync::IrqSafeSpinlockGuard;
use vfs::{CreateInfo, IoContext, NodeRef, Read, Seek, Write};
// use vfs::{IoContext, Read, ReadDirectory, Seek, VnodeKind, VnodeRef, Write};
use yggdrasil_abi::{
error::SyscallResult,
@ -18,6 +20,7 @@ use yggdrasil_abi::{
use crate::{
block,
debug::LogLevel,
fs,
mem::{phys, table::MapAttributes},
proc::{self, io::ProcessIo},
task::{
@ -30,26 +33,29 @@ use crate::{
mod arg;
use arg::*;
// fn run_with_io<T, F: FnOnce(IrqSafeSpinlockGuard<ProcessIo>) -> T>(proc: &Process, f: F) -> T {
// let io = proc.io.lock();
// f(io)
// }
//
// fn run_with_io_at<
// T,
// F: FnOnce(Option<VnodeRef>, IrqSafeSpinlockGuard<ProcessIo>) -> Result<T, Error>,
// >(
// proc: &Process,
// at: Option<RawFd>,
// f: F,
// ) -> Result<T, Error> {
// let io = proc.io.lock();
// let at = at
// .map(|fd| io.file(fd).and_then(|f| f.borrow().node()))
// .transpose()?;
//
// f(at, io)
// }
fn run_with_io<T, F: FnOnce(IrqSafeSpinlockGuard<ProcessIo>) -> T>(proc: &Process, f: F) -> T {
let io = proc.io.lock();
f(io)
}
fn run_with_io_at<
T,
F: FnOnce(Option<NodeRef>, IrqSafeSpinlockGuard<ProcessIo>) -> Result<T, Error>,
>(
proc: &Process,
at: Option<RawFd>,
f: F,
) -> Result<T, Error> {
let io = proc.io.lock();
let at = at
.map(|fd| {
io.file(fd)
.and_then(|f| f.node().ok_or(Error::InvalidFile).cloned())
})
.transpose()?;
f(at, io)
}
fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error> {
let thread = Thread::current();
@ -118,7 +124,206 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
}
// I/O
SyscallFunction::Open => {
let at = arg_option_fd(args[0] as u32);
let path = arg_user_str(args[1] as usize, args[2] as usize)?;
let opts = OpenOptions::from(args[3] as u32);
let mode = FileMode::from(args[4] as u32);
run_with_io_at(&process, at, |at, mut io| {
let file = io.ioctx().open(at, path, opts, mode)?;
// TODO NO_CTTY?
if process.session_terminal().is_none() &&
let Some(node) = file.node() && node.is_terminal() {
debugln!("Session terminal set for #{}: {}", process.id(), path);
process.set_session_terminal(node.clone());
}
let fd = io.place_file(file)?;
Ok(fd.0 as usize)
})
}
SyscallFunction::OpenDirectory => {
let at = arg_option_fd(args[0] as u32);
let path = arg_user_str(args[1] as usize, args[2] as usize)?;
run_with_io_at(&process, at, |at, mut io| {
let node = io.ioctx().find(at, path, true, true)?;
let access = io.ioctx().check_access(vfs::Action::Read, &node)?;
let file = node.open_directory(access)?;
let fd = io.place_file(file)?;
Ok(fd.0 as usize)
})
}
SyscallFunction::Read => {
let fd = RawFd(args[0] as u32);
let data = arg_buffer_mut(args[1] as _, args[2] as _)?;
run_with_io(&process, |io| io.file(fd)?.read(data))
}
SyscallFunction::Write => {
let fd = RawFd(args[0] as u32);
let data = arg_buffer_ref(args[1] as _, args[2] as _)?;
run_with_io(&process, |io| io.file(fd)?.write(data))
}
SyscallFunction::Seek => {
let fd = RawFd(args[0] as u32);
let pos = SeekFrom::from(args[1]);
run_with_io(&process, |io| io.file(fd)?.seek(pos).map(|v| v as usize))
}
SyscallFunction::ReadDirectory => {
let fd = RawFd(args[0] as u32);
let buffer = arg_user_slice_mut::<MaybeUninit<DirectoryEntry>>(
args[1] as usize,
args[2] as usize,
)?;
run_with_io(&process, |io| io.file(fd)?.read_dir(buffer))
}
SyscallFunction::Close => {
let fd = RawFd(args[0] as u32);
run_with_io(&process, |mut io| {
io.close_file(fd)?;
Ok(0)
})
}
SyscallFunction::Mount => {
let options = arg_user_ref::<MountOptions>(args[0] as usize)?;
run_with_io(&process, |mut io| {
let fs_root = fs::create_filesystem(options)?;
io.ioctx().mount(options.target, fs_root)?;
Ok(0)
})
}
SyscallFunction::Unmount => {
todo!();
}
SyscallFunction::DeviceRequest => {
let fd = RawFd(args[0] as u32);
let req = arg_user_mut::<DeviceRequest>(args[1] as usize)?;
run_with_io(&process, |io| {
let file = io.file(fd)?;
let node = file.node().ok_or(Error::InvalidFile)?;
node.device_request(req)?;
Ok(0)
})
}
SyscallFunction::GetMetadata => {
let at = arg_option_fd(args[0] as u32);
let path = arg_user_str(args[1] as usize, args[2] as usize)?;
let buffer = arg_user_mut::<MaybeUninit<FileAttr>>(args[3] as usize)?;
let follow = args[4] != 0;
run_with_io_at(&process, at, |at, mut io| {
let node = if path.is_empty() {
at.ok_or(Error::InvalidArgument)?
} else {
io.ioctx().find(None, path, follow, true)?
};
let metadata = node.metadata()?;
let size = node.size()?;
buffer.write(FileAttr {
size,
ty: node.ty(),
mode: metadata.mode,
});
Ok(0)
})
}
SyscallFunction::CreateDirectory => {
let at = arg_option_fd(args[0] as u32);
let path = arg_user_str(args[1] as usize, args[2] as usize)?;
let mode = FileMode::from(args[3] as u32);
run_with_io_at(&process, at, |at, mut io| {
io.ioctx().create_directory(at, path, mode)?;
Ok(0)
})
}
SyscallFunction::Remove => {
let at = arg_option_fd(args[0] as u32);
let path = arg_user_str(args[1] as usize, args[2] as usize)?;
let recurse = args[3] != 0;
run_with_io_at(&process, at, |at, mut io| {
io.ioctx().remove_file(at, path)?;
Ok(0)
})
}
SyscallFunction::RemoveDirectory => {
todo!()
}
// Process management
SyscallFunction::SpawnProcess => {
let options = arg_user_ref::<SpawnOptions>(args[0] as usize)?;
run_with_io(&process, |mut io| {
// let node = io.ioctx().find(None, options.program, true, true)?;
// Setup a new process from the file
let file = io.ioctx().open_exec(None, options.program)?;
// let file = node.open(OpenOptions::READ)?;
let (child_process, child_main) = proc::exec::load_elf(
options.program,
file,
options.arguments,
options.environment,
)?;
let pid: u32 = child_process.id().into();
// Inherit group and session from the creator
child_process.inherit(&process)?;
// Inherit root from the creator
// let child_ioctx = IoContext::new(io.ioctx().root().clone());
let child_ioctx = IoContext::inherit(io.ioctx());
let mut child_io = child_process.io.lock();
child_io.set_ioctx(child_ioctx);
for opt in options.optional {
match opt {
&SpawnOption::InheritFile { source, child } => {
let src_file = io.file(source)?;
child_io.set_file(child, src_file.clone())?;
}
&SpawnOption::SetProcessGroup(pgroup) => {
child_process.set_group_id(pgroup.into());
}
_ => (),
}
}
if let Some(fd) = options.optional.iter().find_map(|item| {
if let &SpawnOption::GainTerminal(fd) = item {
Some(fd)
} else {
None
}
}) {
debugln!("{} requested terminal {:?}", pid, fd);
let file = child_io.file(fd)?;
let node = file.node().ok_or(Error::InvalidFile)?;
let mut req = DeviceRequest::SetTerminalGroup(child_process.group_id().into());
node.device_request(&mut req)?;
}
drop(child_io);
child_main.enqueue_somewhere();
Ok(pid as _)
})
}
SyscallFunction::SpawnThread => {
let options = arg_user_ref::<ThreadSpawnOptions>(args[0] as usize)?;
let id = process.spawn_thread(options)?;
@ -180,6 +385,25 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
process.set_group_id(group_id);
Ok(0)
}
SyscallFunction::StartSession => {
let session_terminal = process.clear_session_terminal();
if let Some(ctty) = session_terminal {
// Drop all FDs referring to the old session terminal
run_with_io(&process, |mut io| {
io.retain(|_, f| {
f.node()
.map(|node| !Arc::ptr_eq(&node, &ctty))
.unwrap_or(true)
});
});
}
process.set_session_id(process.id());
process.set_group_id(process.id());
Ok(0)
}
// Waiting and polling
SyscallFunction::WaitProcess => {
let pid = ProcessId::from(args[0] as u32);
@ -197,10 +421,6 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
}
_ => {
todo!("System call: {:?}", func);
}
}
}

View File

@ -20,6 +20,7 @@ use alloc::{
};
use futures_util::Future;
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use vfs::NodeRef;
use crate::{
mem::{
@ -129,7 +130,7 @@ struct ProcessInner {
session_id: ProcessId,
group_id: ProcessId,
// session_terminal: Option<VnodeRef>,
session_terminal: Option<NodeRef>,
threads: Vec<Arc<Thread>>,
mutexes: BTreeMap<usize, Arc<UserspaceMutex>>,
}
@ -170,7 +171,7 @@ impl Process {
state: ProcessState::Running,
session_id: id,
group_id: id,
// session_terminal: None,
session_terminal: None,
threads: Vec::new(),
mutexes: BTreeMap::new(),
}),
@ -245,28 +246,28 @@ impl Process {
}
// Resources
// pub fn session_terminal(&self) -> Option<VnodeRef> {
// self.inner.lock().session_terminal.clone()
// }
pub fn session_terminal(&self) -> Option<NodeRef> {
self.inner.lock().session_terminal.clone()
}
// pub fn set_session_terminal(&self, node: VnodeRef) {
// self.inner.lock().session_terminal.replace(node);
// }
pub fn set_session_terminal(&self, node: NodeRef) {
self.inner.lock().session_terminal.replace(node);
}
// pub fn clear_session_terminal(&self) -> Option<VnodeRef> {
// self.inner.lock().session_terminal.take()
// }
pub fn clear_session_terminal(&self) -> Option<NodeRef> {
self.inner.lock().session_terminal.take()
}
// pub fn inherit(&self, parent: &Process) -> Result<(), Error> {
// let mut our_inner = self.inner.lock();
// let their_inner = parent.inner.lock();
pub fn inherit(&self, parent: &Process) -> Result<(), Error> {
let mut our_inner = self.inner.lock();
let their_inner = parent.inner.lock();
// our_inner.session_id = their_inner.session_id;
// our_inner.group_id = their_inner.group_id;
// our_inner.session_terminal = their_inner.session_terminal.clone();
our_inner.session_id = their_inner.session_id;
our_inner.group_id = their_inner.group_id;
our_inner.session_terminal = their_inner.session_terminal.clone();
// Ok(())
// }
Ok(())
}
// State
pub fn get_exit_status(&self) -> Option<ExitCode> {