501 lines
15 KiB
Rust
501 lines
15 KiB
Rust
use core::{any::Any, cell::Cell, fmt, mem::MaybeUninit};
|
|
|
|
use alloc::{boxed::Box, sync::Arc};
|
|
use yggdrasil_abi::{
|
|
error::Error,
|
|
io::{DirectoryEntry, OpenOptions, SeekFrom},
|
|
};
|
|
|
|
use crate::{
|
|
device::{BlockDeviceWrapper, CharDeviceWrapper},
|
|
node::NodeRef,
|
|
traits::{Read, Seek, Write},
|
|
};
|
|
|
|
use self::{
|
|
device::{BlockFile, CharFile},
|
|
directory::DirectoryFile,
|
|
regular::RegularFile,
|
|
};
|
|
|
|
mod device;
|
|
mod directory;
|
|
mod regular;
|
|
|
|
pub enum DirectoryOpenPosition {
|
|
FromPhysical(u64),
|
|
FromCache,
|
|
}
|
|
|
|
pub type FileRef = Arc<File>;
|
|
|
|
// TODO some kind of a mutex instead?
|
|
pub enum File {
|
|
Directory(DirectoryFile),
|
|
Regular(RegularFile),
|
|
Block(BlockFile),
|
|
Char(CharFile),
|
|
}
|
|
|
|
impl File {
|
|
pub fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
|
|
let position = Cell::new(position.into());
|
|
Arc::new(Self::Directory(DirectoryFile { node, position }))
|
|
}
|
|
|
|
pub fn regular(
|
|
node: NodeRef,
|
|
position: u64,
|
|
instance_data: Option<Box<dyn Any>>,
|
|
opts: OpenOptions,
|
|
) -> Arc<Self> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
Arc::new(Self::Regular(RegularFile {
|
|
node,
|
|
read,
|
|
write,
|
|
instance_data,
|
|
position: Cell::new(position),
|
|
}))
|
|
}
|
|
|
|
pub fn block(
|
|
device: BlockDeviceWrapper,
|
|
node: NodeRef,
|
|
opts: OpenOptions,
|
|
) -> Result<Arc<Self>, Error> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
if read && !device.is_readable() {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
if write && !device.is_writable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Arc::new(Self::Block(BlockFile {
|
|
device,
|
|
node,
|
|
position: Cell::new(0),
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
pub fn char(
|
|
device: CharDeviceWrapper,
|
|
node: NodeRef,
|
|
opts: OpenOptions,
|
|
) -> Result<Arc<Self>, Error> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
if read && !device.is_readable() {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
if write && !device.is_writable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Arc::new(Self::Char(CharFile {
|
|
device,
|
|
node,
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
|
|
match self {
|
|
Self::Directory(dir) => dir.read_entries(entries),
|
|
_ => Err(Error::NotADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Read for File {
|
|
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
|
match self {
|
|
Self::Regular(file) => file.read(buf),
|
|
Self::Block(file) => file.read(buf),
|
|
Self::Char(file) => file.read(buf),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Write for File {
|
|
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
|
match self {
|
|
Self::Regular(file) => file.write(buf),
|
|
Self::Block(file) => file.write(buf),
|
|
Self::Char(file) => file.write(buf),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Seek for File {
|
|
fn tell(&self) -> Result<u64, Error> {
|
|
match self {
|
|
Self::Regular(file) => Ok(file.position.get()),
|
|
Self::Block(file) => Ok(file.position.get()),
|
|
Self::Char(_) => Err(Error::InvalidOperation),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
|
|
fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
|
match self {
|
|
Self::Regular(file) => file.seek(from),
|
|
Self::Block(file) => file.seek(from),
|
|
Self::Char(_) => Err(Error::InvalidOperation),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for File {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Regular(file) => f
|
|
.debug_struct("RegularFile")
|
|
.field("position", &file.position.get())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Block(file) => f
|
|
.debug_struct("BlockFile")
|
|
.field("position", &file.position.get())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Char(file) => f
|
|
.debug_struct("CharFile")
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use core::{any::Any, cell::RefCell, mem::MaybeUninit, str::FromStr};
|
|
use std::sync::Arc;
|
|
|
|
use kernel_util::sync::IrqSafeSpinlock;
|
|
use yggdrasil_abi::{
|
|
error::Error,
|
|
io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
|
|
util::FixedString,
|
|
};
|
|
|
|
use crate::{
|
|
device::{BlockDevice, CharDevice},
|
|
file::DirectoryOpenPosition,
|
|
node::{CommonImpl, DirectoryImpl, Node, NodeRef, RegularImpl},
|
|
traits::{Read, Seek, Write},
|
|
};
|
|
|
|
#[test]
|
|
fn physical_dir_read() {
|
|
struct D {
|
|
entries: Vec<(String, NodeRef)>,
|
|
}
|
|
struct F;
|
|
|
|
impl CommonImpl for D {}
|
|
impl DirectoryImpl for D {
|
|
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
|
Ok(DirectoryOpenPosition::FromPhysical(0))
|
|
}
|
|
|
|
fn read_entries(
|
|
&self,
|
|
_node: &NodeRef,
|
|
pos: u64,
|
|
entries: &mut [MaybeUninit<DirectoryEntry>],
|
|
) -> Result<(usize, u64), Error> {
|
|
let pos = pos as usize;
|
|
if pos == self.entries.len() {
|
|
return Ok((0, pos as u64));
|
|
}
|
|
|
|
let count = core::cmp::min(entries.len(), self.entries.len() - pos);
|
|
for i in 0..count {
|
|
let (name, node) = &self.entries[i];
|
|
let entry = DirectoryEntry {
|
|
name: FixedString::from_str(name)?,
|
|
ty: node.ty(),
|
|
};
|
|
|
|
entries[i].write(entry);
|
|
}
|
|
|
|
Ok((count, (pos + count) as u64))
|
|
}
|
|
}
|
|
|
|
impl CommonImpl for F {}
|
|
impl RegularImpl for F {}
|
|
|
|
let d = Node::directory(D {
|
|
entries: Vec::from_iter([
|
|
("f1".to_owned(), Node::regular(F)),
|
|
("f2".to_owned(), Node::regular(F)),
|
|
("f3".to_owned(), Node::regular(F)),
|
|
]),
|
|
});
|
|
|
|
let f = d.open_directory().unwrap();
|
|
|
|
let mut entries = [MaybeUninit::uninit(); 16];
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 3);
|
|
|
|
unsafe {
|
|
assert_eq!(
|
|
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
|
&[
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f1").unwrap(),
|
|
ty: FileType::File,
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f2").unwrap(),
|
|
ty: FileType::File,
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f3").unwrap(),
|
|
ty: FileType::File
|
|
}
|
|
]
|
|
);
|
|
}
|
|
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cache_dir_read() {
|
|
struct D;
|
|
|
|
impl CommonImpl for D {}
|
|
impl DirectoryImpl for D {
|
|
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
|
Ok(DirectoryOpenPosition::FromCache)
|
|
}
|
|
}
|
|
|
|
let d = Node::directory(D);
|
|
let child = Node::directory(D);
|
|
|
|
d.add_child("child1", child).unwrap();
|
|
|
|
let f = d.open_directory().unwrap();
|
|
|
|
let mut entries = [MaybeUninit::uninit(); 16];
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 3);
|
|
unsafe {
|
|
assert_eq!(
|
|
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
|
&[
|
|
DirectoryEntry {
|
|
name: FixedString::from_str(".").unwrap(),
|
|
ty: FileType::Directory
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("..").unwrap(),
|
|
ty: FileType::Directory
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("child1").unwrap(),
|
|
ty: FileType::Directory
|
|
}
|
|
]
|
|
);
|
|
}
|
|
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn file_read_write() {
|
|
struct F {
|
|
data: Arc<RefCell<Vec<u8>>>,
|
|
}
|
|
|
|
impl CommonImpl for F {
|
|
fn size(&self, _node: &NodeRef) -> Result<usize, Error> {
|
|
Ok(self.data.borrow().len())
|
|
}
|
|
}
|
|
impl RegularImpl for F {
|
|
fn open(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_opts: OpenOptions,
|
|
) -> Result<(u64, Option<Box<dyn Any>>), Error> {
|
|
Ok((0, None))
|
|
}
|
|
|
|
fn read(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_instance: Option<&Box<dyn Any>>,
|
|
pos: u64,
|
|
buf: &mut [u8],
|
|
) -> Result<usize, Error> {
|
|
let pos = pos as usize;
|
|
let data = self.data.borrow();
|
|
if pos >= data.len() {
|
|
return Ok(0);
|
|
}
|
|
let count = core::cmp::min(data.len() - pos, buf.len());
|
|
buf[..count].copy_from_slice(&data[pos..pos + count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn write(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_instance: Option<&Box<dyn Any>>,
|
|
pos: u64,
|
|
buf: &[u8],
|
|
) -> Result<usize, Error> {
|
|
let pos = pos as usize;
|
|
let mut data = self.data.borrow_mut();
|
|
data.resize(pos + buf.len(), 0);
|
|
data[pos..pos + buf.len()].copy_from_slice(buf);
|
|
Ok(buf.len())
|
|
}
|
|
}
|
|
|
|
let data = Arc::new(RefCell::new(vec![]));
|
|
let node = Node::regular(F { data: data.clone() });
|
|
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
|
let mut buf = [0; 512];
|
|
|
|
assert_eq!(&*data.borrow(), &[]);
|
|
assert_eq!(file.tell().unwrap(), 0);
|
|
|
|
assert_eq!(file.write(b"Hello").unwrap(), 5);
|
|
assert_eq!(file.tell().unwrap(), 5);
|
|
assert_eq!(&*data.borrow(), b"Hello");
|
|
|
|
assert_eq!(file.seek(SeekFrom::End(-2)).unwrap(), 3);
|
|
assert_eq!(file.tell().unwrap(), 3);
|
|
|
|
assert_eq!(file.write(b"123456").unwrap(), 6);
|
|
assert_eq!(file.tell().unwrap(), 9);
|
|
assert_eq!(&*data.borrow(), b"Hel123456");
|
|
|
|
assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
|
|
assert_eq!(file.read(&mut buf).unwrap(), 7);
|
|
assert_eq!(file.tell().unwrap(), 9);
|
|
assert_eq!(&buf[..7], b"l123456");
|
|
}
|
|
|
|
#[test]
|
|
fn block_device() {
|
|
struct B {
|
|
data: Arc<IrqSafeSpinlock<Vec<u8>>>,
|
|
}
|
|
|
|
impl BlockDevice for B {
|
|
fn read(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
|
|
let data = self.data.lock();
|
|
let pos = pos as usize;
|
|
if pos >= data.len() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let count = core::cmp::min(data.len() - pos, buf.len());
|
|
buf[..count].copy_from_slice(&data[pos..pos + count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn write(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
|
|
let mut data = self.data.lock();
|
|
let pos = pos as usize;
|
|
if pos >= data.len() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let count = core::cmp::min(data.len() - pos, buf.len());
|
|
data[pos..pos + count].copy_from_slice(&buf[..count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn size(&self) -> Result<usize, Error> {
|
|
Ok(self.data.lock().len())
|
|
}
|
|
}
|
|
|
|
let vec = vec![0; 1024];
|
|
let state = Arc::new(IrqSafeSpinlock::new(vec));
|
|
let data = state.clone();
|
|
let dev = Box::leak(Box::new(B { data }));
|
|
let mut buf = [0; 512];
|
|
|
|
let node = Node::block(dev);
|
|
|
|
let file = node.open(OpenOptions::READ | OpenOptions::WRITE).unwrap();
|
|
|
|
assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 1024);
|
|
assert_eq!(file.write(b"12345").unwrap(), 0);
|
|
assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
|
|
assert_eq!(file.write(b"12345").unwrap(), 5);
|
|
assert_eq!(&state.lock()[..6], b"12345\0");
|
|
|
|
assert_eq!(file.read(&mut buf).unwrap(), 512);
|
|
assert_eq!(buf, [0; 512]);
|
|
assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
|
|
assert_eq!(file.read(&mut buf[..8]).unwrap(), 8);
|
|
assert_eq!(&buf[..8], b"345\0\0\0\0\0");
|
|
}
|
|
|
|
#[test]
|
|
fn char_device() {
|
|
struct C;
|
|
|
|
impl CharDevice for C {
|
|
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
|
buf.fill(b'@');
|
|
Ok(buf.len())
|
|
}
|
|
|
|
fn is_writable(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
static DEV: C = C;
|
|
|
|
let node = Node::char(&DEV);
|
|
let mut buf = [0; 512];
|
|
|
|
let err = node.open(OpenOptions::WRITE).unwrap_err();
|
|
assert_eq!(err, Error::ReadOnly);
|
|
|
|
let file = node.open(OpenOptions::READ).unwrap();
|
|
assert_eq!(file.tell().unwrap_err(), Error::InvalidOperation);
|
|
assert_eq!(
|
|
file.seek(SeekFrom::Start(10)).unwrap_err(),
|
|
Error::InvalidOperation
|
|
);
|
|
assert_eq!(file.read(&mut buf).unwrap(), 512);
|
|
assert_eq!(buf, [b'@'; 512]);
|
|
|
|
assert_eq!(file.write(b"1234").unwrap_err(), Error::ReadOnly);
|
|
}
|
|
}
|