use core::{ any::Any, fmt, mem::MaybeUninit, task::{Context, Poll}, }; use alloc::{ collections::{btree_map::Entry, BTreeMap}, sync::Arc, }; use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider}; use libk_util::sync::IrqSafeSpinlock; use yggdrasil_abi::{ error::Error, io::{ DeviceRequest, DirectoryEntry, OpenOptions, RawFd, SeekFrom, TerminalOptions, TerminalSize, }, net::SocketAddr, }; use crate::vfs::{ channel::ChannelDescriptor, device::{BlockDeviceWrapper, CharDeviceWrapper}, node::NodeRef, socket::{ConnectionSocketWrapper, ListenerSocketWrapper, PacketSocketWrapper}, traits::{Read, Seek, Write}, ConnectionSocket, FdPoll, FileReadiness, ListenerSocket, Node, PacketSocket, PseudoTerminalMaster, PseudoTerminalSlave, SharedMemory, Socket, TimerFile, }; use self::{ device::{BlockFile, CharFile}, directory::DirectoryFile, pipe::PipeEnd, regular::RegularFile, }; use super::pty; 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; // TODO some kind of a mutex instead? /// Describes an open file #[allow(missing_docs)] pub enum File { Directory(DirectoryFile), Regular(RegularFile), Block(BlockFile), Char(CharFile), PacketSocket(Arc), ListenerSocket(Arc), StreamSocket(Arc), AnonymousPipe(PipeEnd), Poll(FdPoll), Timer(TimerFile), Channel(ChannelDescriptor), SharedMemory(Arc), PtySlave(Arc, NodeRef), PtyMaster(Arc, NodeRef), } /// Contains a per-process fd -> FileRef map pub struct FileSet { map: BTreeMap, } impl File { /// 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); ( Arc::new(Self::AnonymousPipe(read)), Arc::new(Self::AnonymousPipe(write)), ) } /// Constructs a new poll channel file pub fn new_poll_channel() -> Arc { Arc::new(Self::Poll(FdPoll::new())) } /// Opens a new message channel, optionally subscribing to it as well pub fn new_message_channel(name: &str, with_sub: bool) -> Arc { let channel = ChannelDescriptor::open(name, with_sub); Arc::new(Self::Channel(channel)) } /// 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(Arc::new(Self::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.clone(), slave.clone()); Ok(( Arc::new(Self::PtyMaster(master, master_node)), Arc::new(Self::PtySlave(slave, slave_node)), )) } /// Creates a new [TimerFile]-backed File pub fn new_timer(repeat: bool) -> FileRef { Arc::new(Self::Timer(TimerFile::new(repeat))) } /// Constructs a [File] from a [PacketSocket] pub fn from_packet_socket(socket: Arc) -> Arc { Arc::new(Self::PacketSocket(Arc::new(PacketSocketWrapper(socket)))) } /// Constructs a [File] from a [ListenerSocket] pub fn from_listener_socket(socket: Arc) -> Arc { Arc::new(Self::ListenerSocket(Arc::new(ListenerSocketWrapper( socket, )))) } /// Constructs a [File] from a [ConnectionSocket] pub fn from_stream_socket(socket: Arc) -> Arc { Arc::new(Self::StreamSocket(Arc::new(ConnectionSocketWrapper( socket, )))) } pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc { let position = IrqSafeSpinlock::new(position.into()); Arc::new(Self::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); Arc::new(Self::Regular(RegularFile { node, read, write, instance_data, position: IrqSafeSpinlock::new(position), })) } pub(crate) fn block( device: BlockDeviceWrapper, 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_writable() { return Err(Error::ReadOnly); } Ok(Arc::new(Self::Block(BlockFile { device, node, position: IrqSafeSpinlock::new(0), read, write, }))) } pub(crate) fn char( device: CharDeviceWrapper, 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_writable() { return Err(Error::ReadOnly); } Ok(Arc::new(Self::Char(CharFile { device, node, read, write, }))) } /// Clones an open file for sending it to another process pub fn send(self: &Arc) -> Result, Error> { match self.as_ref() { Self::Char(_) => Ok(self.clone()), Self::Block(_) => todo!(), Self::Regular(file) => Ok(Arc::new(Self::Regular(file.clone()))), Self::SharedMemory(shm) => Ok(Arc::new(Self::SharedMemory(shm.clone()))), Self::PtySlave(pt, pt_node) => { Ok(Arc::new(Self::PtySlave(pt.clone(), pt_node.clone()))) } Self::PtyMaster(pt, pt_node) => { Ok(Arc::new(Self::PtyMaster(pt.clone(), pt_node.clone()))) } _ => { log::info!("Invalid file send(): {:?}", self); Err(Error::InvalidOperation) } } } /// Reads entries from the directory pub fn read_dir(&self, entries: &mut [MaybeUninit]) -> Result { match self { Self::Directory(dir) => dir.read_entries(entries), _ => Err(Error::NotADirectory), } } /// Returns the underlying [Node] the file contains pub fn node(&self) -> Option<&NodeRef> { match self { Self::Directory(file) => Some(&file.node), Self::Regular(file) => Some(&file.node), Self::Block(file) => Some(&file.node), Self::Char(file) => Some(&file.node), Self::PtyMaster(_, node) => Some(node), Self::PtySlave(_, node) => Some(node), _ => None, } } /// Polls a file for "read-readiness" pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll> { match self { Self::Char(f) => f.device.0.poll_read(cx), Self::Channel(ch) => ch.poll_read(cx), Self::Poll(ch) => ch.poll_read(cx), Self::PtyMaster(f, _) => f.poll_read(cx), Self::PtySlave(f, _) => f.poll_read(cx), Self::PacketSocket(sock) => sock.poll_read(cx), Self::StreamSocket(sock) => sock.poll_read(cx), Self::ListenerSocket(sock) => sock.poll_read(cx), Self::Timer(timer) => timer.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, req: &mut DeviceRequest) -> Result<(), Error> { match self { Self::Char(f) => f.device.0.device_request(req), Self::Block(f) => f.device.0.device_request(req), Self::PtySlave(f, _) => f.device_request(req), Self::PtyMaster(f, _) => f.device_request(req), _ => Err(Error::InvalidOperation), } } /// Interprets the file as a poll channel pub fn as_poll_channel(&self) -> Result<&FdPoll, Error> { if let Self::Poll(poll) = self { Ok(poll) } else { Err(Error::InvalidOperation) } } /// Interprets the file as a message channel pub fn as_message_channel(&self) -> Result<&ChannelDescriptor, Error> { if let Self::Channel(ch) = self { Ok(ch) } else { Err(Error::InvalidOperation) } } /// Interprets the file as a socket pub fn as_socket(&self) -> Result<&dyn Socket, Error> { match self { Self::PacketSocket(socket) => Ok(socket.0.as_ref()), _ => Err(Error::InvalidOperation), } } /// Sends data to a socket pub fn send_to(&self, buffer: &[u8], recepient: Option) -> Result { match (self, recepient) { (Self::PacketSocket(socket), recepient) => socket.send(recepient, buffer), (Self::StreamSocket(socket), None) => socket.send(buffer), (_, _) => todo!(), } } /// Receives data from a socket pub fn receive_from( &self, buffer: &mut [u8], remote: &mut MaybeUninit, ) -> Result { match self { Self::PacketSocket(socket) => { let (addr, len) = socket.receive(buffer)?; remote.write(addr); Ok(len) } Self::StreamSocket(socket) => { // Always the same remote.write(socket.remote_address().unwrap()); socket.receive(buffer) } _ => Err(Error::InvalidOperation), } } /// Waits for incoming connection to be accepted by the listener pub fn accept(&self, remote: &mut MaybeUninit) -> Result { match self { Self::ListenerSocket(socket) => { let (address, incoming) = socket.accept()?; remote.write(address); Ok(File::from_stream_socket(incoming)) } _ => Err(Error::InvalidOperation), } } } impl PageProvider for File { fn get_page(&self, offset: u64) -> Result { match self { Self::Block(f) => f.device.0.get_page(offset), Self::SharedMemory(f) => f.get_page(offset), _ => Err(Error::InvalidOperation), } } fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> { match self { Self::Block(f) => f.device.0.release_page(offset, phys), Self::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 { match self { Self::Regular(file) => file.read(buf), Self::Block(file) => file.read(buf), Self::Char(file) => file.read(buf), Self::AnonymousPipe(pipe) => pipe.read(buf), Self::PtySlave(pt, _) => pt.read(buf), Self::PtyMaster(pt, _) => pt.read(buf), // TODO maybe allow reading trigger count? Self::Timer(_) => Err(Error::InvalidOperation), // TODO maybe allow reading FDs from poll channels as if they were regular streams? Self::Poll(_) => Err(Error::InvalidOperation), // TODO maybe allow reading messages from Channels? Self::Channel(_) => Err(Error::InvalidOperation), Self::SharedMemory(_) => Err(Error::InvalidOperation), // TODO maybe allow reading messages from Packet/Stream sockets? Self::PacketSocket(_) | Self::ListenerSocket(_) | Self::StreamSocket(_) => { Err(Error::InvalidOperation) } Self::Directory(_) => Err(Error::IsADirectory), } } } impl Write for File { fn write(&self, buf: &[u8]) -> Result { match self { Self::Regular(file) => file.write(buf), Self::Block(file) => file.write(buf), Self::Char(file) => file.write(buf), Self::AnonymousPipe(pipe) => pipe.write(buf), Self::PtySlave(pt, _) => pt.write(buf), Self::PtyMaster(pt, _) => pt.write(buf), Self::Timer(timer) => timer.write(buf), // TODO maybe allow adding FDs to poll channels this way Self::Poll(_) => Err(Error::InvalidOperation), // TODO maybe allow writing messages to Channels? Self::Channel(_) => Err(Error::InvalidOperation), Self::SharedMemory(_) => Err(Error::InvalidOperation), // TODO maybe allow writing messages to Packet/Stream sockets? Self::PacketSocket(_) | Self::ListenerSocket(_) | Self::StreamSocket(_) => { Err(Error::InvalidOperation) } Self::Directory(_) => Err(Error::IsADirectory), } } } impl Seek for File { fn tell(&self) -> Result { match self { Self::Regular(file) => Ok(*file.position.lock()), Self::Block(file) => Ok(*file.position.lock()), Self::Directory(_) => Err(Error::IsADirectory), _ => Err(Error::InvalidOperation), } } fn seek(&self, from: SeekFrom) -> Result { match self { Self::Regular(file) => file.seek(from), Self::Block(file) => file.seek(from), Self::Directory(_) => Err(Error::IsADirectory), _ => Err(Error::InvalidOperation), } } } 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.lock()) .field("read", &file.read) .field("write", &file.write) .finish_non_exhaustive(), Self::Block(file) => f .debug_struct("BlockFile") .field("position", &*file.position.lock()) .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(), Self::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(), Self::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(), Self::Channel(_) => f.debug_struct("Channel").finish_non_exhaustive(), Self::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(), Self::PtySlave(_, _) => f.debug_struct("PtySlave").finish_non_exhaustive(), Self::PtyMaster(_, _) => f.debug_struct("PtyMaster").finish_non_exhaustive(), Self::PacketSocket(sock) => f .debug_struct("PacketSocket") .field("local", &sock.local_address()) .field("remote", &sock.remote_address()) .finish_non_exhaustive(), Self::StreamSocket(sock) => f .debug_struct("StreamSocket") .field("local", &sock.local_address()) .field("remote", &sock.remote_address()) .finish_non_exhaustive(), Self::ListenerSocket(sock) => f .debug_struct("ListenerSocket") .field("local", &sock.local_address()) .finish_non_exhaustive(), Self::Timer(_) => f.debug_struct("Timer").finish_non_exhaustive(), } } } 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 if self.map.remove(&fd).is_some() { Ok(()) } else { Err(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 yggdrasil_abi::{ error::Error, io::{DirectoryEntry, FileType, OpenOptions, SeekFrom}, util::FixedString, }; use crate::vfs::{ device::CharDevice, file::DirectoryOpenPosition, impls::const_value_node, node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl}, traits::{Read, Seek, Write}, FileReadiness, 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: 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())), ("f2".to_owned(), Node::regular(F, NodeFlags::empty())), ("f3".to_owned(), Node::regular(F, NodeFlags::empty())), ]), }, NodeFlags::empty(), ); 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: 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 { Ok(DirectoryOpenPosition::FromCache) } } let d = Node::directory(D, NodeFlags::empty()); let child = Node::directory(D, NodeFlags::empty()); d.add_child("child1", 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: 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>>, } 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()); let file = node .open( OpenOptions::READ | OpenOptions::WRITE, AccessToken::test_authorized(), ) .unwrap(); let mut buf = [0; 512]; assert_eq!(&*data.lock().unwrap(), &[]); 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"); } #[test] fn char_device() { struct C; impl FileReadiness for C { fn poll_read(&self, _cx: &mut Context<'_>) -> Poll> { unreachable!() } } impl CharDevice for C { fn read(&self, buf: &mut [u8]) -> Result { buf.fill(b'@'); Ok(buf.len()) } fn is_writable(&self) -> bool { false } } static DEV: C = C; let node = Node::char(&DEV, NodeFlags::empty()); let mut buf = [0; 512]; let err = node .open(OpenOptions::WRITE, AccessToken::test_authorized()) .unwrap_err(); assert_eq!(err, Error::ReadOnly); 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(), 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); } }