use core::{ any::Any, fmt, mem::MaybeUninit, task::{Context, Poll}, }; use alloc::{ boxed::Box, collections::{btree_map::Entry, BTreeMap}, sync::Arc, }; use async_trait::async_trait; use device::{BlockFile, CharFile}; use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider}; use libk_util::{ io::{ReadAt, WriteAt}, sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock}, }; use yggdrasil_abi::{ error::Error, io::{ options::{self, FileOptionVariant}, DirectoryEntry, FileMode, OpenOptions, RawFd, SeekFrom, TerminalOptions, TerminalSize, TimerOptions, }, net::{MessageHeader, MessageHeaderMut}, option::{OptionValue, RequestValue}, process::{ProcessGroupId, ProcessWait, WaitFlags}, }; use crate::{ device::{block::BlockDeviceFile, char::CharDeviceFile}, task::process::Process, vfs::{ node::NodeRef, traits::{Read, Seek, Write}, FdPoll, FileReadiness, Node, SharedMemory, TimerFile, }, }; use self::{directory::DirectoryFile, pipe::PipeEnd, regular::RegularFile}; use super::{ pid::PidFile, pty::{self, PseudoTerminalMaster, PseudoTerminalSlave}, socket::SocketWrapper, Metadata, }; mod device; mod directory; mod pipe; mod regular; /// Per-file optional instance data created when a regular file is opened pub type InstanceData = Arc; /// 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; #[derive(Clone)] struct FileOptions { non_blocking: bool, } pub struct File { options: IrqSafeRwLock, inner: FileInner, } enum FileInner { Directory(DirectoryFile), Regular(RegularFile), Block(BlockFile), Char(CharFile), Socket(SocketWrapper), AnonymousPipe(PipeEnd), Poll(FdPoll), Timer(TimerFile), SharedMemory(Arc), PtySlave(TerminalHalfWrapper), PtyMaster(TerminalHalfWrapper), Pid(PidFile), } #[async_trait] pub trait TerminalHalf { async fn read(&self, buf: &mut [u8]) -> Result; fn read_nonblocking(&self, buf: &mut [u8]) -> Result; fn write(&self, buf: &[u8]) -> Result; fn poll_read(&self, cx: &mut Context<'_>) -> Poll>; fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result; } #[derive(Clone)] pub struct TerminalHalfWrapper { half: Arc, node: NodeRef, } /// Contains a per-process fd -> FileRef map pub struct FileSet { map: BTreeMap, } impl File { fn from_inner(inner: FileInner) -> Arc { Arc::new(Self { options: IrqSafeRwLock::new(FileOptions { non_blocking: false, }), inner, }) } /// Constructs a pipe pair, returning its `(read, write)` ends pub fn new_pipe_pair(capacity: usize) -> (Arc, Arc) { let (read, write) = PipeEnd::new_pair(capacity); let read = FileInner::AnonymousPipe(read); let write = FileInner::AnonymousPipe(write); (Self::from_inner(read), Self::from_inner(write)) } /// Constructs a new poll channel file pub fn new_poll_channel() -> Arc { Self::from_inner(FileInner::Poll(FdPoll::new())) } /// Creates a buffer of shared memory and associates a [File] with it pub fn new_shared_memory(size: usize) -> Result, Error> { let shm = SharedMemory::new(size)?; Ok(Self::from_inner(FileInner::SharedMemory(Arc::new(shm)))) } /// Creates a pair of PTY master/slave pub fn new_pseudo_terminal( config: TerminalOptions, size: TerminalSize, ) -> Result<(Arc, Arc), Error> { let (master, slave) = pty::create(config, size)?; let master = Arc::new(master); let slave = Arc::new(slave); let (master_node, slave_node) = Node::pseudo_terminal_nodes(&master, &slave, Metadata::now_root(FileMode::new(0o644))); let master = Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper { half: master, node: master_node, })); let slave = Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper { half: slave, node: slave_node, })); Ok((master, slave)) } pub fn open_pty_master(pty: Arc, node: Arc) -> Arc { Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper { half: pty, node, })) } pub fn open_pty_slave(pty: Arc, node: Arc) -> Arc { Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper { half: pty, node })) } /// Creates a new [TimerFile]-backed File pub fn new_timer(options: TimerOptions) -> FileRef { let repeat = options.contains(TimerOptions::REPEAT); let timer = TimerFile::new(repeat); Self::from_inner(FileInner::Timer(timer)) } /// Creates a new [PidFile]-backed file pub fn new_pid( parent: Arc, wait: &ProcessWait, flags: WaitFlags, ) -> Result { PidFile::new(parent, wait, flags) .map(FileInner::Pid) .map(Self::from_inner) } /// Constructs a [File] from a [PacketSocket], [ConnectionSocket] or a [ListenerSocket]. pub fn from_socket>(socket: S) -> Arc { Self::from_inner(FileInner::Socket(socket.into())) } pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc { let position = IrqSafeSpinlock::new(position.into()); Self::from_inner(FileInner::Directory(DirectoryFile { node, position })) } pub(crate) fn regular( node: NodeRef, position: u64, instance_data: Option, opts: OpenOptions, ) -> Arc { let read = opts.contains(OpenOptions::READ); let write = opts.contains(OpenOptions::WRITE); Self::from_inner(FileInner::Regular(RegularFile { node, read, write, instance_data, position: IrqSafeSpinlock::new(position), })) } pub(crate) fn block( device: BlockDeviceFile, node: NodeRef, opts: OpenOptions, ) -> Result, 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_writeable() { return Err(Error::ReadOnly); } Ok(Self::from_inner(FileInner::Block(BlockFile { device, node, position: IrqSafeSpinlock::new(0), read, write, }))) } pub(crate) fn char( device: CharDeviceFile, node: NodeRef, opts: OpenOptions, ) -> Result, 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_writeable() { return Err(Error::ReadOnly); } Ok(Self::from_inner(FileInner::Char(CharFile { device, node, read, write, }))) } pub fn is_non_blocking(&self) -> bool { self.options.read().non_blocking } /// Clones an open file for sending it to another process pub fn send(self: &Arc) -> Result, Error> { let options = self.options.read().clone(); Ok(Arc::new(Self { inner: self.inner.send()?, options: IrqSafeRwLock::new(options), })) } pub fn set_option(&self, option: u32, buffer: &[u8]) -> Result<(), Error> { let option = FileOptionVariant::try_from(option)?; let mut options = self.options.write(); match option { FileOptionVariant::NonBlocking => { options.non_blocking = options::NonBlocking::load(buffer)?; Ok(()) } } } pub fn get_option(&self, option: u32, buffer: &mut [u8]) -> Result { let option = FileOptionVariant::try_from(option)?; let options = self.options.read(); match option { FileOptionVariant::NonBlocking => { options::NonBlocking::store(&options.non_blocking, buffer) } } } /// Reads entries from the directory pub fn read_dir(&self, entries: &mut [MaybeUninit]) -> Result { match &self.inner { FileInner::Directory(dir) => dir.read_entries(entries), _ => Err(Error::NotADirectory), } } /// Returns the underlying [Node] the file contains pub fn node(&self) -> Option<&NodeRef> { match &self.inner { FileInner::Directory(file) => Some(&file.node), FileInner::Regular(file) => Some(&file.node), FileInner::Block(file) => Some(&file.node), FileInner::Char(file) => Some(&file.node), FileInner::PtyMaster(half) => Some(&half.node), FileInner::PtySlave(half) => Some(&half.node), _ => None, } } /// Polls a file for "read-readiness" pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll> { match &self.inner { FileInner::Char(f) => f.device.poll_read(cx), FileInner::Poll(ch) => ch.poll_read(cx), FileInner::PtyMaster(half) => half.half.poll_read(cx), FileInner::PtySlave(half) => half.half.poll_read(cx), FileInner::Socket(socket) => socket.poll_read(cx), FileInner::Timer(timer) => timer.poll_read(cx), FileInner::Pid(pid) => pid.poll_read(cx), FileInner::AnonymousPipe(pipe) => pipe.poll_read(cx), // Polling not implemented, return ready immediately (XXX ?) _ => Poll::Ready(Err(Error::NotImplemented)), } } /// Performs a device-specific request pub fn device_request( &self, option: u32, buffer: &mut [u8], len: usize, ) -> Result { match &self.inner { FileInner::Char(f) => f.device.device_request(option, buffer, len), FileInner::Block(f) => f.device.device_request(option, buffer, len), FileInner::PtySlave(half) => half.half.device_request(option, buffer, len), FileInner::PtyMaster(half) => half.half.device_request(option, buffer, len), _ => { log::warn!("device_request {option:#x}: not a device"); Err(Error::InvalidOperation) } } } /// Interprets the file as a poll channel pub fn as_poll_channel(&self) -> Result<&FdPoll, Error> { if let FileInner::Poll(poll) = &self.inner { Ok(poll) } else { Err(Error::InvalidOperation) } } /// Interprets the file as a socket pub fn as_socket(&self) -> Result<&SocketWrapper, Error> { match &self.inner { FileInner::Socket(socket) => Ok(socket), _ => Err(Error::InvalidOperation), } } pub fn is_terminal(&self) -> bool { match &self.inner { FileInner::Char(dev) => dev.is_terminal(), FileInner::PtySlave(_) | FileInner::PtyMaster(_) => true, _ => false, } } pub fn set_terminal_group(&self, group_id: ProcessGroupId) -> Result<(), Error> { use yggdrasil_abi::io::device as abi_device; let mut buffer = [0; 8]; let len = abi_device::SetTerminalGroup::store_request(&group_id, &mut buffer)?; self.device_request( abi_device::SetTerminalGroup::VARIANT.into(), &mut buffer, len, )?; Ok(()) } } impl PageProvider for File { fn get_page(&self, offset: u64) -> Result { match &self.inner { FileInner::Block(f) => f.device.get_page(offset), FileInner::SharedMemory(f) => f.get_page(offset), _ => Err(Error::InvalidOperation), } } fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> { match &self.inner { FileInner::Block(f) => f.device.release_page(offset, phys), FileInner::SharedMemory(f) => f.release_page(offset, phys), _ => Err(Error::InvalidOperation), } } fn clone_page( &self, _offset: u64, _src_phys: PhysicalAddress, _src_attrs: MapAttributes, ) -> Result { todo!() } } impl Read for File { fn read(&self, buf: &mut [u8]) -> Result { let non_blocking = self.options.read().non_blocking; match &self.inner { FileInner::Regular(file) => file.read(buf), FileInner::Block(file) => file.read(buf), FileInner::Char(file) => file.read(buf, non_blocking), FileInner::AnonymousPipe(pipe) => pipe.read(buf, non_blocking), FileInner::PtySlave(half) => half.read(buf, non_blocking), FileInner::PtyMaster(half) => half.read(buf, non_blocking), FileInner::Timer(timer) => timer.read(buf, non_blocking), FileInner::Pid(pid) => pid.read(buf, non_blocking), // TODO allow reads from sockets FileInner::Socket(socket) => { let mut header = MessageHeaderMut { source: None, ancillary: None, ancillary_len: 0, payload: buf, }; socket.receive_message(&mut header, non_blocking) } FileInner::Directory(_) | FileInner::Poll(_) | FileInner::SharedMemory(_) => { Err(Error::InvalidOperation) } } } } impl Write for File { fn write(&self, buf: &[u8]) -> Result { let non_blocking = self.options.read().non_blocking; match &self.inner { FileInner::Regular(file) => file.write(buf), FileInner::Block(file) => file.write(buf), FileInner::Char(file) => file.write(buf, non_blocking), FileInner::AnonymousPipe(pipe) => pipe.write(buf), FileInner::PtySlave(half) => half.write(buf), FileInner::PtyMaster(half) => half.write(buf), FileInner::Timer(timer) => timer.write(buf), FileInner::Socket(socket) => { let header = MessageHeader { destination: None, payload: buf, ancillary: &[], }; socket.send_message(&header, non_blocking) } FileInner::Pid(_) | FileInner::Poll(_) | FileInner::SharedMemory(_) | FileInner::Directory(_) => Err(Error::InvalidOperation), } } } impl ReadAt for File { fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result { // let non_blocking = self.options.read().non_blocking; match &self.inner { FileInner::Regular(file) => file.read_at(pos, buf), _ => todo!(), } } } impl WriteAt for File { fn write_at(&self, pos: u64, buf: &[u8]) -> Result { // let non_blocking = self.options.read().non_blocking; match &self.inner { FileInner::Regular(file) => file.write_at(pos, buf), _ => todo!(), } } } impl Seek for File { fn tell(&self) -> Result { match &self.inner { FileInner::Regular(file) => Ok(*file.position.lock()), FileInner::Block(file) => Ok(*file.position.lock()), FileInner::Directory(_) => Err(Error::IsADirectory), _ => Ok(0), } } fn seek(&self, from: SeekFrom) -> Result { if from == SeekFrom::Current(0) { return self.tell(); } match &self.inner { FileInner::Regular(file) => file.seek(from), FileInner::Block(file) => file.seek(from), FileInner::Directory(_) => Err(Error::IsADirectory), _ => Err(Error::InvalidOperation), } } } impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.inner { FileInner::Regular(file) => f .debug_struct("RegularFile") .field("position", &*file.position.lock()) .field("read", &file.read) .field("write", &file.write) .finish_non_exhaustive(), FileInner::Block(file) => f .debug_struct("BlockFile") .field("position", &*file.position.lock()) .field("read", &file.read) .field("write", &file.write) .finish_non_exhaustive(), FileInner::Char(file) => f .debug_struct("CharFile") .field("read", &file.read) .field("write", &file.write) .finish_non_exhaustive(), FileInner::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(), FileInner::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(), FileInner::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(), FileInner::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(), FileInner::PtySlave(_) => f.debug_struct("PtySlave").finish_non_exhaustive(), FileInner::PtyMaster(_) => f.debug_struct("PtyMaster").finish_non_exhaustive(), FileInner::Socket(socket) => fmt::Debug::fmt(socket, f), FileInner::Pid(pid) => fmt::Debug::fmt(pid, f), FileInner::Timer(_) => f.debug_struct("Timer").finish_non_exhaustive(), } } } impl FileInner { pub fn send(&self) -> Result { match self { Self::Char(char) => Ok(Self::Char(char.clone())), Self::Regular(file) => Ok(Self::Regular(file.clone())), Self::SharedMemory(shm) => Ok(Self::SharedMemory(shm.clone())), Self::Pid(pid) => Ok(Self::Pid(pid.clone())), Self::PtySlave(half) => Ok(Self::PtySlave(half.clone())), Self::PtyMaster(half) => Ok(Self::PtyMaster(half.clone())), _ => todo!(), } } } impl TerminalHalfWrapper { pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result { if non_blocking { self.half.read_nonblocking(buf) } else { block!(self.half.read(buf).await)? } } pub fn write(&self, buf: &[u8]) -> Result { self.half.write(buf) } } impl FileSet { /// Creates an empty [FileSet] pub fn new() -> Self { Self { map: BTreeMap::new(), } } /// Returns the [FileRef] associated with given `fd` or an error if it does not exist pub fn file(&self, fd: RawFd) -> Result<&FileRef, Error> { self.map.get(&fd).ok_or(Error::InvalidFile) } /// Associates a `file` with `fd`, returning an error if requested `fd` is already taken pub fn set_file(&mut self, fd: RawFd, file: FileRef) -> Result<(), Error> { if self.map.contains_key(&fd) { return Err(Error::AlreadyExists); } self.map.insert(fd, file); Ok(()) } /// Associates a `file` with any available [RawFd] and returns it pub fn place_file(&mut self, file: FileRef, skip_stdio: bool) -> Result { let start = if skip_stdio { 3 } else { 0 }; for idx in start..64 { let fd = RawFd::from(idx); if let Entry::Vacant(e) = self.map.entry(fd) { e.insert(file); return Ok(fd); } } // TODO OutOfFiles Err(Error::OutOfMemory) } /// Removes and closes a [FileRef] from the struct pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> { // Do nothing, file will be dropped and closed // TODO call File's close() and return its status let _ = self.take_file(fd)?; Ok(()) } pub fn take_file(&mut self, fd: RawFd) -> Result { self.map.remove(&fd).ok_or(Error::InvalidFile) } /// Removes all [FileRef]s from the struct which do not pass the `predicate` check pub fn retain bool>(&mut self, predicate: F) { self.map.retain(predicate); } /// Returns an iterator over the file set pub fn iter(&self) -> impl Iterator { self.map.iter() } /// Closes all of the files pub fn close_all(&mut self) { self.map.clear(); } } #[cfg(test)] mod tests { use core::{ mem::MaybeUninit, str::FromStr, task::{Context, Poll}, }; use std::sync::{Arc, Mutex}; use async_trait::async_trait; use yggdrasil_abi::{ error::Error, io::{DirectoryEntry, FileType, OpenOptions, SeekFrom}, util::FixedString, }; use crate::vfs::{ file::DirectoryOpenPosition, impls::const_value_node, node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl}, traits::{Read, Seek, Write}, FileReadiness, Filename, InstanceData, }; #[test] fn file_send_sync() { fn file_send(_f: &T) {} fn file_sync(_f: &T) {} let node = const_value_node("1234"); let file = node .open(OpenOptions::READ, AccessToken::test_authorized()) .unwrap(); file_send(&file); file_sync(&file); } #[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 { Ok(DirectoryOpenPosition::FromPhysical(0)) } fn read_entries( &self, _node: &NodeRef, pos: u64, entries: &mut [MaybeUninit], ) -> 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: Some(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, NodeFlags::empty(), None, None), ), ( "f2".to_owned(), Node::regular(F, NodeFlags::empty(), None, None), ), ( "f3".to_owned(), Node::regular(F, NodeFlags::empty(), None, None), ), ]), }, NodeFlags::empty(), None, None, ); let f = d.open_directory(AccessToken::test_authorized()).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: Some(FileType::File), }, DirectoryEntry { name: FixedString::from_str("f2").unwrap(), ty: Some(FileType::File), }, DirectoryEntry { name: FixedString::from_str("f3").unwrap(), ty: Some(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 { Ok(DirectoryOpenPosition::FromCache) } } let d = Node::directory(D, NodeFlags::empty(), None, None); let child = Node::directory(D, NodeFlags::empty(), None, None); d.add_child(Filename::new("child1").unwrap(), child) .unwrap(); let f = d.open_directory(AccessToken::test_authorized()).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: Some(FileType::Directory) }, DirectoryEntry { name: FixedString::from_str("..").unwrap(), ty: Some(FileType::Directory) }, DirectoryEntry { name: FixedString::from_str("child1").unwrap(), ty: Some(FileType::Directory) } ] ); } let count = f.read_dir(&mut entries).unwrap(); assert_eq!(count, 0); } #[test] fn file_read_write() { struct F { data: Arc>>, } impl CommonImpl for F { fn size(&self, _node: &NodeRef) -> Result { Ok(self.data.lock().unwrap().len() as _) } } impl RegularImpl for F { fn open( &self, _node: &NodeRef, _opts: OpenOptions, ) -> Result<(u64, Option), Error> { Ok((0, None)) } fn read( &self, _node: &NodeRef, _instance: Option<&InstanceData>, pos: u64, buf: &mut [u8], ) -> Result { let pos = pos as usize; let data = self.data.lock().unwrap(); 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<&InstanceData>, pos: u64, buf: &[u8], ) -> Result { let pos = pos as usize; let mut data = self.data.lock().unwrap(); data.resize(pos + buf.len(), 0); data[pos..pos + buf.len()].copy_from_slice(buf); Ok(buf.len()) } } let data = Arc::new(Mutex::new(vec![])); let node = Node::regular(F { data: data.clone() }, NodeFlags::empty(), None, None); let file = node .open( OpenOptions::READ | OpenOptions::WRITE, AccessToken::test_authorized(), ) .unwrap(); let mut buf = [0; 512]; assert_eq!(&*data.lock().unwrap(), b""); assert_eq!(file.tell().unwrap(), 0); assert_eq!(file.write(b"Hello").unwrap(), 5); assert_eq!(file.tell().unwrap(), 5); assert_eq!(&*data.lock().unwrap(), 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.lock().unwrap(), 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"); } }