use core::mem::MaybeUninit; use yggdrasil_abi::{ error::Error, io::{DeviceRequest, DirectoryEntry, FileMode, GroupId, OpenOptions, UserId}, }; use crate::vfs::file::{File, FileRef}; use super::{AccessToken, CreateInfo, Metadata, Node, NodeFlags, NodeImpl, NodeRef}; impl Node { // 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.device_request(req), NodeImpl::Char(dev) => dev.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 { match &self.data { NodeImpl::Regular(imp) => { let (pos, instance) = imp.open(self, opts)?; if opts.contains(OpenOptions::TRUNCATE) { imp.truncate(self, 0)?; } Ok(File::regular(self.clone(), pos, instance, opts)) } NodeImpl::Block(dev) => File::block(dev.clone(), self.clone(), opts), NodeImpl::Char(dev) => File::char(dev.clone(), self.clone(), opts), NodeImpl::Hardlink(_) => unreachable!("BUG: Hard links cannot be referenced directly"), // TODO: maybe merge open_directory and open? NodeImpl::Directory(_) => Err(Error::IsADirectory), NodeImpl::Symlink(_) => todo!(), NodeImpl::PseudoTerminalSlave(pty) => { let pty = pty.upgrade().ok_or(Error::DoesNotExist)?; Ok(File::open_pty_slave(pty, self.clone())) } NodeImpl::PseudoTerminalMaster(pty) => { let pty = pty.upgrade().ok_or(Error::DoesNotExist)?; Ok(File::open_pty_master(pty, self.clone())) } } } // Directory /// Opens the node as a directory for reading its entries pub fn open_directory(self: &NodeRef, _check: AccessToken) -> Result { 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, entries: &mut [MaybeUninit], ) -> Result<(usize, u64), Error> { self.as_directory()?.imp.read_entries(self, pos, entries) } /// 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 { let dir = self.as_directory()?; { let children = dir.children.lock(); if let Some((_, node)) = children.iter().find(|(name_, _)| name_ == name) { return Ok(node.clone()); } } let node = match dir.imp.lookup(self, name) { Err(Error::NotImplemented) => Err(Error::DoesNotExist), res => res, }?; self.add_child(name, node.clone())?; Ok(node) } /// Creates an entry within a directory with given [CreateInfo]. pub fn create(self: &NodeRef, info: &CreateInfo, check: AccessToken) -> Result { let directory = self.as_directory()?; 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.clone(), )?; self.create_node(node, &info.name, check) } /// Attaches a pre-created node to its parent pub fn create_node( self: &NodeRef, node: NodeRef, name: &str, _check: AccessToken, ) -> Result { let directory = self.as_directory()?; match directory.imp.attach_node(self, &node, name) { Ok(_) | Err(Error::NotImplemented) => (), Err(err) => return Err(err), } // Attach the created node to the directory in memory cache self.add_child(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, gid: Option, mode: Option, _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!(); log::error!("TODO: update real node metadata"); } Ok(()) } /// Returns the "metadata" of the file: uid, gid, access mode pub fn metadata(self: &NodeRef) -> Result { if self.flags.contains(NodeFlags::IN_MEMORY_PROPS) { let props = self.props.lock(); return Ok(props.metadata); } self.data_as_common().metadata(self) } // TODO clarify directory size /// Returns the size in bytes of the node pub fn size(self: &NodeRef) -> Result { // Try to fetch the size from the cache let mut props = self.props.lock(); if let Some(size) = props.size { return Ok(size); } if self.flags.contains(NodeFlags::IN_MEMORY_SIZE) { if let Ok(dir) = self.as_directory() { return Ok(dir.children.lock().len() as _); } log::warn!( "Possible bug: node has IN_MEMORY_SIZE, but is not a directory: {:?}", self.ty() ); Ok(0) // Err(Error::NotImplemented) } else { // Fetch the size from the node let size = self.data_as_common().size(self)?; props.size = Some(size); Ok(size) } } pub fn read_link(self: &NodeRef, buffer: &mut [u8]) -> Result { let symlink = self.as_symlink()?; symlink.imp.read_link(buffer) } }