| // 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 std::fmt; |
| use std::sync::Arc; |
| |
| use crate::fs::*; |
| use crate::lock::Mutex; |
| use crate::logging::{impossible_error, not_implemented}; |
| use crate::mm::{DesiredAddress, MappedVmo, MappingOptions}; |
| use crate::syscalls::SyscallResult; |
| use crate::task::*; |
| use crate::types::as_any::*; |
| use crate::types::*; |
| |
| pub const MAX_LFS_FILESIZE: usize = 0x7fffffffffffffff; |
| |
| pub enum SeekOrigin { |
| SET, |
| CUR, |
| END, |
| } |
| |
| impl SeekOrigin { |
| pub fn from_raw(whence: u32) -> Result<SeekOrigin, Errno> { |
| match whence { |
| SEEK_SET => Ok(SeekOrigin::SET), |
| SEEK_CUR => Ok(SeekOrigin::CUR), |
| SEEK_END => Ok(SeekOrigin::END), |
| _ => error!(EINVAL), |
| } |
| } |
| } |
| |
| /// Corresponds to struct file_operations in Linux, plus any filesystem-specific data. |
| pub trait FileOps: Send + Sync + AsAny { |
| /// Called when the FileObject is closed. |
| fn close(&self, _file: &FileObject) {} |
| |
| /// Read from the file without an offset. If your file is seekable, consider implementing this |
| /// with [`fileops_impl_seekable`]. |
| fn read( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno>; |
| /// Read from the file at an offset. If your file is seekable, consider implementing this with |
| /// [`fileops_impl_nonseekable`]. |
| fn read_at( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno>; |
| /// Write to the file without an offset. If your file is seekable, consider implementing this |
| /// with [`fileops_impl_seekable`]. |
| fn write( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno>; |
| /// Write to the file at a offset. If your file is nonseekable, consider implementing this with |
| /// [`fileops_impl_nonseekable`]. |
| fn write_at( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno>; |
| |
| /// Adjust the seek offset if the file is seekable. |
| fn seek( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: off_t, |
| whence: SeekOrigin, |
| ) -> Result<off_t, Errno>; |
| |
| /// Returns a VMO representing this file. At least the requested protection flags must |
| /// be set on the VMO. Reading or writing the VMO must read or write the file. If this is not |
| /// possible given the requested protection, an error must be returned. |
| /// The `length` is a hint for the desired size of the VMO. The returned VMO may be larger or |
| /// smaller than the requested length. |
| /// This method is typically called by [`Self::mmap`]. |
| fn get_vmo( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _length: Option<usize>, |
| _prot: zx::VmarFlags, |
| ) -> Result<zx::Vmo, Errno> { |
| error!(ENODEV) |
| } |
| |
| /// Responds to an mmap call. The default implementation calls [`Self::get_vmo`] to get a VMO |
| /// and then maps it with [`crate::mm::MemoryManager::map`]. |
| /// Only implement this trait method if your file needs to control mapping, or record where |
| /// a VMO gets mapped. |
| fn mmap( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| vmo_offset: u64, |
| length: usize, |
| flags: zx::VmarFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<MappedVmo, Errno> { |
| // Sanitize the protection flags to only include PERM_READ, PERM_WRITE, and PERM_EXECUTE. |
| let zx_prot_flags = flags |
| & (zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE | zx::VmarFlags::PERM_EXECUTE); |
| |
| let vmo = Arc::new(if options.contains(MappingOptions::SHARED) { |
| self.get_vmo(file, current_task, Some(length), zx_prot_flags)? |
| } else { |
| // TODO(tbodt): Use VMO_FLAG_PRIVATE to have the filesystem server do the clone for us. |
| let vmo = self.get_vmo( |
| file, |
| current_task, |
| Some(length), |
| zx_prot_flags - zx::VmarFlags::PERM_WRITE, |
| )?; |
| let mut clone_flags = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE; |
| if !zx_prot_flags.contains(zx::VmarFlags::PERM_WRITE) { |
| clone_flags |= zx::VmoChildOptions::NO_WRITE; |
| } |
| vmo.create_child(clone_flags, 0, vmo.get_size().map_err(impossible_error)?) |
| .map_err(impossible_error)? |
| }); |
| let addr = current_task.mm.map( |
| addr, |
| vmo.clone(), |
| vmo_offset, |
| length, |
| flags, |
| options, |
| Some(filename), |
| )?; |
| Ok(MappedVmo::new(vmo, addr)) |
| } |
| |
| fn readdir( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Establish a one-shot, asynchronous wait for the given FdEvents for the given file and task. |
| /// If the events are already active at the time of calling, handler will be called on immediately |
| /// on the next wait. |
| fn wait_async( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _waiter: &Arc<Waiter>, |
| _events: FdEvents, |
| _handler: EventHandler, |
| ) -> WaitKey; |
| |
| /// Cancel a wait set up by wait_async. |
| /// Returns true if the wait has not been activated and has been cancelled. |
| fn cancel_wait(&self, _current_task: &CurrentTask, _waiter: &Arc<Waiter>, _key: WaitKey); |
| |
| fn query_events(&self, current_task: &CurrentTask) -> FdEvents; |
| |
| fn ioctl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| request: u32, |
| _user_addr: UserAddress, |
| ) -> Result<SyscallResult, Errno> { |
| default_ioctl(request) |
| } |
| |
| fn fcntl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _cmd: u32, |
| _arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| error!(EINVAL) |
| } |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for non-seekable files. |
| /// You must implement [`FileOps::read`] and [`FileOps::write`]. |
| macro_rules! fileops_impl_nonseekable { |
| () => { |
| fn read_at( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: usize, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(ESPIPE) |
| } |
| fn write_at( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: usize, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(ESPIPE) |
| } |
| fn seek( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: crate::types::off_t, |
| _whence: crate::fs::SeekOrigin, |
| ) -> Result<crate::types::off_t, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(ESPIPE) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for seekable files. |
| /// You must implement [`FileOps::read_at`] and [`FileOps::write_at`]. |
| macro_rules! fileops_impl_seekable { |
| () => { |
| fn read( |
| &self, |
| file: &crate::fs::FileObject, |
| current_task: &crate::task::CurrentTask, |
| data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| let mut offset = file.offset.lock(); |
| let size = self.read_at(file, current_task, *offset as usize, data)?; |
| *offset += size as crate::types::off_t; |
| Ok(size) |
| } |
| fn write( |
| &self, |
| file: &crate::fs::FileObject, |
| current_task: &crate::task::CurrentTask, |
| data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| let mut offset = file.offset.lock(); |
| if file.flags().contains(OpenFlags::APPEND) { |
| *offset = file.node().info().size as crate::types::off_t; |
| } |
| let size = self.write_at(file, current_task, *offset as usize, data)?; |
| *offset += size as crate::types::off_t; |
| Ok(size) |
| } |
| fn seek( |
| &self, |
| file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| offset: crate::types::off_t, |
| whence: crate::fs::SeekOrigin, |
| ) -> Result<crate::types::off_t, crate::types::Errno> { |
| use crate::types::errno::*; |
| let mut current_offset = file.offset.lock(); |
| let new_offset = match whence { |
| crate::fs::SeekOrigin::SET => Some(offset), |
| crate::fs::SeekOrigin::CUR => (*current_offset).checked_add(offset), |
| crate::fs::SeekOrigin::END => { |
| let stat = file.node().stat()?; |
| offset.checked_add(stat.st_size as crate::types::off_t) |
| } |
| } |
| .ok_or(errno!(EINVAL))?; |
| |
| if new_offset < 0 { |
| return error!(EINVAL); |
| } |
| |
| *current_offset = new_offset; |
| Ok(*current_offset) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for files that ignore |
| /// seeking operations and always read/write at offset 0. |
| /// You must implement [`FileOps::read_at`] and [`FileOps::write_at`]. |
| macro_rules! fileops_impl_seekless { |
| () => { |
| fn read( |
| &self, |
| file: &crate::fs::FileObject, |
| current_task: &crate::task::CurrentTask, |
| data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| self.read_at(file, current_task, 0, data) |
| } |
| fn write( |
| &self, |
| file: &crate::fs::FileObject, |
| current_task: &crate::task::CurrentTask, |
| data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| self.write_at(file, current_task, 0, data) |
| } |
| fn seek( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: crate::types::off_t, |
| _whence: crate::fs::SeekOrigin, |
| ) -> Result<crate::types::off_t, crate::types::Errno> { |
| Ok(0) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for directories. You must implement |
| /// [`FileOps::seek`] and [`FileOps::readdir`]. |
| macro_rules! fileops_impl_directory { |
| () => { |
| crate::fs::fileops_impl_nonblocking!(); |
| |
| fn read( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(EISDIR) |
| } |
| |
| fn read_at( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: usize, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(EISDIR) |
| } |
| |
| fn write( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(EISDIR) |
| } |
| |
| fn write_at( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _offset: usize, |
| _data: &[crate::types::UserBuffer], |
| ) -> Result<usize, crate::types::Errno> { |
| use crate::types::errno::*; |
| error!(EISDIR) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for files that never block |
| /// while reading/writing. The [`FileOps::wait_async`] and [`FileOps::query_events`] methods are |
| /// implemented for you. |
| macro_rules! fileops_impl_nonblocking { |
| () => { |
| fn wait_async( |
| &self, |
| _file: &crate::fs::FileObject, |
| _current_task: &crate::task::CurrentTask, |
| _waiter: &std::sync::Arc<crate::task::Waiter>, |
| _events: crate::fs::FdEvents, |
| _handler: crate::task::EventHandler, |
| ) -> crate::task::WaitKey { |
| crate::task::WaitKey::empty() |
| } |
| |
| fn cancel_wait( |
| &self, |
| _current_task: &CurrentTask, |
| _waiter: &std::sync::Arc<crate::task::Waiter>, |
| _key: crate::task::WaitKey, |
| ) { |
| } |
| |
| fn query_events(&self, _current_task: &crate::task::CurrentTask) -> crate::fs::FdEvents { |
| crate::fs::FdEvents::POLLIN | crate::fs::FdEvents::POLLOUT |
| } |
| }; |
| } |
| |
| // Public re-export of macros allows them to be used like regular rust items. |
| |
| pub(crate) use fileops_impl_directory; |
| pub(crate) use fileops_impl_nonblocking; |
| pub(crate) use fileops_impl_nonseekable; |
| pub(crate) use fileops_impl_seekable; |
| pub(crate) use fileops_impl_seekless; |
| |
| pub fn default_ioctl(request: u32) -> Result<SyscallResult, Errno> { |
| not_implemented!("ioctl: request=0x{:x}", request); |
| error!(ENOTTY) |
| } |
| |
| pub struct OPathOps {} |
| |
| impl OPathOps { |
| pub fn new() -> OPathOps { |
| OPathOps {} |
| } |
| } |
| |
| impl FileOps for OPathOps { |
| fileops_impl_nonblocking!(); |
| |
| fn read( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn read_at( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn write( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn write_at( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn seek( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: off_t, |
| _whence: SeekOrigin, |
| ) -> Result<off_t, Errno> { |
| error!(EBADF) |
| } |
| fn get_vmo( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _length: Option<usize>, |
| _prot: zx::VmarFlags, |
| ) -> Result<zx::Vmo, Errno> { |
| error!(EBADF) |
| } |
| fn readdir( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| error!(EBADF) |
| } |
| |
| fn ioctl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _request: u32, |
| _user_addr: UserAddress, |
| ) -> Result<SyscallResult, Errno> { |
| error!(EBADF) |
| } |
| |
| fn fcntl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _cmd: u32, |
| _arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| // Note: this can be a valid operation for files opened with O_PATH. |
| error!(EINVAL) |
| } |
| } |
| |
| /// A session with a file object. |
| /// |
| /// Each time a client calls open(), we create a new FileObject from the |
| /// underlying FsNode that receives the open(). This object contains the state |
| /// that is specific to this sessions whereas the underlying FsNode contains |
| /// the state that is shared between all the sessions. |
| pub struct FileObject { |
| ops: Box<dyn FileOps>, |
| |
| /// The NamespaceNode associated with this FileObject. |
| /// |
| /// Represents the name the process used to open this file. |
| pub name: NamespaceNode, |
| |
| pub fs: FileSystemHandle, |
| |
| pub offset: Mutex<off_t>, |
| |
| flags: Mutex<OpenFlags>, |
| |
| async_owner: Mutex<pid_t>, |
| } |
| |
| pub type FileHandle = Arc<FileObject>; |
| |
| impl FileObject { |
| /// Create a FileObject that is not mounted in a namespace. |
| /// |
| /// The returned FileObject does not have a name. |
| pub fn new_anonymous( |
| ops: Box<dyn FileOps>, |
| node: FsNodeHandle, |
| flags: OpenFlags, |
| ) -> FileHandle { |
| Self::new(ops, NamespaceNode::new_anonymous(node), flags) |
| } |
| |
| /// Create a FileObject with an associated NamespaceNode. |
| /// |
| /// This function is not typically called directly. Instead, consider |
| /// calling NamespaceNode::open. |
| pub fn new(ops: Box<dyn FileOps>, name: NamespaceNode, flags: OpenFlags) -> FileHandle { |
| let fs = name.entry.node.fs(); |
| Arc::new(Self { |
| name, |
| fs, |
| ops, |
| offset: Mutex::new(0), |
| flags: Mutex::new(flags), |
| async_owner: Mutex::new(0), |
| }) |
| } |
| |
| /// The FsNode from which this FileObject was created. |
| pub fn node(&self) -> &FsNodeHandle { |
| &self.name.entry.node |
| } |
| |
| pub fn can_read(&self) -> bool { |
| // TODO: Consider caching the access mode outside of this lock |
| // because it cannot change. |
| self.flags.lock().can_read() |
| } |
| |
| pub fn can_write(&self) -> bool { |
| // TODO: Consider caching the access mode outside of this lock |
| // because it cannot change. |
| self.flags.lock().can_write() |
| } |
| |
| fn ops(&self) -> &dyn FileOps { |
| &*self.ops |
| } |
| |
| /// Returns the `FileObject`'s `FileOps` as a `&T`, or `None` if the downcast fails. |
| /// |
| /// This is useful for syscalls that only operate on a certain type of file. |
| pub fn downcast_file<T>(&self) -> Option<&T> |
| where |
| T: 'static, |
| { |
| self.ops().as_any().downcast_ref::<T>() |
| } |
| |
| pub fn blocking_op<T, Op>( |
| &self, |
| current_task: &CurrentTask, |
| mut op: Op, |
| events: FdEvents, |
| deadline: Option<zx::Time>, |
| ) -> Result<T, Errno> |
| where |
| Op: FnMut() -> Result<T, Errno>, |
| { |
| match op() { |
| Err(errno) if errno == EAGAIN && !self.flags().contains(OpenFlags::NONBLOCK) => {} |
| result => return result, |
| } |
| |
| let waiter = Waiter::new(); |
| loop { |
| self.ops().wait_async(self, current_task, &waiter, events, WaitCallback::none()); |
| match op() { |
| Err(errno) if errno == EAGAIN => waiter |
| .wait_until(current_task, deadline.unwrap_or(zx::Time::INFINITE)) |
| .map_err(|e| if e == ETIMEDOUT { errno!(EAGAIN) } else { e })?, |
| result => return result, |
| } |
| } |
| } |
| |
| pub fn read(&self, current_task: &CurrentTask, data: &[UserBuffer]) -> Result<usize, Errno> { |
| if !self.can_read() { |
| return error!(EBADF); |
| } |
| self.blocking_op( |
| current_task, |
| || self.ops().read(self, current_task, data), |
| FdEvents::POLLIN | FdEvents::POLLHUP, |
| None, |
| ) |
| } |
| |
| pub fn read_at( |
| &self, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| if !self.can_read() { |
| return error!(EBADF); |
| } |
| self.blocking_op( |
| current_task, |
| || self.ops().read_at(self, current_task, offset, data), |
| FdEvents::POLLIN | FdEvents::POLLHUP, |
| None, |
| ) |
| } |
| |
| pub fn write(&self, current_task: &CurrentTask, data: &[UserBuffer]) -> Result<usize, Errno> { |
| if !self.can_write() { |
| return error!(EBADF); |
| } |
| self.blocking_op( |
| current_task, |
| || { |
| if self.flags().contains(OpenFlags::APPEND) { |
| let _guard = self.node().append_lock.write(); |
| self.ops().write(self, current_task, data) |
| } else { |
| let _guard = self.node().append_lock.read(); |
| self.ops().write(self, current_task, data) |
| } |
| }, |
| FdEvents::POLLOUT | FdEvents::POLLHUP, |
| None, |
| ) |
| } |
| |
| pub fn write_at( |
| &self, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &[UserBuffer], |
| ) -> Result<usize, Errno> { |
| if !self.can_write() { |
| return error!(EBADF); |
| } |
| self.blocking_op( |
| current_task, |
| || { |
| let _guard = self.node().append_lock.read(); |
| self.ops().write_at(self, current_task, offset, data) |
| }, |
| FdEvents::POLLOUT | FdEvents::POLLHUP, |
| None, |
| ) |
| } |
| |
| pub fn seek( |
| &self, |
| current_task: &CurrentTask, |
| offset: off_t, |
| whence: SeekOrigin, |
| ) -> Result<off_t, Errno> { |
| self.ops().seek(self, current_task, offset, whence) |
| } |
| |
| pub fn get_vmo( |
| &self, |
| current_task: &CurrentTask, |
| length: Option<usize>, |
| prot: zx::VmarFlags, |
| ) -> Result<zx::Vmo, Errno> { |
| if prot.contains(zx::VmarFlags::PERM_READ) && !self.can_read() { |
| return error!(EACCES); |
| } |
| if prot.contains(zx::VmarFlags::PERM_WRITE) && !self.can_write() { |
| return error!(EACCES); |
| } |
| // TODO: Check for PERM_EXECUTE by checking whether the filesystem is mounted as noexec. |
| self.ops().get_vmo(self, current_task, length, prot) |
| } |
| |
| pub fn mmap( |
| &self, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| vmo_offset: u64, |
| length: usize, |
| flags: zx::VmarFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<MappedVmo, Errno> { |
| if flags.contains(zx::VmarFlags::PERM_READ) && !self.can_read() { |
| return error!(EACCES); |
| } |
| if flags.contains(zx::VmarFlags::PERM_WRITE) |
| && !self.can_write() |
| && options.contains(MappingOptions::SHARED) |
| { |
| return error!(EACCES); |
| } |
| // TODO: Check for PERM_EXECUTE by checking whether the filesystem is mounted as noexec. |
| self.ops().mmap(self, current_task, addr, vmo_offset, length, flags, options, filename) |
| } |
| |
| pub fn readdir( |
| &self, |
| current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| match self.ops().readdir(self, current_task, sink) { |
| // The ENOSPC we catch here is generated by DirentSink::add. We |
| // return the error to the caller only if we didn't have space for |
| // the first directory entry. |
| // |
| // We use ENOSPC rather than EINVAL to signal this condition |
| // because EINVAL is a very generic error. We only want to perform |
| // this transformation in exactly the case where there was not |
| // sufficient space in the DirentSink. |
| Err(errno) if errno == ENOSPC && sink.actual() > 0 => Ok(()), |
| Err(errno) if errno == ENOSPC => Err(errno), |
| result => result, |
| } |
| } |
| |
| pub fn ioctl( |
| &self, |
| current_task: &CurrentTask, |
| request: u32, |
| user_addr: UserAddress, |
| ) -> Result<SyscallResult, Errno> { |
| self.ops().ioctl(self, current_task, request, user_addr) |
| } |
| |
| pub fn fcntl( |
| &self, |
| current_task: &CurrentTask, |
| cmd: u32, |
| arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| self.ops().fcntl(self, current_task, cmd, arg) |
| } |
| |
| pub fn update_file_flags(&self, value: OpenFlags, mask: OpenFlags) { |
| let mask_bits = mask.bits(); |
| let mut flags = self.flags.lock(); |
| let bits = (flags.bits() & !mask_bits) | (value.bits() & mask_bits); |
| *flags = OpenFlags::from_bits_truncate(bits); |
| } |
| |
| pub fn flags(&self) -> OpenFlags { |
| *self.flags.lock() |
| } |
| |
| /// Get the async owner of this file. |
| /// |
| /// See fcntl(F_GETOWN) |
| pub fn get_async_owner(&self) -> pid_t { |
| *self.async_owner.lock() |
| } |
| |
| /// Set the async owner of this file. |
| /// |
| /// See fcntl(F_SETOWN) |
| pub fn set_async_owner(&self, owner: pid_t) { |
| *self.async_owner.lock() = owner; |
| } |
| |
| /// Wait on the specified events and call the EventHandler when ready |
| pub fn wait_async( |
| &self, |
| current_task: &CurrentTask, |
| waiter: &Arc<Waiter>, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> WaitKey { |
| self.ops().wait_async(self, current_task, waiter, events, handler) |
| } |
| |
| // Cancel a wait set up with wait_async |
| pub fn cancel_wait(&self, current_task: &CurrentTask, waiter: &Arc<Waiter>, key: WaitKey) { |
| self.ops().cancel_wait(current_task, waiter, key); |
| } |
| |
| // Return the events currently active |
| pub fn query_events(&self, current_task: &CurrentTask) -> FdEvents { |
| self.ops().query_events(current_task) |
| } |
| |
| // |
| /// Updates the file's seek offset without an upper bound on the resulting offset. |
| /// |
| /// This is useful for files without a defined size. |
| /// |
| /// Errors if `whence` is invalid, or the calculated offset is invalid. |
| /// |
| /// - `offset`: The target offset from `whence`. |
| /// - `whence`: The location from which to compute the updated offset. |
| pub fn unbounded_seek(&self, offset: off_t, whence: SeekOrigin) -> Result<off_t, Errno> { |
| let mut current_offset = self.offset.lock(); |
| let new_offset = match whence { |
| SeekOrigin::SET => Some(offset), |
| SeekOrigin::CUR => (*current_offset).checked_add(offset), |
| SeekOrigin::END => Some(MAX_LFS_FILESIZE as i64), |
| } |
| .ok_or(errno!(EINVAL))?; |
| |
| if new_offset < 0 { |
| return error!(EINVAL); |
| } |
| |
| *current_offset = new_offset; |
| Ok(*current_offset) |
| } |
| } |
| |
| impl Drop for FileObject { |
| fn drop(&mut self) { |
| self.ops().close(self); |
| } |
| } |
| |
| impl fmt::Debug for FileObject { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("FileObject") |
| .field("name", &self.name) |
| .field("offset", &self.offset) |
| .finish() |
| } |
| } |