| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use fuchsia_zircon as zx; |
| use once_cell::sync::OnceCell; |
| use std::sync::{Arc, Weak}; |
| |
| use crate::device::DeviceMode; |
| use crate::fs::pipe::Pipe; |
| use crate::fs::socket::*; |
| use crate::fs::*; |
| use crate::lock::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; |
| use crate::task::*; |
| use crate::types::as_any::AsAny; |
| use crate::types::*; |
| |
| pub struct FsNode { |
| /// The FsNodeOps for this FsNode. |
| /// |
| /// The FsNodeOps are implemented by the individual file systems to provide |
| /// specific behaviors for this FsNode. |
| ops: Box<dyn FsNodeOps>, |
| |
| /// The FileSystem that owns this FsNode's tree. |
| fs: Weak<FileSystem>, |
| |
| /// The inode number for this FsNode. |
| pub inode_num: ino_t, |
| |
| /// The pipe located at this node, if any. |
| /// |
| /// Used if, and only if, the node has a mode of FileMode::IFIFO. |
| fifo: Option<Arc<Mutex<Pipe>>>, |
| |
| /// The socket located at this node, if any. |
| /// |
| /// Used if, and only if, the node has a mode of FileMode::IFSOCK. |
| /// |
| /// The `OnceCell` is initialized when a new socket node is created: |
| /// - in `Socket::new` (e.g., from `sys_socket`) |
| /// - in `sys_bind`, before the node is given a name (i.e., before it could be accessed by |
| /// others) |
| socket: OnceCell<SocketHandle>, |
| |
| /// Mutable informationa about this node. |
| /// |
| /// This data is used to populate the stat_t structure. |
| info: RwLock<FsNodeInfo>, |
| |
| /// A RwLock to synchronize append operations for this node. |
| /// |
| /// FileObjects writing with O_APPEND should grab a write() lock on this |
| /// field to ensure they operate sequentially. FileObjects writing without |
| /// O_APPEND should grab read() lock so that they can operate in parallel. |
| pub append_lock: RwLock<()>, |
| } |
| |
| pub type FsNodeHandle = Arc<FsNode>; |
| |
| pub struct FsNodeInfo { |
| pub mode: FileMode, |
| pub size: usize, |
| pub storage_size: usize, |
| pub blksize: i64, |
| pub uid: uid_t, |
| pub gid: gid_t, |
| pub link_count: u64, |
| pub time_create: zx::Time, |
| pub time_access: zx::Time, |
| pub time_modify: zx::Time, |
| pub dev: DeviceType, |
| pub rdev: DeviceType, |
| } |
| |
| /// st_blksize is measured in units of 512 bytes. |
| const DEFAULT_BYTES_PER_BLOCK: i64 = 512; |
| |
| impl Default for FsNodeInfo { |
| fn default() -> Self { |
| FsNodeInfo { |
| mode: Default::default(), |
| size: Default::default(), |
| storage_size: Default::default(), |
| blksize: DEFAULT_BYTES_PER_BLOCK, |
| uid: Default::default(), |
| gid: Default::default(), |
| link_count: Default::default(), |
| time_create: Default::default(), |
| time_access: Default::default(), |
| time_modify: Default::default(), |
| dev: Default::default(), |
| rdev: Default::default(), |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub enum UnlinkKind { |
| /// Unlink a directory. |
| Directory, |
| |
| /// Unlink a non-directory. |
| NonDirectory, |
| } |
| |
| pub enum SymlinkTarget { |
| Path(FsString), |
| Node(NamespaceNode), |
| } |
| |
| #[derive(PartialEq, Eq)] |
| pub enum XattrOp { |
| /// Set the value of the extended attribute regardless of whether it exists. |
| Set, |
| /// Create a new extended attribute. Fail if it already exists. |
| Create, |
| /// Replace the value of the extended attribute. Fail if it doesn't exist. |
| Replace, |
| } |
| |
| pub trait FsNodeOps: Send + Sync + AsAny { |
| /// Open a FileObject for this node. |
| /// |
| /// The returned FileOps will be used to create a FileObject, which might |
| /// be assigned an FdNumber. |
| fn open(&self, node: &FsNode, flags: OpenFlags) -> Result<Box<dyn FileOps>, Errno>; |
| |
| /// Find an existing child node and populate the child parameter. Return the node. |
| /// |
| /// The child parameter is an empty node. Operations other than initialize may panic before |
| /// initialize is called. |
| fn lookup(&self, _node: &FsNode, _name: &FsStr) -> Result<FsNodeHandle, Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Create and return the given child node. |
| /// |
| /// The mode field of the FsNodeInfo indicates what kind of child to |
| /// create. |
| /// |
| /// This function is never called with FileMode::IFDIR. The mkdir function |
| /// is used to create directories instead. |
| fn mknod(&self, _node: &FsNode, _name: &FsStr, _mode: FileMode) -> Result<FsNodeHandle, Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Create and return the given child node as a subdirectory. |
| fn mkdir(&self, _node: &FsNode, _name: &FsStr) -> Result<FsNodeHandle, Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Creates a symlink with the given `target` path. |
| fn create_symlink( |
| &self, |
| _node: &FsNode, |
| _name: &FsStr, |
| _target: &FsStr, |
| ) -> Result<FsNodeHandle, Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Reads the symlink from this node. |
| fn readlink( |
| &self, |
| _node: &FsNode, |
| _current_task: &CurrentTask, |
| ) -> Result<SymlinkTarget, Errno> { |
| error!(EINVAL) |
| } |
| |
| /// Create a hard link with the given name to the given child. |
| fn link(&self, _node: &FsNode, _name: &FsStr, _child: &FsNodeHandle) -> Result<(), Errno> { |
| error!(EPERM) |
| } |
| |
| /// Remove the child with the given name, if the child exists. |
| /// |
| /// The UnlinkKind parameter indicates whether the caller intends to unlink |
| /// a directory or a non-directory child. |
| fn unlink(&self, _node: &FsNode, _name: &FsStr, _child: &FsNodeHandle) -> Result<(), Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Change the length of the file. |
| fn truncate(&self, _node: &FsNode, _length: u64) -> Result<(), Errno> { |
| error!(EINVAL) |
| } |
| |
| /// Update node.info as needed. |
| /// |
| /// FsNode calls this method before converting the FsNodeInfo struct into |
| /// the stat_t struct to give the file system a chance to update this data |
| /// before it is used by clients. |
| /// |
| /// File systems that keep the FsNodeInfo up-to-date do not need to |
| /// override this function. |
| /// |
| /// Return a reader lock on the updated information. |
| fn update_info<'a>(&self, node: &'a FsNode) -> Result<RwLockReadGuard<'a, FsNodeInfo>, Errno> { |
| Ok(node.info()) |
| } |
| |
| /// Get an extended attribute on the node. |
| fn get_xattr(&self, _name: &FsStr) -> Result<FsString, Errno> { |
| error!(ENOTSUP) |
| } |
| |
| /// Set an extended attribute on the node. |
| fn set_xattr(&self, _name: &FsStr, _value: &FsStr, _op: XattrOp) -> Result<(), Errno> { |
| error!(ENOTSUP) |
| } |
| |
| fn remove_xattr(&self, _name: &FsStr) -> Result<(), Errno> { |
| error!(ENOTSUP) |
| } |
| |
| fn list_xattrs(&self) -> Result<Vec<FsString>, Errno> { |
| error!(ENOTSUP) |
| } |
| } |
| |
| /// Implements [`FsNodeOps`] methods in a way that makes sense for symlinks. |
| /// You must implement [`FsNodeOps::readlink`]. |
| macro_rules! fs_node_impl_symlink { |
| () => { |
| fn open( |
| &self, |
| _node: &crate::fs::FsNode, |
| _flags: crate::types::OpenFlags, |
| ) -> Result<Box<dyn crate::fs::FileOps>, crate::types::Errno> { |
| unreachable!("Symlink nodes cannot be opened."); |
| } |
| }; |
| } |
| |
| #[allow(unknown_lints)] // TODO(fxbug.dev/99424): remove this after toolchain roll when lint is known |
| #[allow(unused_macro_rules)] // TODO(fxbug.dev/100318): remove unused macro rules and re-enable |
| /// Implements [`FsNodeOps::set_xattr`] by delegating to another [`FsNodeOps`] |
| /// object. |
| macro_rules! fs_node_impl_xattr_delegate { |
| ($self:ident, $delegate:expr) => { |
| fn get_xattr( |
| &$self, |
| name: &crate::fs::FsStr, |
| ) -> Result<FsString, crate::types::Errno> { |
| $delegate.get_xattr(name) |
| } |
| |
| fn set_xattr( |
| &$self, |
| name: &crate::fs::FsStr, |
| value: &crate::fs::FsStr, |
| op: crate::fs::XattrOp, |
| ) -> Result<(), crate::types::Errno> { |
| $delegate.set_xattr(name, value, op) |
| } |
| |
| fn remove_xattr( |
| &$self, |
| name: &crate::fs::FsStr, |
| ) -> Result<(), crate::types::Errno> { |
| $delegate.remove_xattr(name) |
| } |
| |
| fn list_xattrs( |
| &$self, |
| ) -> Result<Vec<crate::fs::FsString>, crate::types::Errno> { |
| $delegate.list_xattrs() |
| } |
| }; |
| ($delegate:expr) => { fs_node_impl_xattr_delegate(self, $delegate) }; |
| } |
| |
| // Public re-export of macros allows them to be used like regular rust items. |
| pub(crate) use fs_node_impl_symlink; |
| pub(crate) use fs_node_impl_xattr_delegate; |
| |
| pub struct SpecialNode; |
| |
| impl FsNodeOps for SpecialNode { |
| fn open(&self, _node: &FsNode, _flags: OpenFlags) -> Result<Box<dyn FileOps>, Errno> { |
| unreachable!("Special nodes cannot be opened."); |
| } |
| } |
| |
| impl FsNode { |
| pub fn new_root(ops: impl FsNodeOps + 'static) -> FsNode { |
| Self::new_internal(Box::new(ops), Weak::new(), 1, FileMode::IFDIR | FileMode::ALLOW_ALL) |
| } |
| |
| pub fn new( |
| ops: Box<dyn FsNodeOps>, |
| fs: &FileSystemHandle, |
| inode_num: ino_t, |
| mode: FileMode, |
| ) -> FsNodeHandle { |
| Arc::new(Self::new_internal(ops, Arc::downgrade(fs), inode_num, mode)) |
| } |
| |
| fn new_internal( |
| ops: Box<dyn FsNodeOps>, |
| fs: Weak<FileSystem>, |
| inode_num: ino_t, |
| mode: FileMode, |
| ) -> FsNode { |
| let now = fuchsia_runtime::utc_time(); |
| let info = FsNodeInfo { |
| mode, |
| link_count: if mode.is_dir() { 2 } else { 1 }, |
| time_create: now, |
| time_access: now, |
| time_modify: now, |
| ..Default::default() |
| }; |
| Self { |
| ops, |
| fs, |
| inode_num, |
| fifo: if mode.is_fifo() { Some(Pipe::new()) } else { None }, |
| socket: OnceCell::new(), |
| info: RwLock::new(info), |
| append_lock: RwLock::new(()), |
| } |
| } |
| |
| pub fn fs(&self) -> FileSystemHandle { |
| self.fs.upgrade().expect("FileSystem did not live long enough") |
| } |
| |
| pub fn set_fs(&mut self, fs: &FileSystemHandle) { |
| self.fs = Arc::downgrade(fs); |
| } |
| |
| fn ops(&self) -> &dyn FsNodeOps { |
| &*self.ops.as_ref() |
| } |
| |
| /// Returns the `FsNode`'s `FsNodeOps` as a `&T`, or `None` if the downcast fails. |
| pub fn downcast_ops<T>(&self) -> Option<&T> |
| where |
| T: 'static, |
| { |
| self.ops().as_any().downcast_ref::<T>() |
| } |
| |
| pub fn open( |
| &self, |
| current_task: &CurrentTask, |
| flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| // If O_PATH is set, there is no need to create a real FileOps because |
| // most file operations are disabled. |
| if flags.contains(OpenFlags::PATH) { |
| return Ok(Box::new(OPathOps::new())); |
| } |
| |
| let (mode, rdev) = { |
| // Don't hold the info lock while calling into open_device or self.ops(). |
| // TODO: The mode and rdev are immutable and shouldn't require a lock to read. |
| let info = self.info(); |
| (info.mode, info.rdev) |
| }; |
| |
| match mode & FileMode::IFMT { |
| FileMode::IFCHR => { |
| current_task.kernel().open_device(current_task, self, flags, rdev, DeviceMode::Char) |
| } |
| FileMode::IFBLK => current_task.kernel().open_device( |
| current_task, |
| self, |
| flags, |
| rdev, |
| DeviceMode::Block, |
| ), |
| FileMode::IFIFO => Ok(Pipe::open(self.fifo.as_ref().unwrap(), flags)), |
| // UNIX domain sockets can't be opened. |
| FileMode::IFSOCK => error!(ENXIO), |
| _ => self.ops().open(self, flags), |
| } |
| } |
| |
| pub fn lookup(&self, name: &FsStr) -> Result<FsNodeHandle, Errno> { |
| self.ops().lookup(self, name) |
| } |
| |
| pub fn mknod(&self, name: &FsStr, mode: FileMode) -> Result<FsNodeHandle, Errno> { |
| self.ops().mknod(self, name, mode) |
| } |
| |
| pub fn mkdir(&self, name: &FsStr) -> Result<FsNodeHandle, Errno> { |
| self.ops().mkdir(self, name) |
| } |
| |
| pub fn create_symlink(&self, name: &FsStr, target: &FsStr) -> Result<FsNodeHandle, Errno> { |
| self.ops().create_symlink(self, name, target) |
| } |
| |
| pub fn readlink(&self, current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> { |
| let now = fuchsia_runtime::utc_time(); |
| self.info_write().time_access = now; |
| self.ops().readlink(self, current_task) |
| } |
| |
| pub fn link(&self, name: &FsStr, child: &FsNodeHandle) -> Result<(), Errno> { |
| self.ops().link(self, name, child) |
| } |
| |
| pub fn unlink(&self, name: &FsStr, child: &FsNodeHandle) -> Result<(), Errno> { |
| self.ops().unlink(self, name, child) |
| } |
| |
| pub fn truncate(&self, length: u64) -> Result<(), Errno> { |
| self.ops().truncate(self, length) |
| } |
| |
| /// Associates the provided socket with this file node. |
| /// |
| /// `set_socket` must be called before it is possible to look up `self`, since user space should |
| /// not be able to look up this node and find the socket missing. |
| /// |
| /// Note that it is a fatal error to call this method if a socket has already been bound for |
| /// this node. |
| /// |
| /// # Parameters |
| /// - `socket`: The socket to store in this file node. |
| pub fn set_socket(&self, socket: SocketHandle) { |
| assert!(self.socket.set(socket).is_ok()); |
| } |
| |
| /// Returns the socket associated with this node, if such a socket exists. |
| pub fn socket(&self) -> Option<&SocketHandle> { |
| self.socket.get() |
| } |
| |
| /// Set the permissions on this FsNode to the given values. |
| /// |
| /// Does not change the IFMT of the node. |
| pub fn chmod(&self, mode: FileMode) { |
| let mut info = self.info_write(); |
| info.mode = (info.mode & !FileMode::PERMISSIONS) | (mode & FileMode::PERMISSIONS); |
| } |
| |
| /// Sets the owner and/or group on this FsNode. |
| pub fn chown(&self, owner: Option<uid_t>, group: Option<gid_t>) { |
| let mut info = self.info_write(); |
| if let Some(owner) = owner { |
| info.uid = owner; |
| } |
| if let Some(group) = group { |
| info.gid = group; |
| } |
| } |
| |
| /// Whether this node is a directory. |
| pub fn is_dir(&self) -> bool { |
| self.info().mode.is_dir() |
| } |
| |
| /// Whether this node is a socket. |
| pub fn is_sock(&self) -> bool { |
| self.info().mode.is_sock() |
| } |
| |
| /// Whether this node is a symbolic link. |
| pub fn is_lnk(&self) -> bool { |
| self.info().mode.is_lnk() |
| } |
| |
| /// Update the access and modify time for this node to now. |
| pub fn touch(&self) { |
| let now = fuchsia_runtime::utc_time(); |
| let mut info = self.info_write(); |
| info.time_access = now; |
| info.time_modify = now; |
| } |
| |
| pub fn stat(&self) -> Result<stat_t, Errno> { |
| let info = self.ops().update_info(self)?; |
| Ok(stat_t { |
| st_ino: self.inode_num, |
| st_mode: info.mode.bits(), |
| st_size: info.size as off_t, |
| st_blocks: info.storage_size as i64 / info.blksize, |
| st_nlink: info.link_count, |
| st_uid: info.uid, |
| st_gid: info.gid, |
| st_ctim: timespec_from_time(info.time_create), |
| st_mtim: timespec_from_time(info.time_modify), |
| st_atim: timespec_from_time(info.time_access), |
| st_dev: info.dev.bits(), |
| st_rdev: info.rdev.bits(), |
| st_blksize: info.blksize, |
| ..Default::default() |
| }) |
| } |
| |
| pub fn get_xattr(&self, name: &FsStr) -> Result<FsString, Errno> { |
| self.ops().get_xattr(name) |
| } |
| |
| pub fn set_xattr(&self, name: &FsStr, value: &FsStr, op: XattrOp) -> Result<(), Errno> { |
| self.ops().set_xattr(name, value, op) |
| } |
| |
| pub fn remove_xattr(&self, name: &FsStr) -> Result<(), Errno> { |
| self.ops().remove_xattr(name) |
| } |
| |
| pub fn list_xattrs(&self) -> Result<Vec<FsString>, Errno> { |
| self.ops().list_xattrs() |
| } |
| |
| pub fn info(&self) -> RwLockReadGuard<'_, FsNodeInfo> { |
| self.info.read() |
| } |
| pub fn info_write(&self) -> RwLockWriteGuard<'_, FsNodeInfo> { |
| self.info.write() |
| } |
| } |
| |
| impl Drop for FsNode { |
| fn drop(&mut self) { |
| if let Some(fs) = self.fs.upgrade() { |
| fs.remove_node(self); |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::testing::*; |
| |
| #[::fuchsia::test] |
| fn open_device_file() { |
| let (_kernel, current_task) = create_kernel_and_task(); |
| |
| // Create a device file that points to the `zero` device (which is automatically |
| // registered in the kernel). |
| current_task |
| .fs |
| .root |
| .create_node(b"zero", FileMode::IFCHR | FileMode::from_bits(0o666), DeviceType::ZERO) |
| .expect("create_node"); |
| |
| // Prepare the user buffer with some values other than the expected content (non-zero). |
| const CONTENT_LEN: usize = 10; |
| let address = map_memory(¤t_task, UserAddress::default(), CONTENT_LEN as u64); |
| current_task.mm.write_memory(address, &[0xff; CONTENT_LEN]).expect("write memory"); |
| |
| // Read from the zero device. |
| let device_file = |
| current_task.open_file(b"zero", OpenFlags::RDONLY).expect("open device file"); |
| device_file |
| .read(¤t_task, &[UserBuffer { address, length: CONTENT_LEN }]) |
| .expect("read from zero"); |
| |
| // Assert the contents. |
| let content = &mut [0xff; CONTENT_LEN]; |
| current_task.mm.read_memory(address, content).expect("read memory"); |
| assert_eq!(&[0; CONTENT_LEN], content); |
| } |
| |
| #[::fuchsia::test] |
| fn node_info_is_reflected_in_stat() { |
| let (_kernel, current_task) = create_kernel_and_task(); |
| |
| // Create a node. |
| let node = ¤t_task |
| .fs |
| .root |
| .create_node(b"zero", FileMode::IFCHR, DeviceType::ZERO) |
| .expect("create_node") |
| .entry |
| .node; |
| { |
| let mut info = node.info_write(); |
| info.mode = FileMode::IFSOCK; |
| info.size = 1; |
| info.storage_size = 8; |
| info.blksize = 4; |
| info.uid = 9; |
| info.gid = 10; |
| info.link_count = 11; |
| info.time_create = zx::Time::from_nanos(1); |
| info.time_access = zx::Time::from_nanos(2); |
| info.time_modify = zx::Time::from_nanos(3); |
| info.dev = DeviceType::new(12, 12); |
| info.rdev = DeviceType::new(13, 13); |
| } |
| let stat = node.stat().expect("stat"); |
| |
| assert_eq!(stat.st_mode, FileMode::IFSOCK.bits()); |
| assert_eq!(stat.st_size, 1); |
| assert_eq!(stat.st_blksize, 4); |
| assert_eq!(stat.st_blocks, 2); |
| assert_eq!(stat.st_uid, 9); |
| assert_eq!(stat.st_gid, 10); |
| assert_eq!(stat.st_nlink, 11); |
| assert_eq!(time_from_timespec(stat.st_ctim).expect("ctim"), zx::Time::from_nanos(1)); |
| assert_eq!(time_from_timespec(stat.st_atim).expect("atim"), zx::Time::from_nanos(2)); |
| assert_eq!(time_from_timespec(stat.st_mtim).expect("mtim"), zx::Time::from_nanos(3)); |
| assert_eq!(stat.st_dev, DeviceType::new(12, 12).bits()); |
| assert_eq!(stat.st_rdev, DeviceType::new(13, 13).bits()); |
| } |
| } |