| // Copyright 2023 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 crate::{ |
| auth::FsCred, |
| fs::{ |
| buffers::{InputBuffer, OutputBuffer, OutputBufferCallback}, |
| default_eof_offset, default_fcntl, default_ioctl, default_seek, fileops_impl_nonseekable, |
| fs_args, CacheConfig, CacheMode, DirEntry, DirectoryEntryType, DirentSink, FallocMode, |
| FdEvents, FdNumber, FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, |
| FileSystemOptions, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, |
| SeekTarget, SymlinkTarget, ValueOrSize, XattrOp, |
| }, |
| logging::{log_error, log_trace, log_warn, not_implemented, not_implemented_log_once}, |
| mm::{vmo::round_up_to_increment, PAGE_SIZE}, |
| syscalls::{SyscallArg, SyscallResult}, |
| task::{CurrentTask, EventHandler, ExitStatus, Kernel, WaitCanceler, WaitQueue, Waiter}, |
| types::{ |
| errno, |
| errno::{EINTR, EINVAL, ENOSYS}, |
| errno_from_code, error, off_t, |
| ownership::ReleasableByRef, |
| statfs, time_from_timespec, uapi, Access, DeviceType, Errno, FileMode, OpenFlags, |
| FUSE_SUPER_MAGIC, |
| }, |
| }; |
| use bstr::B; |
| use starnix_lock::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; |
| use std::{ |
| collections::{hash_map::Entry, HashMap, VecDeque}, |
| sync::Arc, |
| }; |
| use zerocopy::{AsBytes, FromBytes, FromZeroes}; |
| |
| const CONFIGURATION_AVAILABLE_EVENT: u64 = u64::MAX; |
| |
| #[derive(Debug, Default)] |
| pub struct DevFuse { |
| connection: Arc<FuseConnection>, |
| } |
| |
| impl FileOps for DevFuse { |
| fileops_impl_nonseekable!(); |
| |
| fn read( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| debug_assert!(offset == 0); |
| file.blocking_op(current_task, FdEvents::POLLIN, None, || self.connection.read(data)) |
| } |
| |
| fn write( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| debug_assert!(offset == 0); |
| self.connection.write(data) |
| } |
| |
| fn wait_async( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| waiter: &Waiter, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| self.connection.wait_async(waiter, events, handler) |
| } |
| |
| fn query_events( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> { |
| Ok(self.connection.query_events()) |
| } |
| } |
| |
| pub fn new_fuse_fs( |
| current_task: &CurrentTask, |
| options: FileSystemOptions, |
| ) -> Result<FileSystemHandle, Errno> { |
| let mut mount_options = fs_args::generic_parse_mount_options(&options.params); |
| let fd = |
| fs_args::parse::<FdNumber>(mount_options.remove(B("fd")).ok_or_else(|| errno!(EINVAL))?)?; |
| let default_permissions = mount_options.remove(B("default_permissions")).is_some(); |
| let connection = current_task |
| .files |
| .get(fd)? |
| .downcast_file::<DevFuse>() |
| .ok_or_else(|| errno!(EINVAL))? |
| .connection |
| .clone(); |
| |
| let fs = FileSystem::new( |
| current_task.kernel(), |
| CacheMode::Cached(CacheConfig::default()), |
| FuseFs { connection: connection.clone(), default_permissions }, |
| options, |
| ); |
| let fuse_node = Arc::new(FuseNode { |
| connection: connection.clone(), |
| nodeid: uapi::FUSE_ROOT_ID as u64, |
| state: Default::default(), |
| }); |
| fuse_node.state.lock().nlookup += 1; |
| |
| let mut root_node = FsNode::new_root(fuse_node.clone()); |
| root_node.node_id = uapi::FUSE_ROOT_ID as u64; |
| fs.set_root_node(root_node); |
| connection.execute_operation(current_task, &fuse_node, FuseOperation::Init)?; |
| Ok(fs) |
| } |
| |
| #[derive(Debug)] |
| struct FuseFs { |
| connection: Arc<FuseConnection>, |
| default_permissions: bool, |
| } |
| |
| impl FuseFs { |
| fn from_fs(fs: &FileSystem) -> Result<&FuseFs, Errno> { |
| fs.downcast_ops::<FuseFs>().ok_or_else(|| errno!(ENOENT)) |
| } |
| } |
| |
| impl FileSystemOps for FuseFs { |
| fn rename( |
| &self, |
| _fs: &FileSystem, |
| _current_task: &CurrentTask, |
| _old_parent: &FsNodeHandle, |
| _old_name: &FsStr, |
| _new_parent: &FsNodeHandle, |
| _new_name: &FsStr, |
| _renamed: &FsNodeHandle, |
| _replaced: Option<&FsNodeHandle>, |
| ) -> Result<(), Errno> { |
| error!(ENOTSUP) |
| } |
| |
| fn generate_node_ids(&self) -> bool { |
| true |
| } |
| |
| fn statfs(&self, fs: &FileSystem, current_task: &CurrentTask) -> Result<statfs, Errno> { |
| let node = if let Ok(node) = FuseNode::from_node(&fs.root().node) { |
| node |
| } else { |
| log_error!("Unexpected file type"); |
| return error!(EINVAL); |
| }; |
| let response = |
| self.connection.execute_operation(current_task, node, FuseOperation::Statfs)?; |
| let statfs_out = if let FuseResponse::Statfs(statfs_out) = response { |
| statfs_out |
| } else { |
| return error!(EINVAL); |
| }; |
| Ok(statfs { |
| f_blocks: statfs_out.st.blocks.try_into().map_err(|_| errno!(EINVAL))?, |
| f_bfree: statfs_out.st.bfree.try_into().map_err(|_| errno!(EINVAL))?, |
| f_bavail: statfs_out.st.bavail.try_into().map_err(|_| errno!(EINVAL))?, |
| f_files: statfs_out.st.files.try_into().map_err(|_| errno!(EINVAL))?, |
| f_ffree: statfs_out.st.ffree.try_into().map_err(|_| errno!(EINVAL))?, |
| f_bsize: statfs_out.st.bsize.try_into().map_err(|_| errno!(EINVAL))?, |
| f_namelen: statfs_out.st.namelen.try_into().map_err(|_| errno!(EINVAL))?, |
| f_frsize: statfs_out.st.frsize.try_into().map_err(|_| errno!(EINVAL))?, |
| ..statfs::default(FUSE_SUPER_MAGIC) |
| }) |
| } |
| fn name(&self) -> &'static FsStr { |
| b"fuse" |
| } |
| fn unmount(&self) { |
| self.connection.disconnect(); |
| } |
| } |
| |
| #[derive(Debug, Default)] |
| struct FuseNodeMutableState { |
| nlookup: u64, |
| } |
| |
| #[derive(Debug)] |
| struct FuseNode { |
| connection: Arc<FuseConnection>, |
| nodeid: u64, |
| state: Mutex<FuseNodeMutableState>, |
| } |
| |
| impl FuseNode { |
| fn from_node(node: &FsNode) -> Result<&Arc<FuseNode>, Errno> { |
| node.downcast_ops::<Arc<FuseNode>>().ok_or_else(|| errno!(ENOENT)) |
| } |
| |
| fn refresh_node_info(info: &mut FsNodeInfo, attributes: uapi::fuse_attr) -> Result<(), Errno> { |
| info.ino = attributes.ino as uapi::ino_t; |
| info.mode = FileMode::from_bits(attributes.mode); |
| info.size = attributes.size.try_into().map_err(|_| errno!(EINVAL))?; |
| info.blocks = attributes.blocks.try_into().map_err(|_| errno!(EINVAL))?; |
| info.blksize = attributes.blksize.try_into().map_err(|_| errno!(EINVAL))?; |
| info.uid = attributes.uid; |
| info.gid = attributes.gid; |
| info.link_count = attributes.nlink.try_into().map_err(|_| errno!(EINVAL))?; |
| info.time_status_change = time_from_timespec(uapi::timespec { |
| tv_sec: attributes.ctime as i64, |
| tv_nsec: attributes.ctimensec as i64, |
| })?; |
| info.time_access = time_from_timespec(uapi::timespec { |
| tv_sec: attributes.atime as i64, |
| tv_nsec: attributes.atimensec as i64, |
| })?; |
| info.time_modify = time_from_timespec(uapi::timespec { |
| tv_sec: attributes.mtime as i64, |
| tv_nsec: attributes.mtimensec as i64, |
| })?; |
| info.rdev = DeviceType::from_bits(attributes.rdev as u64); |
| Ok(()) |
| } |
| |
| /// Build a FsNodeHandle from a FuseResponse that is expected to be a FuseResponse::Entry. |
| fn fs_node_from_entry( |
| &self, |
| node: &FsNode, |
| name: &FsStr, |
| response: FuseResponse, |
| ) -> Result<FsNodeHandle, Errno> { |
| let entry = if let FuseResponse::Entry(entry) = response { |
| entry |
| } else { |
| return error!(EINVAL); |
| }; |
| if entry.nodeid == 0 { |
| return error!(ENOENT); |
| } |
| let node = node.fs().get_or_create_node(Some(entry.nodeid), |id| { |
| let fuse_node = Arc::new(FuseNode { |
| connection: self.connection.clone(), |
| nodeid: entry.nodeid, |
| state: Default::default(), |
| }); |
| let mut info = FsNodeInfo::default(); |
| FuseNode::refresh_node_info(&mut info, entry.attr)?; |
| Ok(FsNode::new_uncached(fuse_node, &node.fs(), id, info)) |
| })?; |
| // . and .. do not get their lookup count increased. |
| if !DirEntry::is_reserved_name(name) { |
| let fuse_node = FuseNode::from_node(&node)?; |
| fuse_node.state.lock().nlookup += 1; |
| } |
| Ok(node) |
| } |
| } |
| |
| struct FuseFileObject { |
| connection: Arc<FuseConnection>, |
| /// The response to the open calls from the userspace process. |
| open_out: uapi::fuse_open_out, |
| |
| /// The current kernel. This is a temporary measure to access a task on close and flush. |
| // TODO(b/297439724): Remove this. |
| kernel: Arc<Kernel>, |
| } |
| |
| impl FuseFileObject { |
| /// Returns the `FuseNode` associated with the opened file. |
| fn get_fuse_node<'a>(&self, file: &'a FileObject) -> Result<&'a Arc<FuseNode>, Errno> { |
| FuseNode::from_node(file.node()) |
| } |
| } |
| |
| impl FileOps for FuseFileObject { |
| fn close(&self, file: &FileObject) { |
| let node = if let Ok(node) = self.get_fuse_node(file) { |
| node |
| } else { |
| log_error!("Unexpected file type"); |
| return; |
| }; |
| // TODO(b/297439724): This should receives a CurrentTask instead of relying on |
| // the system task. |
| let mode = file.node().info().mode; |
| match self.kernel.kthreads.workaround_for_b297439724_new_system_task() { |
| Ok(workaround_task) => { |
| if let Err(e) = self.connection.execute_operation( |
| &workaround_task, |
| node, |
| FuseOperation::Release { flags: file.flags(), mode, open_out: self.open_out }, |
| ) { |
| log_error!("Error when relasing fh: {e:?}"); |
| } |
| workaround_task.thread_group.exit(ExitStatus::Exit(0)); |
| workaround_task.release(()); |
| } |
| Err(e) => { |
| log_error!("Error creating workaround task: {e:?}"); |
| } |
| } |
| } |
| |
| fn flush(&self, file: &FileObject) { |
| let node = if let Ok(node) = self.get_fuse_node(file) { |
| node |
| } else { |
| log_error!("Unexpected file type"); |
| return; |
| }; |
| // TODO(b/297439724): This should receives a CurrentTask instead of relying on |
| // the system task. |
| match self.kernel.kthreads.workaround_for_b297439724_new_system_task() { |
| Ok(workaround_task) => { |
| if let Err(e) = self.connection.execute_operation( |
| &workaround_task, |
| node, |
| FuseOperation::Flush(self.open_out), |
| ) { |
| log_error!("Error when flushing fh: {e:?}"); |
| } |
| workaround_task.thread_group.exit(ExitStatus::Exit(0)); |
| workaround_task.release(()); |
| } |
| Err(e) => { |
| log_error!("Error creating workaround task: {e:?}"); |
| } |
| } |
| } |
| |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| |
| fn read( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| let node = self.get_fuse_node(file)?; |
| let response = self.connection.execute_operation( |
| current_task, |
| node, |
| FuseOperation::Read(uapi::fuse_read_in { |
| fh: self.open_out.fh, |
| offset: offset.try_into().map_err(|_| errno!(EINVAL))?, |
| size: data.available().try_into().unwrap_or(u32::MAX), |
| read_flags: 0, |
| lock_owner: 0, |
| flags: 0, |
| padding: 0, |
| }), |
| )?; |
| let read_out = if let FuseResponse::Read(read_out) = response { |
| read_out |
| } else { |
| return error!(EINVAL); |
| }; |
| data.write(&read_out) |
| } |
| |
| fn write( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| let node = self.get_fuse_node(file)?; |
| let content = data.peek_all()?; |
| let response = self.connection.execute_operation( |
| current_task, |
| node, |
| FuseOperation::Write { |
| write_in: uapi::fuse_write_in { |
| fh: self.open_out.fh, |
| offset: offset.try_into().map_err(|_| errno!(EINVAL))?, |
| size: content.len().try_into().map_err(|_| errno!(EINVAL))?, |
| write_flags: 0, |
| lock_owner: 0, |
| flags: 0, |
| padding: 0, |
| }, |
| content, |
| }, |
| )?; |
| let write_out = if let FuseResponse::Write(write_out) = response { |
| write_out |
| } else { |
| return error!(EINVAL); |
| }; |
| |
| let written = write_out.size as usize; |
| |
| data.advance(written)?; |
| Ok(written) |
| } |
| |
| fn seek( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| current_offset: off_t, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno> { |
| // Only delegate SEEK_DATA and SEEK_HOLE to the userspace process. |
| if matches!(target, SeekTarget::Data(_) | SeekTarget::Hole(_)) { |
| let node = self.get_fuse_node(file)?; |
| let response = self.connection.execute_operation( |
| current_task, |
| node, |
| FuseOperation::Seek(uapi::fuse_lseek_in { |
| fh: self.open_out.fh, |
| offset: target.offset().try_into().map_err(|_| errno!(EINVAL))?, |
| whence: target.whence(), |
| padding: 0, |
| }), |
| ); |
| match response { |
| Ok(response) => { |
| let seek_out = if let FuseResponse::Seek(seek_out) = response { |
| seek_out |
| } else { |
| return error!(EINVAL); |
| }; |
| return seek_out.offset.try_into().map_err(|_| errno!(EINVAL)); |
| } |
| // If errno is ENOSYS, the userspace process doesn't support the seek operation and |
| // the default seek must be used. |
| Err(errno) if errno == ENOSYS => {} |
| Err(errno) => return Err(errno), |
| }; |
| } |
| |
| default_seek(current_offset, target, |offset| { |
| let eof_offset = default_eof_offset(file, current_task)?; |
| offset.checked_add(eof_offset).ok_or_else(|| errno!(EINVAL)) |
| }) |
| } |
| |
| fn wait_async( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _waiter: &Waiter, |
| _events: FdEvents, |
| _handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| None |
| } |
| |
| fn query_events( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> { |
| let node = self.get_fuse_node(file)?; |
| let response = self.connection.execute_operation( |
| current_task, |
| node, |
| FuseOperation::Poll(uapi::fuse_poll_in { |
| fh: self.open_out.fh, |
| kh: 0, |
| flags: 0, |
| events: FdEvents::all().bits(), |
| }), |
| )?; |
| let poll_out = if let FuseResponse::Poll(poll_out) = response { |
| poll_out |
| } else { |
| return error!(EINVAL); |
| }; |
| FdEvents::from_bits(poll_out.revents).ok_or_else(|| errno!(EINVAL)) |
| } |
| |
| fn readdir( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| let configuration = self.connection.get_configuration(current_task)?; |
| let use_readdirplus = { |
| if configuration.flags.contains(FuseInitFlags::DO_READDIRPLUS) { |
| if configuration.flags.contains(FuseInitFlags::READDIRPLUS_AUTO) { |
| sink.offset() == 0 |
| } else { |
| true |
| } |
| } else { |
| false |
| } |
| }; |
| // Request a number of bytes related to the user capacity. If none is given, default to a |
| // single page of data. |
| let user_capacity = if let Some(base_user_capacity) = sink.user_capacity() { |
| if use_readdirplus { |
| // Add some amount of capacity for the entries. |
| base_user_capacity * 3 / 2 |
| } else { |
| base_user_capacity |
| } |
| } else { |
| *PAGE_SIZE as usize |
| }; |
| let node = self.get_fuse_node(file)?; |
| let response = self.connection.execute_operation( |
| current_task, |
| node, |
| FuseOperation::Readdir { |
| read_in: uapi::fuse_read_in { |
| fh: self.open_out.fh, |
| offset: sink.offset().try_into().map_err(|_| errno!(EINVAL))?, |
| size: user_capacity.try_into().map_err(|_| errno!(EINVAL))?, |
| read_flags: 0, |
| lock_owner: 0, |
| flags: 0, |
| padding: 0, |
| }, |
| use_readdirplus, |
| }, |
| )?; |
| let dirents = if let FuseResponse::Readdir(dirents) = response { |
| dirents |
| } else { |
| return error!(EINVAL); |
| }; |
| let mut sink_result = Ok(()); |
| for (dirent, name, entry) in dirents { |
| if let Some(entry) = entry { |
| // nodeid == 0 means the server doesn't want to send entry info. |
| if entry.nodeid != 0 { |
| if let Err(e) = |
| node.fs_node_from_entry(file.node(), &name, FuseResponse::Entry(entry)) |
| { |
| log_error!("Unable to prefill entry: {e:?}"); |
| } |
| } |
| } |
| if sink_result.is_ok() { |
| sink_result = sink.add( |
| dirent.ino, |
| dirent.off.try_into().map_err(|_| errno!(EINVAL))?, |
| DirectoryEntryType::from_bits( |
| dirent.type_.try_into().map_err(|_| errno!(EINVAL))?, |
| ), |
| &name, |
| ); |
| } |
| } |
| sink_result |
| } |
| |
| fn ioctl( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| not_implemented_log_once!("ioctl is using default implementation for use."); |
| default_ioctl(file, current_task, request, arg) |
| } |
| |
| fn fcntl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| cmd: u32, |
| _arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| not_implemented_log_once!("fcntl is using default implementation for use."); |
| default_fcntl(cmd) |
| } |
| } |
| |
| impl FsNodeOps for Arc<FuseNode> { |
| fn check_access( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| access: Access, |
| ) -> Result<(), Errno> { |
| if FuseFs::from_fs(&node.fs())?.default_permissions { |
| return Errno::fail(ENOSYS); |
| } |
| |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Access { mask: (access & Access::ACCESS_MASK).bits() }, |
| )?; |
| if let FuseResponse::Access(result) = response { |
| result |
| } else { |
| error!(EINVAL) |
| } |
| } |
| |
| fn create_file_ops( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| // The node already exists. The creation has been handled before calling this method. |
| let flags = flags & !(OpenFlags::CREAT | OpenFlags::EXCL); |
| let mode = node.info().mode; |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Open { flags, mode }, |
| )?; |
| let open_out = if let FuseResponse::Open(open_out) = response { |
| open_out |
| } else { |
| return error!(EINVAL); |
| }; |
| Ok(Box::new(FuseFileObject { |
| connection: self.connection.clone(), |
| open_out, |
| kernel: current_task.kernel().clone(), |
| })) |
| } |
| |
| fn lookup( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| ) -> Result<FsNodeHandle, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Lookup { name: name.to_owned() }, |
| )?; |
| self.fs_node_from_entry(node, name, response) |
| } |
| |
| fn mknod( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| mode: FileMode, |
| dev: DeviceType, |
| _owner: FsCred, |
| ) -> Result<FsNodeHandle, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Mknod { |
| mknod_in: uapi::fuse_mknod_in { |
| mode: mode.bits(), |
| rdev: dev.bits() as u32, |
| umask: current_task.fs().umask().bits(), |
| padding: 0, |
| }, |
| name: name.to_owned(), |
| }, |
| )?; |
| self.fs_node_from_entry(node, name, response) |
| } |
| |
| fn mkdir( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| mode: FileMode, |
| _owner: FsCred, |
| ) -> Result<FsNodeHandle, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Mkdir { |
| mkdir_in: uapi::fuse_mkdir_in { |
| mode: mode.bits(), |
| umask: current_task.fs().umask().bits(), |
| }, |
| name: name.to_owned(), |
| }, |
| )?; |
| self.fs_node_from_entry(node, name, response) |
| } |
| |
| fn create_symlink( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| target: &FsStr, |
| _owner: FsCred, |
| ) -> Result<FsNodeHandle, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Symlink { target: target.to_owned(), name: name.to_owned() }, |
| )?; |
| self.fs_node_from_entry(node, name, response) |
| } |
| |
| fn readlink(&self, _node: &FsNode, current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> { |
| let response = |
| self.connection.execute_operation(current_task, self, FuseOperation::Readlink)?; |
| let read_out = if let FuseResponse::Read(read_out) = response { |
| read_out |
| } else { |
| return error!(EINVAL); |
| }; |
| Ok(SymlinkTarget::Path(read_out)) |
| } |
| |
| fn link( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| child: &FsNodeHandle, |
| ) -> Result<(), Errno> { |
| let child_node = FuseNode::from_node(child)?; |
| self.connection |
| .execute_operation( |
| current_task, |
| self, |
| FuseOperation::Link { |
| link_in: uapi::fuse_link_in { oldnodeid: child_node.nodeid }, |
| name: name.to_owned(), |
| }, |
| ) |
| .map(|_| ()) |
| } |
| |
| fn unlink( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| _child: &FsNodeHandle, |
| ) -> Result<(), Errno> { |
| self.connection |
| .execute_operation(current_task, self, FuseOperation::Unlink { name: name.to_owned() }) |
| .map(|_| ()) |
| } |
| |
| fn truncate( |
| &self, |
| node: &FsNode, |
| current_task: &CurrentTask, |
| length: u64, |
| ) -> Result<(), Errno> { |
| node.update_info(|info| { |
| // Truncate is implemented by updating the attributes of the file. |
| let attributes = uapi::fuse_setattr_in { |
| size: length, |
| valid: uapi::FATTR_SIZE, |
| ..Default::default() |
| }; |
| |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::SetAttr(attributes), |
| )?; |
| let attr = if let FuseResponse::Attr(attr) = response { |
| attr |
| } else { |
| return error!(EINVAL); |
| }; |
| FuseNode::refresh_node_info(info, attr.attr)?; |
| Ok(()) |
| }) |
| } |
| |
| fn allocate( |
| &self, |
| _node: &FsNode, |
| _current_task: &CurrentTask, |
| _mode: FallocMode, |
| _offset: u64, |
| _length: u64, |
| ) -> Result<(), Errno> { |
| not_implemented!("FsNodeOps::allocate"); |
| error!(ENOTSUP) |
| } |
| |
| fn refresh_info<'a>( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| info: &'a RwLock<FsNodeInfo>, |
| ) -> Result<RwLockReadGuard<'a, FsNodeInfo>, Errno> { |
| let response = |
| self.connection.execute_operation(current_task, self, FuseOperation::GetAttr)?; |
| let attr = if let FuseResponse::Attr(attr) = response { |
| attr |
| } else { |
| return error!(EINVAL); |
| }; |
| let mut info = info.write(); |
| FuseNode::refresh_node_info(&mut info, attr.attr)?; |
| Ok(RwLockWriteGuard::downgrade(info)) |
| } |
| |
| fn get_xattr( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| max_size: usize, |
| ) -> Result<ValueOrSize<FsString>, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::GetXAttr { |
| getxattr_in: uapi::fuse_getxattr_in { |
| size: max_size.try_into().map_err(|_| errno!(EINVAL))?, |
| padding: 0, |
| }, |
| name: name.to_vec(), |
| }, |
| )?; |
| if let FuseResponse::GetXAttr(result) = response { |
| Ok(result) |
| } else { |
| error!(EINVAL) |
| } |
| } |
| |
| fn set_xattr( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| value: &FsStr, |
| op: XattrOp, |
| ) -> Result<(), Errno> { |
| self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::SetXAttr { |
| setxattr_in: uapi::fuse_setxattr_in { |
| size: value.len().try_into().map_err(|_| errno!(EINVAL))?, |
| flags: op.into_flags(), |
| setxattr_flags: 0, |
| padding: 0, |
| }, |
| name: name.to_owned(), |
| value: value.to_owned(), |
| }, |
| )?; |
| Ok(()) |
| } |
| |
| fn remove_xattr( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| name: &FsStr, |
| ) -> Result<(), Errno> { |
| self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::RemoveXAttr { name: name.to_owned() }, |
| )?; |
| Ok(()) |
| } |
| |
| fn list_xattrs( |
| &self, |
| _node: &FsNode, |
| current_task: &CurrentTask, |
| max_size: usize, |
| ) -> Result<ValueOrSize<Vec<FsString>>, Errno> { |
| let response = self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::ListXAttr(uapi::fuse_getxattr_in { |
| size: max_size.try_into().map_err(|_| errno!(EINVAL))?, |
| padding: 0, |
| }), |
| )?; |
| if let FuseResponse::GetXAttr(result) = response { |
| Ok(result.map(|s| { |
| let mut result = s.split(|c| *c == 0).map(|s| s.to_vec()).collect::<Vec<_>>(); |
| // The returned string ends with a '\0', so the split ends with an empty value that |
| // needs to be removed. |
| result.pop(); |
| result |
| })) |
| } else { |
| error!(EINVAL) |
| } |
| } |
| |
| fn forget(&self, _node: &FsNode, current_task: &CurrentTask) -> Result<(), Errno> { |
| if self.connection.state.lock().disconnected { |
| return Ok(()); |
| } |
| let nlookup = self.state.lock().nlookup; |
| if nlookup > 0 { |
| self.connection.execute_operation( |
| current_task, |
| self, |
| FuseOperation::Forget(uapi::fuse_forget_in { nlookup }), |
| )?; |
| }; |
| Ok(()) |
| } |
| } |
| |
| #[derive(Debug, Default)] |
| struct FuseConnection { |
| /// Mutable state of the connection. |
| state: Mutex<FuseMutableState>, |
| } |
| |
| impl FuseConnection { |
| fn disconnect(&self) { |
| self.state.lock().disconnect() |
| } |
| |
| fn get_configuration( |
| &self, |
| current_task: &CurrentTask, |
| ) -> Result<Arc<FuseConfiguration>, Errno> { |
| let mut state = self.state.lock(); |
| if let Some(configuration) = state.configuration.as_ref() { |
| return Ok(configuration.clone()); |
| } |
| loop { |
| if state.disconnected { |
| return error!(EINTR); |
| } |
| let waiter = Waiter::new(); |
| state.waiters.wait_async_value(&waiter, CONFIGURATION_AVAILABLE_EVENT); |
| if let Some(configuration) = state.configuration.as_ref() { |
| return Ok(configuration.clone()); |
| } |
| MutexGuard::unlocked(&mut state, || waiter.wait(current_task))?; |
| } |
| } |
| |
| /// Execute the given operation on the `node`. If the operation is not asynchronous, this |
| /// method will wait on the userspace process for a response. If the operation is interrupted, |
| /// an interrupt will be sent to the userspace process and the operation will then block until |
| /// the initial operation has a response. This block can only be interrupted by the filesystem |
| /// being unmounted. |
| fn execute_operation( |
| &self, |
| current_task: &CurrentTask, |
| node: &FuseNode, |
| operation: FuseOperation, |
| ) -> Result<FuseResponse, Errno> { |
| let configuration = match operation { |
| FuseOperation::Init => Arc::new(FuseConfiguration::for_init()), |
| _ => self.get_configuration(current_task)?, |
| }; |
| let mut state = self.state.lock(); |
| if let Some(result) = state.operations_state.get(&operation.opcode()) { |
| return result.clone(); |
| } |
| if !operation.has_response() { |
| state.queue_operation(current_task, node, operation, configuration, None)?; |
| return Ok(FuseResponse::None); |
| } |
| let waiter = Waiter::new(); |
| let is_async = operation.is_async(); |
| let unique_id = state.queue_operation( |
| current_task, |
| node, |
| operation, |
| configuration.clone(), |
| Some(&waiter), |
| )?; |
| if is_async { |
| return Ok(FuseResponse::None); |
| } |
| let mut first_loop = true; |
| loop { |
| if let Some(response) = state.get_response(unique_id) { |
| return response; |
| } |
| match MutexGuard::unlocked(&mut state, || waiter.wait(current_task)) { |
| Ok(()) => {} |
| Err(e) if e == EINTR => { |
| // If interrupted by another process, send an interrupt command to the server |
| // the first time, then wait unconditionally. |
| if first_loop { |
| state.interrupt(current_task, node, unique_id, configuration.clone())?; |
| first_loop = false; |
| } |
| } |
| Err(e) => { |
| log_error!("Unexpected error: {e:?}"); |
| return Err(e); |
| } |
| } |
| } |
| } |
| |
| fn wait_async( |
| &self, |
| waiter: &Waiter, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| Some(self.state.lock().waiters.wait_async_fd_events(waiter, events, handler)) |
| } |
| |
| fn query_events(&self) -> FdEvents { |
| self.state.lock().query_events() |
| } |
| |
| fn read(&self, data: &mut dyn OutputBuffer) -> Result<usize, Errno> { |
| self.state.lock().read(data) |
| } |
| |
| fn write(&self, data: &mut dyn InputBuffer) -> Result<usize, Errno> { |
| self.state.lock().write(data) |
| } |
| } |
| |
| #[derive(Debug)] |
| struct FuseConfiguration { |
| flags: FuseInitFlags, |
| } |
| |
| impl FuseConfiguration { |
| // Build a fake configuration, valid only to send the Init message. |
| fn for_init() -> Self { |
| Self { flags: FuseInitFlags::all() } |
| } |
| } |
| |
| impl TryFrom<uapi::fuse_init_out> for FuseConfiguration { |
| type Error = Errno; |
| fn try_from(init_out: uapi::fuse_init_out) -> Result<Self, Errno> { |
| let unknown_flags = init_out.flags & !FuseInitFlags::all().bits(); |
| if unknown_flags != 0 { |
| log_warn!("FUSE daemon requested unknown flags in init: {unknown_flags}"); |
| } |
| let flags = FuseInitFlags::from_bits_truncate(init_out.flags); |
| Ok(Self { flags }) |
| } |
| } |
| |
| /// A per connection state for operations that can be shortcircuited. |
| /// |
| /// For a number of Fuse operation, Fuse protocol specifies that if they fail in a specific way, |
| /// they should not be sent to the server again and must be handled in a predefined way. This |
| /// map keep track of these operations for a given connection. If this map contains a result for a |
| /// given opcode, any further attempt to send this opcode to userspace will be answered with the |
| /// content of the map. |
| type OperationsState = HashMap<uapi::fuse_opcode, Result<FuseResponse, Errno>>; |
| |
| #[derive(Debug, Default)] |
| struct FuseMutableState { |
| /// Whether the mount has been disconnected. |
| disconnected: bool, |
| |
| /// Last unique id used to identify messages between the kernel and user space. |
| last_unique_id: u64, |
| |
| /// The configuration, negotiated with the client. |
| configuration: Option<Arc<FuseConfiguration>>, |
| |
| /// In progress operations. |
| operations: HashMap<u64, RunningOperation>, |
| |
| /// Enqueued messages. These messages have not yet been sent to userspace. There should be |
| /// multiple queues, but for now, push every messages to the same queue. |
| /// New messages are added at the end of the queues. Read consume from the front of the queue. |
| message_queue: VecDeque<FuseKernelMessage>, |
| |
| /// Queue to notify of new messages. |
| waiters: WaitQueue, |
| |
| /// The state of the different operations, to allow short-circuiting the userspace process. |
| operations_state: OperationsState, |
| } |
| |
| impl FuseMutableState { |
| fn set_configuration(&mut self, configuration: FuseConfiguration) { |
| debug_assert!(self.configuration.is_none()); |
| log_trace!("Fuse configuration: {configuration:?}"); |
| self.configuration = Some(Arc::new(configuration)); |
| self.waiters.notify_value(CONFIGURATION_AVAILABLE_EVENT); |
| } |
| |
| /// Disconnect the mount. Happens on unmount. Every filesystem operation will fail with EINTR, |
| /// and every read/write on the /dev/fuse fd will fail with ENODEV. |
| fn disconnect(&mut self) { |
| if self.disconnected { |
| return; |
| } |
| self.disconnected = true; |
| self.message_queue.clear(); |
| for operation in &mut self.operations { |
| operation.1.response = Some(error!(EINTR)); |
| } |
| self.waiters.notify_all(); |
| } |
| |
| /// Queue the given operation on the internal queue for the userspace daemon to read. If |
| /// `waiter` is not None, register `waiter` to be notified when userspace responds to the |
| /// operation. This should only be used if the operation expects a response. |
| fn queue_operation( |
| &mut self, |
| current_task: &CurrentTask, |
| node: &FuseNode, |
| operation: FuseOperation, |
| configuration: Arc<FuseConfiguration>, |
| waiter: Option<&Waiter>, |
| ) -> Result<u64, Errno> { |
| debug_assert!(waiter.is_some() == operation.has_response(), "{operation:?}"); |
| if self.disconnected { |
| return error!(EINTR); |
| } |
| let operation = Arc::new(operation); |
| self.last_unique_id += 1; |
| let message = FuseKernelMessage::new( |
| self.last_unique_id, |
| current_task, |
| node, |
| operation, |
| configuration, |
| )?; |
| if let Some(waiter) = waiter { |
| self.waiters.wait_async_value(waiter, self.last_unique_id); |
| } |
| if message.operation.has_response() { |
| self.operations.insert(self.last_unique_id, message.operation.clone().into()); |
| } |
| self.message_queue.push_back(message); |
| self.waiters.notify_fd_events(FdEvents::POLLIN); |
| Ok(self.last_unique_id) |
| } |
| |
| /// Interrupt the operation with the given unique_id. |
| /// |
| /// If the operation is still enqueued, this will immediately dequeue the operation and return |
| /// with an EINTR error. |
| /// |
| /// If not, it will send an interrupt operation. |
| fn interrupt( |
| &mut self, |
| current_task: &CurrentTask, |
| node: &FuseNode, |
| unique_id: u64, |
| configuration: Arc<FuseConfiguration>, |
| ) -> Result<(), Errno> { |
| debug_assert!(self.operations.contains_key(&unique_id)); |
| |
| let mut in_queue = false; |
| self.message_queue.retain(|m| { |
| if m.header.unique == unique_id { |
| self.operations.remove(&unique_id); |
| in_queue = true; |
| false |
| } else { |
| true |
| } |
| }); |
| if in_queue { |
| // Nothing to do, the operation has been cancelled before being sent. |
| return error!(EINTR); |
| } |
| self.queue_operation( |
| current_task, |
| node, |
| FuseOperation::Interrupt { unique_id }, |
| configuration, |
| None, |
| ) |
| .map(|_| ()) |
| } |
| |
| /// Returns the response for the operation with the given identifier. Returns None if the |
| /// operation is still in flight. |
| fn get_response(&mut self, unique_id: u64) -> Option<Result<FuseResponse, Errno>> { |
| match self.operations.entry(unique_id) { |
| Entry::Vacant(_) => Some(error!(EINVAL)), |
| Entry::Occupied(mut entry) => { |
| let result = entry.get_mut().response.take(); |
| if result.is_some() { |
| entry.remove(); |
| } |
| result |
| } |
| } |
| } |
| |
| fn query_events(&self) -> FdEvents { |
| let mut events = FdEvents::POLLOUT; |
| if self.disconnected || !self.message_queue.is_empty() { |
| events |= FdEvents::POLLIN |
| }; |
| if self.disconnected { |
| events |= FdEvents::POLLERR; |
| } |
| events |
| } |
| |
| fn read(&mut self, data: &mut dyn OutputBuffer) -> Result<usize, Errno> { |
| if self.disconnected { |
| return error!(ENODEV); |
| } |
| if let Some(message) = self.message_queue.pop_front() { |
| message.serialize(data, &message.configuration) |
| } else { |
| error!(EAGAIN) |
| } |
| } |
| |
| fn write(&mut self, data: &mut dyn InputBuffer) -> Result<usize, Errno> { |
| if self.disconnected { |
| return error!(ENODEV); |
| } |
| let mut header = uapi::fuse_out_header::new_zeroed(); |
| if data.read(header.as_bytes_mut())? != std::mem::size_of::<uapi::fuse_out_header>() { |
| return error!(EINVAL); |
| } |
| let remainder: usize = (header.len as usize) - std::mem::size_of::<uapi::fuse_out_header>(); |
| if data.available() < remainder { |
| return error!(EINVAL); |
| } |
| self.waiters.notify_value(header.unique); |
| let running_operation = |
| self.operations.get_mut(&header.unique).ok_or_else(|| errno!(EINVAL))?; |
| let operation = running_operation.operation.clone(); |
| if header.error < 0 { |
| log_trace!("Fuse: {operation:?} -> {header:?}"); |
| let code = i16::try_from(-header.error).unwrap_or(EINVAL.error_code() as i16); |
| let errno = errno_from_code!(code); |
| running_operation.response = |
| Some(operation.handle_error(&mut self.operations_state, errno)); |
| } else { |
| let mut buffer = vec![0u8; remainder]; |
| if data.read(&mut buffer)? != remainder { |
| return error!(EINVAL); |
| } |
| let response = operation.parse_response(buffer)?; |
| log_trace!("Fuse: {operation:?} -> {response:?}"); |
| if operation.is_async() { |
| self.handle_async(&operation, response)?; |
| } else { |
| running_operation.response = Some(Ok(response)); |
| } |
| } |
| if operation.is_async() { |
| self.operations.remove(&header.unique); |
| } |
| Ok(header.len as usize) |
| } |
| |
| fn handle_async( |
| &mut self, |
| operation: &FuseOperation, |
| response: FuseResponse, |
| ) -> Result<(), Errno> { |
| debug_assert!(operation.is_async()); |
| if let FuseOperation::Init = operation { |
| if let FuseResponse::Init(init_out) = response { |
| self.set_configuration(init_out.try_into()?); |
| } else { |
| return error!(EINVAL); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// An operation that is either queued to be send to userspace, or already sent to userspace and |
| /// waiting for a response. |
| #[derive(Debug)] |
| struct RunningOperation { |
| operation: Arc<FuseOperation>, |
| response: Option<Result<FuseResponse, Errno>>, |
| } |
| |
| impl From<Arc<FuseOperation>> for RunningOperation { |
| fn from(operation: Arc<FuseOperation>) -> Self { |
| Self { operation, response: None } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct FuseKernelMessage { |
| header: uapi::fuse_in_header, |
| operation: Arc<FuseOperation>, |
| configuration: Arc<FuseConfiguration>, |
| } |
| |
| impl FuseKernelMessage { |
| fn new( |
| unique: u64, |
| current_task: &CurrentTask, |
| node: &FuseNode, |
| operation: Arc<FuseOperation>, |
| configuration: Arc<FuseConfiguration>, |
| ) -> Result<Self, Errno> { |
| let creds = current_task.creds(); |
| Ok(Self { |
| header: uapi::fuse_in_header { |
| len: u32::try_from( |
| std::mem::size_of::<uapi::fuse_in_header>() + operation.len(&configuration), |
| ) |
| .map_err(|_| errno!(EINVAL))?, |
| opcode: operation.opcode(), |
| unique, |
| nodeid: node.nodeid, |
| uid: creds.uid, |
| gid: creds.gid, |
| pid: current_task.get_tid() as u32, |
| padding: 0, |
| }, |
| operation, |
| configuration, |
| }) |
| } |
| |
| fn serialize( |
| &self, |
| data: &mut dyn OutputBuffer, |
| configuration: &FuseConfiguration, |
| ) -> Result<usize, Errno> { |
| let size = data.write(self.header.as_bytes())?; |
| Ok(size + self.operation.serialize(data, configuration)?) |
| } |
| } |
| |
| bitflags::bitflags! { |
| pub struct FuseInitFlags : u32 { |
| const BIG_WRITES = uapi::FUSE_BIG_WRITES; |
| const DONT_MASK = uapi::FUSE_DONT_MASK; |
| const SPLICE_WRITE = uapi::FUSE_SPLICE_WRITE; |
| const SPLICE_MOVE = uapi::FUSE_SPLICE_MOVE; |
| const SPLICE_READ = uapi::FUSE_SPLICE_READ; |
| const DO_READDIRPLUS = uapi::FUSE_DO_READDIRPLUS; |
| const READDIRPLUS_AUTO = uapi::FUSE_READDIRPLUS_AUTO; |
| const SETXATTR_EXT = uapi::FUSE_SETXATTR_EXT; |
| } |
| } |
| |
| #[derive(Debug)] |
| enum FuseOperation { |
| Access { |
| mask: u32, |
| }, |
| Flush(uapi::fuse_open_out), |
| Forget(uapi::fuse_forget_in), |
| GetAttr, |
| Init, |
| Interrupt { |
| /// Identifier of the operation to interrupt |
| unique_id: u64, |
| }, |
| GetXAttr { |
| getxattr_in: uapi::fuse_getxattr_in, |
| /// Name of the attribute |
| name: FsString, |
| }, |
| ListXAttr(uapi::fuse_getxattr_in), |
| Lookup { |
| /// Name of the entry to lookup |
| name: FsString, |
| }, |
| Mkdir { |
| mkdir_in: uapi::fuse_mkdir_in, |
| /// Name of the entry to create |
| name: FsString, |
| }, |
| Mknod { |
| mknod_in: uapi::fuse_mknod_in, |
| /// Name of the node to create |
| name: FsString, |
| }, |
| Link { |
| link_in: uapi::fuse_link_in, |
| /// Name of the link to create |
| name: FsString, |
| }, |
| Open { |
| flags: OpenFlags, |
| mode: FileMode, |
| }, |
| Poll(uapi::fuse_poll_in), |
| Read(uapi::fuse_read_in), |
| Readdir { |
| read_in: uapi::fuse_read_in, |
| /// Whether to use the READDIRPLUS api |
| use_readdirplus: bool, |
| }, |
| Readlink, |
| Release { |
| flags: OpenFlags, |
| mode: FileMode, |
| open_out: uapi::fuse_open_out, |
| }, |
| RemoveXAttr { |
| /// Name of the attribute |
| name: FsString, |
| }, |
| Seek(uapi::fuse_lseek_in), |
| SetAttr(uapi::fuse_setattr_in), |
| SetXAttr { |
| setxattr_in: uapi::fuse_setxattr_in, |
| /// Name of the attribute |
| name: FsString, |
| /// Value of the attribute |
| value: FsString, |
| }, |
| Statfs, |
| Symlink { |
| /// Target of the link |
| target: FsString, |
| /// Name of the link |
| name: FsString, |
| }, |
| Unlink { |
| /// Name of the file to unlink |
| name: FsString, |
| }, |
| Write { |
| write_in: uapi::fuse_write_in, |
| // Content to write |
| content: Vec<u8>, |
| }, |
| } |
| |
| #[derive(Clone, Debug)] |
| enum FuseResponse { |
| Access(Result<(), Errno>), |
| Attr(uapi::fuse_attr_out), |
| Entry(uapi::fuse_entry_out), |
| GetXAttr(ValueOrSize<FsString>), |
| Init(uapi::fuse_init_out), |
| Open(uapi::fuse_open_out), |
| Poll(uapi::fuse_poll_out), |
| Read( |
| // Content read |
| Vec<u8>, |
| ), |
| Seek(uapi::fuse_lseek_out), |
| Readdir(Vec<(uapi::fuse_dirent, FsString, Option<uapi::fuse_entry_out>)>), |
| Statfs(uapi::fuse_statfs_out), |
| Write(uapi::fuse_write_out), |
| None, |
| } |
| |
| impl FuseOperation { |
| fn serialize( |
| &self, |
| data: &mut dyn OutputBuffer, |
| configuration: &FuseConfiguration, |
| ) -> Result<usize, Errno> { |
| match self { |
| Self::Access { mask } => { |
| let message = uapi::fuse_access_in { mask: *mask, padding: 0 }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::Flush(open_in) => { |
| let message = |
| uapi::fuse_flush_in { fh: open_in.fh, unused: 0, padding: 0, lock_owner: 0 }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::Forget(forget_in) => data.write_all(forget_in.as_bytes()), |
| Self::GetAttr | Self::Readlink | Self::Statfs => Ok(0), |
| Self::GetXAttr { getxattr_in, name } => { |
| let mut len = data.write_all(getxattr_in.as_bytes())?; |
| len += Self::write_null_terminated(data, name)?; |
| Ok(len) |
| } |
| Self::Init => { |
| let message = uapi::fuse_init_in { |
| major: uapi::FUSE_KERNEL_VERSION, |
| minor: uapi::FUSE_KERNEL_MINOR_VERSION, |
| flags: FuseInitFlags::all().bits(), |
| ..Default::default() |
| }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::Interrupt { unique_id } => { |
| let message = uapi::fuse_interrupt_in { unique: *unique_id }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::ListXAttr(getxattr_in) => data.write_all(getxattr_in.as_bytes()), |
| Self::Lookup { name } => Self::write_null_terminated(data, name), |
| Self::Open { flags, .. } => { |
| let message = uapi::fuse_open_in { flags: flags.bits(), open_flags: 0 }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::Poll(poll_in) => data.write_all(poll_in.as_bytes()), |
| Self::Mkdir { mkdir_in, name } => { |
| let mut len = data.write_all(mkdir_in.as_bytes())?; |
| len += Self::write_null_terminated(data, name)?; |
| Ok(len) |
| } |
| Self::Mknod { mknod_in, name } => { |
| let mut len = data.write_all(mknod_in.as_bytes())?; |
| len += Self::write_null_terminated(data, name)?; |
| Ok(len) |
| } |
| Self::Link { link_in, name } => { |
| let mut len = data.write_all(link_in.as_bytes())?; |
| len += Self::write_null_terminated(data, name)?; |
| Ok(len) |
| } |
| Self::Read(read_in) | Self::Readdir { read_in, .. } => { |
| data.write_all(read_in.as_bytes()) |
| } |
| Self::Release { open_out, .. } => { |
| let message = uapi::fuse_release_in { |
| fh: open_out.fh, |
| flags: 0, |
| release_flags: 0, |
| lock_owner: 0, |
| }; |
| data.write_all(message.as_bytes()) |
| } |
| Self::RemoveXAttr { name } => Self::write_null_terminated(data, name), |
| Self::Seek(seek_in) => data.write_all(seek_in.as_bytes()), |
| Self::SetAttr(setattr_in) => data.write_all(setattr_in.as_bytes()), |
| Self::SetXAttr { setxattr_in, name, value } => { |
| let header = if configuration.flags.contains(FuseInitFlags::SETXATTR_EXT) { |
| setxattr_in.as_bytes() |
| } else { |
| &setxattr_in.as_bytes()[..8] |
| }; |
| let mut len = data.write_all(header)?; |
| len += Self::write_null_terminated(data, name)?; |
| len += data.write_all(value.as_bytes())?; |
| Ok(len) |
| } |
| Self::Symlink { target, name } => { |
| let mut len = Self::write_null_terminated(data, name)?; |
| len += Self::write_null_terminated(data, target)?; |
| Ok(len) |
| } |
| Self::Unlink { name } => Self::write_null_terminated(data, name), |
| Self::Write { write_in, content } => { |
| let mut len = data.write_all(write_in.as_bytes())?; |
| len += data.write_all(content)?; |
| Ok(len) |
| } |
| } |
| } |
| |
| fn write_null_terminated( |
| data: &mut dyn OutputBuffer, |
| content: &Vec<u8>, |
| ) -> Result<usize, Errno> { |
| let mut len = data.write_all(content.as_bytes())?; |
| len += data.write_all(&[0])?; |
| Ok(len) |
| } |
| |
| fn opcode(&self) -> u32 { |
| match self { |
| Self::Access { .. } => uapi::fuse_opcode_FUSE_ACCESS, |
| Self::Flush(_) => uapi::fuse_opcode_FUSE_FLUSH, |
| Self::Forget(_) => uapi::fuse_opcode_FUSE_FORGET, |
| Self::GetAttr => uapi::fuse_opcode_FUSE_GETATTR, |
| Self::GetXAttr { .. } => uapi::fuse_opcode_FUSE_GETXATTR, |
| Self::Init => uapi::fuse_opcode_FUSE_INIT, |
| Self::Interrupt { .. } => uapi::fuse_opcode_FUSE_INTERRUPT, |
| Self::ListXAttr(_) => uapi::fuse_opcode_FUSE_LISTXATTR, |
| Self::Lookup { .. } => uapi::fuse_opcode_FUSE_LOOKUP, |
| Self::Mkdir { .. } => uapi::fuse_opcode_FUSE_MKDIR, |
| Self::Mknod { .. } => uapi::fuse_opcode_FUSE_MKNOD, |
| Self::Link { .. } => uapi::fuse_opcode_FUSE_LINK, |
| Self::Open { flags, mode } => { |
| if mode.is_dir() || flags.contains(OpenFlags::DIRECTORY) { |
| uapi::fuse_opcode_FUSE_OPENDIR |
| } else { |
| uapi::fuse_opcode_FUSE_OPEN |
| } |
| } |
| Self::Poll(_) => uapi::fuse_opcode_FUSE_POLL, |
| Self::Read(_) => uapi::fuse_opcode_FUSE_READ, |
| Self::Readdir { use_readdirplus, .. } => { |
| if *use_readdirplus { |
| uapi::fuse_opcode_FUSE_READDIRPLUS |
| } else { |
| uapi::fuse_opcode_FUSE_READDIR |
| } |
| } |
| Self::Readlink => uapi::fuse_opcode_FUSE_READLINK, |
| Self::Release { flags, mode, .. } => { |
| if mode.is_dir() || flags.contains(OpenFlags::DIRECTORY) { |
| uapi::fuse_opcode_FUSE_RELEASEDIR |
| } else { |
| uapi::fuse_opcode_FUSE_RELEASE |
| } |
| } |
| Self::RemoveXAttr { .. } => uapi::fuse_opcode_FUSE_REMOVEXATTR, |
| Self::Seek(_) => uapi::fuse_opcode_FUSE_LSEEK, |
| Self::SetAttr(_) => uapi::fuse_opcode_FUSE_SETATTR, |
| Self::SetXAttr { .. } => uapi::fuse_opcode_FUSE_SETXATTR, |
| Self::Statfs => uapi::fuse_opcode_FUSE_STATFS, |
| Self::Symlink { .. } => uapi::fuse_opcode_FUSE_SYMLINK, |
| Self::Unlink { .. } => uapi::fuse_opcode_FUSE_UNLINK, |
| Self::Write { .. } => uapi::fuse_opcode_FUSE_WRITE, |
| } |
| } |
| |
| fn len(&self, configuration: &FuseConfiguration) -> usize { |
| #[derive(Debug, Default)] |
| struct CountingOutputBuffer { |
| written: usize, |
| } |
| |
| impl OutputBuffer for CountingOutputBuffer { |
| fn available(&self) -> usize { |
| usize::MAX |
| } |
| |
| fn bytes_written(&self) -> usize { |
| self.written |
| } |
| |
| fn zero(&mut self) -> Result<usize, Errno> { |
| panic!("Should not be called"); |
| } |
| |
| fn write_each( |
| &mut self, |
| _callback: &mut OutputBufferCallback<'_>, |
| ) -> Result<usize, Errno> { |
| panic!("Should not be called."); |
| } |
| |
| fn write_all(&mut self, buffer: &[u8]) -> Result<usize, Errno> { |
| self.written += buffer.len(); |
| Ok(buffer.len()) |
| } |
| } |
| |
| let mut counting_output_buffer = CountingOutputBuffer::default(); |
| self.serialize(&mut counting_output_buffer, configuration) |
| .expect("Serialization should not fail"); |
| counting_output_buffer.written |
| } |
| |
| fn has_response(&self) -> bool { |
| !matches!(self, Self::Interrupt { .. } | Self::Forget(_)) |
| } |
| |
| fn is_async(&self) -> bool { |
| matches!(self, Self::Init) |
| } |
| |
| fn to_response<T: FromBytes + AsBytes>(buffer: &[u8]) -> T { |
| let mut result = T::new_zeroed(); |
| let length_to_copy = std::cmp::min(buffer.len(), std::mem::size_of::<T>()); |
| result.as_bytes_mut()[..length_to_copy].copy_from_slice(&buffer[..length_to_copy]); |
| result |
| } |
| |
| fn parse_response(&self, buffer: Vec<u8>) -> Result<FuseResponse, Errno> { |
| debug_assert!(self.has_response()); |
| match self { |
| Self::Access { .. } => Ok(FuseResponse::Access(Ok(()))), |
| Self::GetAttr | Self::SetAttr(_) => { |
| Ok(FuseResponse::Attr(Self::to_response::<uapi::fuse_attr_out>(&buffer))) |
| } |
| Self::GetXAttr { getxattr_in, .. } | Self::ListXAttr(getxattr_in) => { |
| if getxattr_in.size == 0 { |
| if buffer.len() < std::mem::size_of::<uapi::fuse_getxattr_out>() { |
| return error!(EINVAL); |
| } |
| let getxattr_out = Self::to_response::<uapi::fuse_getxattr_out>(&buffer); |
| Ok(FuseResponse::GetXAttr(ValueOrSize::Size(getxattr_out.size as usize))) |
| } else { |
| Ok(FuseResponse::GetXAttr(buffer.into())) |
| } |
| } |
| Self::Init => Ok(FuseResponse::Init(Self::to_response::<uapi::fuse_init_out>(&buffer))), |
| Self::Lookup { .. } |
| | Self::Mkdir { .. } |
| | Self::Mknod { .. } |
| | Self::Link { .. } |
| | Self::Symlink { .. } => { |
| Ok(FuseResponse::Entry(Self::to_response::<uapi::fuse_entry_out>(&buffer))) |
| } |
| Self::Open { .. } => { |
| Ok(FuseResponse::Open(Self::to_response::<uapi::fuse_open_out>(&buffer))) |
| } |
| Self::Poll(_) => { |
| Ok(FuseResponse::Poll(Self::to_response::<uapi::fuse_poll_out>(&buffer))) |
| } |
| Self::Read(_) | Self::Readlink => Ok(FuseResponse::Read(buffer)), |
| Self::Readdir { use_readdirplus, .. } => { |
| let mut result = vec![]; |
| let mut slice = &buffer[..]; |
| while !slice.is_empty() { |
| // If using READDIRPLUS, the data starts with the entry. |
| let entry = if *use_readdirplus { |
| if slice.len() < std::mem::size_of::<uapi::fuse_entry_out>() { |
| return error!(EINVAL); |
| } |
| let entry = Self::to_response::<uapi::fuse_entry_out>(slice); |
| slice = &slice[std::mem::size_of::<uapi::fuse_entry_out>()..]; |
| Some(entry) |
| } else { |
| None |
| }; |
| // The next item is the dirent. |
| if slice.len() < std::mem::size_of::<uapi::fuse_dirent>() { |
| return error!(EINVAL); |
| } |
| let dirent = Self::to_response::<uapi::fuse_dirent>(slice); |
| // And it ends with the name. |
| slice = &slice[std::mem::size_of::<uapi::fuse_dirent>()..]; |
| let namelen = dirent.namelen as usize; |
| if slice.len() < namelen { |
| return error!(EINVAL); |
| } |
| let name: FsString = slice[..namelen].to_owned(); |
| result.push((dirent, name, entry)); |
| let skipped = round_up_to_increment(namelen, 8)?; |
| if slice.len() < skipped { |
| return error!(EINVAL); |
| } |
| slice = &slice[skipped..]; |
| } |
| Ok(FuseResponse::Readdir(result)) |
| } |
| Self::Flush(_) |
| | Self::Release { .. } |
| | Self::RemoveXAttr { .. } |
| | Self::SetXAttr { .. } |
| | Self::Unlink { .. } => Ok(FuseResponse::None), |
| Self::Statfs => { |
| Ok(FuseResponse::Statfs(Self::to_response::<uapi::fuse_statfs_out>(&buffer))) |
| } |
| Self::Seek(_) => { |
| Ok(FuseResponse::Seek(Self::to_response::<uapi::fuse_lseek_out>(&buffer))) |
| } |
| Self::Write { .. } => { |
| Ok(FuseResponse::Write(Self::to_response::<uapi::fuse_write_out>(&buffer))) |
| } |
| Self::Interrupt { .. } | Self::Forget(_) => { |
| panic!("Response for operation without one"); |
| } |
| } |
| } |
| |
| /// Handles an error from the userspace daemon. |
| /// |
| /// Given the `errno` returned by the userspace daemon, returns the response the caller should |
| /// see. This can also update the `OperationState` to allow shortcircuit on future requests. |
| fn handle_error( |
| &self, |
| state: &mut OperationsState, |
| errno: Errno, |
| ) -> Result<FuseResponse, Errno> { |
| match self { |
| Self::Access { .. } if errno == ENOSYS => { |
| state.insert(self.opcode(), Ok(FuseResponse::Access(Errno::fail(ENOSYS)))); |
| Ok(FuseResponse::Access(Errno::fail(ENOSYS))) |
| } |
| Self::Flush(_) if errno == ENOSYS => { |
| state.insert(self.opcode(), Ok(FuseResponse::None)); |
| Ok(FuseResponse::None) |
| } |
| Self::Seek(_) if errno == ENOSYS => { |
| state.insert(self.opcode(), Err(errno.clone())); |
| Err(errno) |
| } |
| Self::Poll(_) if errno == ENOSYS => { |
| let response = FuseResponse::Poll(uapi::fuse_poll_out { |
| revents: (FdEvents::POLLIN | FdEvents::POLLOUT).bits(), |
| padding: 0, |
| }); |
| state.insert(self.opcode(), Ok(response.clone())); |
| Ok(response) |
| } |
| _ => Err(errno), |
| } |
| } |
| } |