| // Cmpyright 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 crate::mm::memory::MemoryObject; |
| use crate::mm::{DesiredAddress, MappingName, MappingOptions, MemoryAccessorExt, ProtectionFlags}; |
| use crate::power::OnWakeOps; |
| use crate::security; |
| use crate::task::{ |
| CurrentTask, CurrentTaskAndLocked, EventHandler, Task, ThreadGroupKey, WaitCallback, |
| WaitCanceler, Waiter, register_delayed_release, |
| }; |
| use crate::vfs::buffers::{InputBuffer, OutputBuffer}; |
| use crate::vfs::file_server::serve_file; |
| use crate::vfs::fsverity::{ |
| FsVerityState, {self}, |
| }; |
| use crate::vfs::{ |
| ActiveNamespaceNode, DirentSink, EpollFileObject, EpollKey, FallocMode, FdTableId, |
| FileSystemHandle, FileWriteGuardMode, FsNodeHandle, FsString, NamespaceNode, RecordLockCommand, |
| RecordLockOwner, |
| }; |
| use starnix_crypt::EncryptionKeyId; |
| use starnix_lifecycle::{ObjectReleaser, ReleaserAction}; |
| use starnix_types::ownership::ReleaseGuard; |
| use starnix_uapi::mount_flags::MountFlags; |
| use starnix_uapi::user_address::ArchSpecific; |
| |
| use fidl::HandleBased; |
| use linux_uapi::{FSCRYPT_MODE_AES_256_CTS, FSCRYPT_MODE_AES_256_XTS}; |
| use starnix_logging::{ |
| CATEGORY_STARNIX_MM, impossible_error, log_error, trace_duration, track_stub, |
| }; |
| use starnix_sync::{ |
| BeforeFsNodeAppend, FileOpsCore, LockBefore, LockEqualOrBefore, Locked, Mutex, Unlocked, |
| }; |
| use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult}; |
| use starnix_types::math::round_up_to_system_page_size; |
| use starnix_types::ownership::Releasable; |
| use starnix_uapi::arc_key::WeakKey; |
| use starnix_uapi::as_any::AsAny; |
| use starnix_uapi::auth::{CAP_FOWNER, CAP_SYS_RAWIO}; |
| use starnix_uapi::errors::{EAGAIN, ETIMEDOUT, Errno}; |
| use starnix_uapi::file_lease::FileLeaseType; |
| use starnix_uapi::file_mode::Access; |
| use starnix_uapi::inotify_mask::InotifyMask; |
| use starnix_uapi::open_flags::OpenFlags; |
| use starnix_uapi::seal_flags::SealFlags; |
| use starnix_uapi::user_address::{UserAddress, UserRef}; |
| use starnix_uapi::vfs::FdEvents; |
| use starnix_uapi::{ |
| FIBMAP, FIGETBSZ, FIONBIO, FIONREAD, FIOQSIZE, FS_CASEFOLD_FL, FS_IOC_ADD_ENCRYPTION_KEY, |
| FS_IOC_ENABLE_VERITY, FS_IOC_FSGETXATTR, FS_IOC_FSSETXATTR, FS_IOC_MEASURE_VERITY, |
| FS_IOC_READ_VERITY_METADATA, FS_IOC_REMOVE_ENCRYPTION_KEY, FS_IOC_SET_ENCRYPTION_POLICY, |
| FS_VERITY_FL, FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER, FSCRYPT_POLICY_V2, SEEK_CUR, SEEK_DATA, |
| SEEK_END, SEEK_HOLE, SEEK_SET, TCGETS, errno, error, fscrypt_add_key_arg, fscrypt_identifier, |
| fsxattr, off_t, pid_t, uapi, |
| }; |
| use std::collections::HashMap; |
| use std::fmt; |
| use std::ops::Deref; |
| use std::sync::{Arc, Weak}; |
| |
| pub const MAX_LFS_FILESIZE: usize = 0x7fff_ffff_ffff_ffff; |
| |
| pub fn checked_add_offset_and_length(offset: usize, length: usize) -> Result<usize, Errno> { |
| let end = offset.checked_add(length).ok_or_else(|| errno!(EINVAL))?; |
| if end > MAX_LFS_FILESIZE { |
| return error!(EINVAL); |
| } |
| Ok(end) |
| } |
| |
| #[derive(Debug)] |
| pub enum SeekTarget { |
| /// Seek to the given offset relative to the start of the file. |
| Set(off_t), |
| /// Seek to the given offset relative to the current position. |
| Cur(off_t), |
| /// Seek to the given offset relative to the end of the file. |
| End(off_t), |
| /// Seek for the first data after the given offset, |
| Data(off_t), |
| /// Seek for the first hole after the given offset, |
| Hole(off_t), |
| } |
| |
| impl SeekTarget { |
| pub fn from_raw(whence: u32, offset: off_t) -> Result<SeekTarget, Errno> { |
| match whence { |
| SEEK_SET => Ok(SeekTarget::Set(offset)), |
| SEEK_CUR => Ok(SeekTarget::Cur(offset)), |
| SEEK_END => Ok(SeekTarget::End(offset)), |
| SEEK_DATA => Ok(SeekTarget::Data(offset)), |
| SEEK_HOLE => Ok(SeekTarget::Hole(offset)), |
| _ => error!(EINVAL), |
| } |
| } |
| |
| pub fn whence(&self) -> u32 { |
| match self { |
| Self::Set(_) => SEEK_SET, |
| Self::Cur(_) => SEEK_CUR, |
| Self::End(_) => SEEK_END, |
| Self::Data(_) => SEEK_DATA, |
| Self::Hole(_) => SEEK_HOLE, |
| } |
| } |
| |
| pub fn offset(&self) -> off_t { |
| match self { |
| Self::Set(off) |
| | Self::Cur(off) |
| | Self::End(off) |
| | Self::Data(off) |
| | Self::Hole(off) => *off, |
| } |
| } |
| } |
| |
| /// Corresponds to struct file_operations in Linux, plus any filesystem-specific data. |
| pub trait FileOps: Send + Sync + AsAny + 'static { |
| /// Called when the FileObject is opened/created |
| fn open( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| ) -> Result<(), Errno> { |
| Ok(()) |
| } |
| |
| /// Called when the FileObject is destroyed. |
| fn close( |
| self: Box<Self>, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObjectState, |
| _current_task: &CurrentTask, |
| ) { |
| } |
| |
| /// Called every time close() is called on this file, even if the file is not ready to be |
| /// released. |
| fn flush( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| ) { |
| } |
| |
| /// Returns whether the file has meaningful seek offsets. Returning `false` is only |
| /// optimization and will makes `FileObject` never hold the offset lock when calling `read` and |
| /// `write`. |
| fn has_persistent_offsets(&self) -> bool { |
| self.is_seekable() |
| } |
| |
| /// Returns whether the file is seekable. |
| fn is_seekable(&self) -> bool; |
| |
| /// Returns true if `write()` operations on the file will update the seek offset. |
| fn writes_update_seek_offset(&self) -> bool { |
| self.has_persistent_offsets() |
| } |
| |
| /// Read from the file at an offset. If the file does not have persistent offsets (either |
| /// directly, or because it is not seekable), offset will be 0 and can be ignored. |
| /// Returns the number of bytes read. |
| fn read( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno>; |
| |
| /// Write to the file with an offset. If the file does not have persistent offsets (either |
| /// directly, or because it is not seekable), offset will be 0 and can be ignored. |
| /// Returns the number of bytes written. |
| fn write( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno>; |
| |
| /// Adjust the `current_offset` if the file is seekable. |
| fn seek( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| current_offset: off_t, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno>; |
| |
| /// Syncs cached state associated with the file descriptor to persistent storage. |
| /// |
| /// The method blocks until the synchronization is complete. |
| fn sync(&self, file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno>; |
| |
| /// Syncs cached data, and only enough metadata to retrieve said data, to persistent storage. |
| /// |
| /// The method blocks until the synchronization is complete. |
| fn data_sync(&self, file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> { |
| // TODO(https://fxbug.dev/297305634) make a default macro once data can be done separately |
| self.sync(file, current_task) |
| } |
| |
| /// 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_memory( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _length: Option<usize>, |
| _prot: ProtectionFlags, |
| ) -> Result<Arc<MemoryObject>, Errno> { |
| error!(ENODEV) |
| } |
| |
| /// Responds to an mmap call. The default implementation calls [`Self::get_memory`] 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, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| memory_offset: u64, |
| length: usize, |
| prot_flags: ProtectionFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<UserAddress, Errno> { |
| trace_duration!(CATEGORY_STARNIX_MM, "FileOpsDefaultMmap"); |
| let min_memory_size = (memory_offset as usize) |
| .checked_add(round_up_to_system_page_size(length)?) |
| .ok_or_else(|| errno!(EINVAL))?; |
| let mut memory = if options.contains(MappingOptions::SHARED) { |
| trace_duration!(CATEGORY_STARNIX_MM, "GetSharedVmo"); |
| self.get_memory(locked, file, current_task, Some(min_memory_size), prot_flags)? |
| } else { |
| trace_duration!(CATEGORY_STARNIX_MM, "GetPrivateVmo"); |
| // TODO(tbodt): Use PRIVATE_CLONE to have the filesystem server do the clone for us. |
| let base_prot_flags = (prot_flags | ProtectionFlags::READ) - ProtectionFlags::WRITE; |
| let memory = self.get_memory( |
| locked, |
| file, |
| current_task, |
| Some(min_memory_size), |
| base_prot_flags, |
| )?; |
| let mut clone_flags = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE; |
| if !prot_flags.contains(ProtectionFlags::WRITE) { |
| clone_flags |= zx::VmoChildOptions::NO_WRITE; |
| } |
| trace_duration!(CATEGORY_STARNIX_MM, "CreatePrivateChildVmo"); |
| Arc::new( |
| memory.create_child(clone_flags, 0, memory.get_size()).map_err(impossible_error)?, |
| ) |
| }; |
| |
| // Write guard is necessary only for shared mappings. Note that this doesn't depend on |
| // `prot_flags` since these can be changed later with `mprotect()`. |
| let file_write_guard = if options.contains(MappingOptions::SHARED) && file.can_write() { |
| let node = &file.name.entry.node; |
| let state = node.write_guard_state.lock(); |
| |
| // `F_SEAL_FUTURE_WRITE` should allow `mmap(PROT_READ)`, but block |
| // `mprotect(PROT_WRITE)`. This is different from `F_SEAL_WRITE`, which blocks |
| // `mmap(PROT_READ)`. To handle this case correctly remove `WRITE` right from the |
| // VMO handle to ensure `mprotect(PROT_WRITE)` fails. |
| let seals = state.get_seals().unwrap_or(SealFlags::empty()); |
| if seals.contains(SealFlags::FUTURE_WRITE) |
| && !seals.contains(SealFlags::WRITE) |
| && !prot_flags.contains(ProtectionFlags::WRITE) |
| { |
| let mut new_rights = zx::Rights::VMO_DEFAULT - zx::Rights::WRITE; |
| if prot_flags.contains(ProtectionFlags::EXEC) { |
| new_rights |= zx::Rights::EXECUTE; |
| } |
| memory = Arc::new(memory.duplicate_handle(new_rights).map_err(impossible_error)?); |
| |
| None |
| } else { |
| Some(FileWriteGuardMode::WriteMapping) |
| } |
| } else { |
| None |
| }; |
| |
| current_task.mm()?.map_memory( |
| addr, |
| memory, |
| memory_offset, |
| length, |
| prot_flags, |
| file.max_access_for_memory_mapping(), |
| options, |
| MappingName::File(filename.into_mapping(file_write_guard)?), |
| ) |
| } |
| |
| /// Respond to a `getdents` or `getdents64` calls. |
| /// |
| /// The `file.offset` lock will be held while entering this method. The implementation must look |
| /// at `sink.offset()` to read the current offset into the file. |
| fn readdir( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| error!(ENOTDIR) |
| } |
| |
| /// Establish a one-shot, edge-triggered, asynchronous wait for the given FdEvents for the |
| /// given file and task. Returns `None` if this file does not support blocking waits. |
| /// |
| /// Active events are not considered. This is similar to the semantics of the |
| /// ZX_WAIT_ASYNC_EDGE flag on zx_wait_async. To avoid missing events, the caller must call |
| /// query_events after calling this. |
| /// |
| /// If your file does not support blocking waits, leave this as the default implementation. |
| fn wait_async( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _waiter: &Waiter, |
| _events: FdEvents, |
| _handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| None |
| } |
| |
| /// The events currently active on this file. |
| /// |
| /// If this function returns `POLLIN` or `POLLOUT`, then FileObject will |
| /// add `POLLRDNORM` and `POLLWRNORM`, respective, which are equivalent in |
| /// the Linux UAPI. |
| /// |
| /// See https://linux.die.net/man/2/poll |
| fn query_events( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> { |
| Ok(FdEvents::POLLIN | FdEvents::POLLOUT) |
| } |
| |
| fn ioctl( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| default_ioctl(file, locked, current_task, request, arg) |
| } |
| |
| fn fcntl( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| cmd: u32, |
| _arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| default_fcntl(cmd) |
| } |
| |
| /// Return a handle that allows access to this file descritor through the zxio protocols. |
| /// |
| /// If None is returned, the file will act as if it was a fd to `/dev/null`. |
| fn to_handle( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<Option<zx::NullableHandle>, Errno> { |
| serve_file(current_task, file, current_task.full_current_creds()) |
| .map(|c| Some(c.0.into_handle().into())) |
| } |
| |
| /// Returns the associated pid_t. |
| /// |
| /// Used by pidfd and `/proc/<pid>`. Unlikely to be used by other files. |
| fn as_thread_group_key(&self, _file: &FileObject) -> Result<ThreadGroupKey, Errno> { |
| error!(EBADF) |
| } |
| |
| fn readahead( |
| &self, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _length: usize, |
| ) -> Result<(), Errno> { |
| error!(EINVAL) |
| } |
| |
| /// Extra information that is included in the /proc/<pid>/fdfino/<fd> entry. |
| fn extra_fdinfo( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileHandle, |
| _current_task: &CurrentTask, |
| ) -> Option<FsString> { |
| None |
| } |
| } |
| |
| /// Marker trait for implementation of FileOps that do not need to implement `close` and can |
| /// then pass a wrapper object as the `FileOps` implementation. |
| pub trait CloseFreeSafe {} |
| impl<T: FileOps + CloseFreeSafe, P: Deref<Target = T> + Send + Sync + 'static> FileOps for P { |
| fn close( |
| self: Box<Self>, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObjectState, |
| _current_task: &CurrentTask, |
| ) { |
| // This method cannot be delegated. T being `CloseFreeSafe` this is fine. |
| } |
| |
| fn flush( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) { |
| self.deref().flush(locked, file, current_task) |
| } |
| |
| fn has_persistent_offsets(&self) -> bool { |
| self.deref().has_persistent_offsets() |
| } |
| |
| fn writes_update_seek_offset(&self) -> bool { |
| self.deref().writes_update_seek_offset() |
| } |
| |
| fn is_seekable(&self) -> bool { |
| self.deref().is_seekable() |
| } |
| |
| fn read( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| self.deref().read(locked, file, current_task, offset, data) |
| } |
| |
| fn write( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| self.deref().write(locked, file, current_task, offset, data) |
| } |
| |
| fn seek( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| current_offset: off_t, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno> { |
| self.deref().seek(locked, file, current_task, current_offset, target) |
| } |
| |
| fn sync(&self, file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.deref().sync(file, current_task) |
| } |
| |
| fn data_sync(&self, file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.deref().data_sync(file, current_task) |
| } |
| |
| fn get_memory( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| length: Option<usize>, |
| prot: ProtectionFlags, |
| ) -> Result<Arc<MemoryObject>, Errno> { |
| self.deref().get_memory(locked, file, current_task, length, prot) |
| } |
| |
| fn mmap( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| memory_offset: u64, |
| length: usize, |
| prot_flags: ProtectionFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<UserAddress, Errno> { |
| self.deref().mmap( |
| locked, |
| file, |
| current_task, |
| addr, |
| memory_offset, |
| length, |
| prot_flags, |
| options, |
| filename, |
| ) |
| } |
| |
| fn readdir( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| self.deref().readdir(locked, file, current_task, sink) |
| } |
| |
| fn wait_async( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| waiter: &Waiter, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| self.deref().wait_async(locked, file, current_task, waiter, events, handler) |
| } |
| |
| fn query_events( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> { |
| self.deref().query_events(locked, file, current_task) |
| } |
| |
| fn ioctl( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| self.deref().ioctl(locked, file, current_task, request, arg) |
| } |
| |
| fn fcntl( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| cmd: u32, |
| arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| self.deref().fcntl(file, current_task, cmd, arg) |
| } |
| |
| fn to_handle( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<Option<zx::NullableHandle>, Errno> { |
| self.deref().to_handle(file, current_task) |
| } |
| |
| fn as_thread_group_key(&self, file: &FileObject) -> Result<ThreadGroupKey, Errno> { |
| self.deref().as_thread_group_key(file) |
| } |
| |
| fn readahead( |
| &self, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| length: usize, |
| ) -> Result<(), Errno> { |
| self.deref().readahead(file, current_task, offset, length) |
| } |
| |
| fn extra_fdinfo( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| file: &FileHandle, |
| current_task: &CurrentTask, |
| ) -> Option<FsString> { |
| self.deref().extra_fdinfo(locked, file, current_task) |
| } |
| } |
| |
| pub fn default_eof_offset<L>( |
| locked: &mut Locked<L>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<off_t, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| Ok(file.node().get_size(locked, current_task)? as off_t) |
| } |
| |
| /// Implement the seek method for a file. The computation from the end of the file must be provided |
| /// through a callback. |
| /// |
| /// Errors if the calculated offset is invalid. |
| /// |
| /// - `current_offset`: The current position |
| /// - `target`: The location to seek to. |
| /// - `compute_end`: Compute the new offset from the end. Return an error if the operation is not |
| /// supported. |
| pub fn default_seek<F>( |
| current_offset: off_t, |
| target: SeekTarget, |
| compute_end: F, |
| ) -> Result<off_t, Errno> |
| where |
| F: FnOnce() -> Result<off_t, Errno>, |
| { |
| let new_offset = match target { |
| SeekTarget::Set(offset) => Some(offset), |
| SeekTarget::Cur(offset) => current_offset.checked_add(offset), |
| SeekTarget::End(offset) => compute_end()?.checked_add(offset), |
| SeekTarget::Data(offset) => { |
| let eof = compute_end().unwrap_or(off_t::MAX); |
| if offset >= eof { |
| return error!(ENXIO); |
| } |
| Some(offset) |
| } |
| SeekTarget::Hole(offset) => { |
| let eof = compute_end()?; |
| if offset >= eof { |
| return error!(ENXIO); |
| } |
| Some(eof) |
| } |
| } |
| .ok_or_else(|| errno!(EINVAL))?; |
| |
| if new_offset < 0 { |
| return error!(EINVAL); |
| } |
| |
| Ok(new_offset) |
| } |
| |
| /// Implement the seek method for a file without an upper bound on the resulting offset. |
| /// |
| /// This is useful for files without a defined size. |
| /// |
| /// Errors if the calculated offset is invalid. |
| /// |
| /// - `current_offset`: The current position |
| /// - `target`: The location to seek to. |
| pub fn unbounded_seek(current_offset: off_t, target: SeekTarget) -> Result<off_t, Errno> { |
| default_seek(current_offset, target, || Ok(MAX_LFS_FILESIZE as off_t)) |
| } |
| |
| #[macro_export] |
| macro_rules! fileops_impl_delegate_read_and_seek { |
| ($self:ident, $delegate:expr) => { |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| |
| fn read( |
| &$self, |
| locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| file: &FileObject, |
| current_task: &$crate::task::CurrentTask, |
| offset: usize, |
| data: &mut dyn $crate::vfs::buffers::OutputBuffer, |
| ) -> Result<usize, starnix_uapi::errors::Errno> { |
| $delegate.read(locked, file, current_task, offset, data) |
| } |
| |
| fn seek( |
| &$self, |
| locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| file: &FileObject, |
| current_task: &$crate::task::CurrentTask, |
| current_offset: starnix_uapi::off_t, |
| target: $crate::vfs::SeekTarget, |
| ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> { |
| $delegate.seek(locked, file, current_task, current_offset, target) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps::seek`] in a way that makes sense for seekable files. |
| #[macro_export] |
| macro_rules! fileops_impl_seekable { |
| () => { |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| |
| fn seek( |
| &self, |
| locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| file: &$crate::vfs::FileObject, |
| current_task: &$crate::task::CurrentTask, |
| current_offset: starnix_uapi::off_t, |
| target: $crate::vfs::SeekTarget, |
| ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> { |
| $crate::vfs::default_seek(current_offset, target, || { |
| $crate::vfs::default_eof_offset(locked, file, current_task) |
| }) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for non-seekable files. |
| #[macro_export] |
| macro_rules! fileops_impl_nonseekable { |
| () => { |
| fn is_seekable(&self) -> bool { |
| false |
| } |
| |
| fn seek( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _current_offset: starnix_uapi::off_t, |
| _target: $crate::vfs::SeekTarget, |
| ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> { |
| starnix_uapi::error!(ESPIPE) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps::seek`] methods in a way that makes sense for files that ignore |
| /// seeking operations and always read/write at offset 0. |
| #[macro_export] |
| macro_rules! fileops_impl_seekless { |
| () => { |
| fn has_persistent_offsets(&self) -> bool { |
| false |
| } |
| |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| |
| fn seek( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _current_offset: starnix_uapi::off_t, |
| _target: $crate::vfs::SeekTarget, |
| ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> { |
| Ok(0) |
| } |
| }; |
| } |
| |
| #[macro_export] |
| macro_rules! fileops_impl_dataless { |
| () => { |
| fn write( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _offset: usize, |
| _data: &mut dyn $crate::vfs::buffers::InputBuffer, |
| ) -> Result<usize, starnix_uapi::errors::Errno> { |
| starnix_uapi::error!(EINVAL) |
| } |
| |
| fn read( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _offset: usize, |
| _data: &mut dyn $crate::vfs::buffers::OutputBuffer, |
| ) -> Result<usize, starnix_uapi::errors::Errno> { |
| starnix_uapi::error!(EINVAL) |
| } |
| }; |
| } |
| |
| /// Implements [`FileOps`] methods in a way that makes sense for directories. You must implement |
| /// [`FileOps::seek`] and [`FileOps::readdir`]. |
| #[macro_export] |
| macro_rules! fileops_impl_directory { |
| () => { |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| |
| fn read( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _offset: usize, |
| _data: &mut dyn $crate::vfs::buffers::OutputBuffer, |
| ) -> Result<usize, starnix_uapi::errors::Errno> { |
| starnix_uapi::error!(EISDIR) |
| } |
| |
| fn write( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| _offset: usize, |
| _data: &mut dyn $crate::vfs::buffers::InputBuffer, |
| ) -> Result<usize, starnix_uapi::errors::Errno> { |
| starnix_uapi::error!(EISDIR) |
| } |
| }; |
| } |
| |
| #[macro_export] |
| macro_rules! fileops_impl_unbounded_seek { |
| () => { |
| fn seek( |
| &self, |
| _locked: &mut starnix_sync::Locked<starnix_sync::FileOpsCore>, |
| _file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| current_offset: starnix_uapi::off_t, |
| target: $crate::vfs::SeekTarget, |
| ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> { |
| $crate::vfs::unbounded_seek(current_offset, target) |
| } |
| }; |
| } |
| |
| #[macro_export] |
| macro_rules! fileops_impl_noop_sync { |
| () => { |
| fn sync( |
| &self, |
| file: &$crate::vfs::FileObject, |
| _current_task: &$crate::task::CurrentTask, |
| ) -> Result<(), starnix_uapi::errors::Errno> { |
| if !file.node().is_reg() && !file.node().is_dir() { |
| return starnix_uapi::error!(EINVAL); |
| } |
| Ok(()) |
| } |
| }; |
| } |
| |
| // Public re-export of macros allows them to be used like regular rust items. |
| |
| pub use { |
| fileops_impl_dataless, fileops_impl_delegate_read_and_seek, fileops_impl_directory, |
| fileops_impl_nonseekable, fileops_impl_noop_sync, fileops_impl_seekable, fileops_impl_seekless, |
| fileops_impl_unbounded_seek, |
| }; |
| pub const AES256_KEY_SIZE: usize = 32; |
| |
| pub fn canonicalize_ioctl_request(current_task: &CurrentTask, request: u32) -> u32 { |
| if current_task.is_arch32() { |
| match request { |
| uapi::arch32::FS_IOC_GETFLAGS => uapi::FS_IOC_GETFLAGS, |
| uapi::arch32::FS_IOC_SETFLAGS => uapi::FS_IOC_SETFLAGS, |
| _ => request, |
| } |
| } else { |
| request |
| } |
| } |
| |
| pub fn default_ioctl( |
| file: &FileObject, |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| match canonicalize_ioctl_request(current_task, request) { |
| TCGETS => error!(ENOTTY), |
| FIGETBSZ => { |
| let node = file.node(); |
| let supported_file = node.is_reg() || node.is_dir(); |
| if !supported_file { |
| return error!(ENOTTY); |
| } |
| |
| let blocksize = file.node().stat(locked, current_task)?.st_blksize; |
| current_task.write_object(arg.into(), &blocksize)?; |
| Ok(SUCCESS) |
| } |
| FIONBIO => { |
| let arg_ref = UserAddress::from(arg).into(); |
| let arg: i32 = current_task.read_object(arg_ref)?; |
| let val = if arg == 0 { |
| // Clear the NONBLOCK flag |
| OpenFlags::empty() |
| } else { |
| // Set the NONBLOCK flag |
| OpenFlags::NONBLOCK |
| }; |
| file.update_file_flags(val, OpenFlags::NONBLOCK); |
| Ok(SUCCESS) |
| } |
| FIOQSIZE => { |
| let node = file.node(); |
| let supported_file = node.is_reg() || node.is_dir(); |
| if !supported_file { |
| return error!(ENOTTY); |
| } |
| |
| let size = file.node().stat(locked, current_task)?.st_size; |
| current_task.write_object(arg.into(), &size)?; |
| Ok(SUCCESS) |
| } |
| FIONREAD => { |
| track_stub!(TODO("https://fxbug.dev/322874897"), "FIONREAD"); |
| if !file.name.entry.node.is_reg() { |
| return error!(ENOTTY); |
| } |
| |
| let size = file |
| .name |
| .entry |
| .node |
| .fetch_and_refresh_info(locked, current_task) |
| .map_err(|_| errno!(EINVAL))? |
| .size; |
| let offset = usize::try_from(*file.offset.lock()).map_err(|_| errno!(EINVAL))?; |
| let remaining = |
| if size < offset { 0 } else { i32::try_from(size - offset).unwrap_or(i32::MAX) }; |
| current_task.write_object(arg.into(), &remaining)?; |
| Ok(SUCCESS) |
| } |
| FS_IOC_FSGETXATTR => { |
| track_stub!(TODO("https://fxbug.dev/322875209"), "FS_IOC_FSGETXATTR"); |
| let arg = UserAddress::from(arg).into(); |
| current_task.write_object(arg, &fsxattr::default())?; |
| Ok(SUCCESS) |
| } |
| FS_IOC_FSSETXATTR => { |
| track_stub!(TODO("https://fxbug.dev/322875271"), "FS_IOC_FSSETXATTR"); |
| let arg = UserAddress::from(arg).into(); |
| let _: fsxattr = current_task.read_object(arg)?; |
| Ok(SUCCESS) |
| } |
| uapi::FS_IOC_GETFLAGS => { |
| track_stub!(TODO("https://fxbug.dev/322874935"), "FS_IOC_GETFLAGS"); |
| let arg = UserRef::<u32>::from(arg); |
| let mut flags: u32 = 0; |
| if matches!(*file.node().fsverity.lock(), FsVerityState::FsVerity) { |
| flags |= FS_VERITY_FL; |
| } |
| if file.node().info().casefold { |
| flags |= FS_CASEFOLD_FL; |
| } |
| current_task.write_object(arg, &flags)?; |
| Ok(SUCCESS) |
| } |
| uapi::FS_IOC_SETFLAGS => { |
| track_stub!(TODO("https://fxbug.dev/322875367"), "FS_IOC_SETFLAGS"); |
| let arg = UserRef::<u32>::from(arg); |
| let flags: u32 = current_task.read_object(arg)?; |
| file.node().update_attributes(locked, current_task, |info| { |
| info.casefold = flags & FS_CASEFOLD_FL != 0; |
| Ok(()) |
| })?; |
| Ok(SUCCESS) |
| } |
| FS_IOC_ENABLE_VERITY => { |
| Ok(fsverity::ioctl::enable(locked, current_task, UserAddress::from(arg).into(), file)?) |
| } |
| FS_IOC_MEASURE_VERITY => { |
| Ok(fsverity::ioctl::measure(locked, current_task, UserAddress::from(arg).into(), file)?) |
| } |
| FS_IOC_READ_VERITY_METADATA => { |
| Ok(fsverity::ioctl::read_metadata(current_task, UserAddress::from(arg).into(), file)?) |
| } |
| FS_IOC_ADD_ENCRYPTION_KEY => { |
| let fscrypt_add_key_ref = UserRef::<fscrypt_add_key_arg>::from(arg); |
| let key_ref_addr = fscrypt_add_key_ref.next()?.addr(); |
| let mut fscrypt_add_key_arg = current_task.read_object(fscrypt_add_key_ref.clone())?; |
| if fscrypt_add_key_arg.key_id != 0 { |
| track_stub!(TODO("https://fxbug.dev/375649227"), "non-zero key ids"); |
| return error!(ENOTSUP); |
| } |
| if fscrypt_add_key_arg.key_spec.type_ != FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER { |
| track_stub!(TODO("https://fxbug.dev/375648306"), "fscrypt descriptor type"); |
| return error!(ENOTSUP); |
| } |
| let key = current_task |
| .read_memory_to_vec(key_ref_addr, fscrypt_add_key_arg.raw_size as usize)?; |
| let user_id = current_task.with_current_creds(|creds| creds.uid); |
| |
| let crypt_service = file.node().fs().crypt_service().ok_or_else(|| errno!(ENOTSUP))?; |
| let key_identifier = crypt_service.add_wrapping_key(&key, user_id)?; |
| fscrypt_add_key_arg.key_spec.u.identifier = |
| fscrypt_identifier { value: key_identifier, ..Default::default() }; |
| current_task.write_object(fscrypt_add_key_ref, &fscrypt_add_key_arg)?; |
| Ok(SUCCESS) |
| } |
| FS_IOC_SET_ENCRYPTION_POLICY => { |
| let fscrypt_policy_ref = UserRef::<uapi::fscrypt_policy_v2>::from(arg); |
| let policy = current_task.read_object(fscrypt_policy_ref)?; |
| if policy.version as u32 != FSCRYPT_POLICY_V2 { |
| track_stub!(TODO("https://fxbug.dev/375649656"), "fscrypt policy v1"); |
| return error!(ENOTSUP); |
| } |
| if policy.flags != 0 { |
| track_stub!( |
| TODO("https://fxbug.dev/375700939"), |
| "fscrypt policy flags", |
| policy.flags |
| ); |
| } |
| if policy.contents_encryption_mode as u32 != FSCRYPT_MODE_AES_256_XTS { |
| track_stub!( |
| TODO("https://fxbug.dev/375684057"), |
| "fscrypt encryption modes", |
| policy.contents_encryption_mode |
| ); |
| } |
| if policy.filenames_encryption_mode as u32 != FSCRYPT_MODE_AES_256_CTS { |
| track_stub!( |
| TODO("https://fxbug.dev/375684057"), |
| "fscrypt encryption modes", |
| policy.filenames_encryption_mode |
| ); |
| } |
| let user_id = current_task.with_current_creds(|creds| creds.uid); |
| if user_id != file.node().info().uid { |
| security::check_task_capable(current_task, CAP_FOWNER) |
| .map_err(|_| errno!(EACCES))?; |
| } |
| |
| let crypt_service = file.node().fs().crypt_service().ok_or_else(|| errno!(ENOTSUP))?; |
| if let Some(users) = |
| crypt_service.get_users_for_key(EncryptionKeyId::from(policy.master_key_identifier)) |
| { |
| if !users.contains(&user_id) { |
| return error!(ENOKEY); |
| } |
| } else { |
| track_stub!( |
| TODO("https://fxbug.dev/375067633"), |
| "users with CAP_FOWNER can set encryption policies with unadded keys" |
| ); |
| return error!(ENOKEY); |
| } |
| |
| let attributes = file.node().fetch_and_refresh_info(locked, current_task)?; |
| if let Some(wrapping_key_id) = &attributes.wrapping_key_id { |
| if wrapping_key_id != &policy.master_key_identifier { |
| return error!(EEXIST); |
| } |
| } else { |
| // Don't deadlock! update_attributes will also lock the attributes. |
| std::mem::drop(attributes); |
| file.node().update_attributes(locked, current_task, |info| { |
| info.wrapping_key_id = Some(policy.master_key_identifier); |
| Ok(()) |
| })?; |
| } |
| Ok(SUCCESS) |
| } |
| FS_IOC_REMOVE_ENCRYPTION_KEY => { |
| let fscrypt_remove_key_arg_ref = UserRef::<uapi::fscrypt_remove_key_arg>::from(arg); |
| let fscrypt_remove_key_arg = current_task.read_object(fscrypt_remove_key_arg_ref)?; |
| if fscrypt_remove_key_arg.key_spec.type_ != FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER { |
| track_stub!(TODO("https://fxbug.dev/375648306"), "fscrypt descriptor type"); |
| return error!(ENOTSUP); |
| } |
| let crypt_service = file.node().fs().crypt_service().ok_or_else(|| errno!(ENOTSUP))?; |
| let user_id = current_task.with_current_creds(|creds| creds.uid); |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let identifier = unsafe { fscrypt_remove_key_arg.key_spec.u.identifier.value }; |
| crypt_service.forget_wrapping_key(identifier, user_id)?; |
| Ok(SUCCESS) |
| } |
| _ => { |
| track_stub!(TODO("https://fxbug.dev/322874917"), "ioctl fallthrough", request); |
| error!(ENOTTY) |
| } |
| } |
| } |
| |
| pub fn default_fcntl(cmd: u32) -> Result<SyscallResult, Errno> { |
| track_stub!(TODO("https://fxbug.dev/322875704"), "default fcntl", cmd); |
| error!(EINVAL) |
| } |
| |
| pub struct OPathOps {} |
| |
| impl OPathOps { |
| pub fn new() -> OPathOps { |
| OPathOps {} |
| } |
| } |
| |
| impl FileOps for OPathOps { |
| fileops_impl_noop_sync!(); |
| |
| fn has_persistent_offsets(&self) -> bool { |
| false |
| } |
| fn is_seekable(&self) -> bool { |
| true |
| } |
| fn read( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn write( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| error!(EBADF) |
| } |
| fn seek( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _current_offset: off_t, |
| _target: SeekTarget, |
| ) -> Result<off_t, Errno> { |
| error!(EBADF) |
| } |
| fn get_memory( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _length: Option<usize>, |
| _prot: ProtectionFlags, |
| ) -> Result<Arc<MemoryObject>, Errno> { |
| error!(EBADF) |
| } |
| fn readdir( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| error!(EBADF) |
| } |
| |
| fn ioctl( |
| &self, |
| _locked: &mut Locked<Unlocked>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _request: u32, |
| _arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| error!(EBADF) |
| } |
| } |
| |
| pub struct ProxyFileOps(pub FileHandle); |
| |
| impl FileOps for ProxyFileOps { |
| // `close` is not delegated because the last reference to a `ProxyFileOps` is not |
| // necessarily the last reference of the proxied file. If this is the case, the |
| // releaser will handle it. |
| // These don't take &FileObject making it too hard to handle them properly in the macro |
| fn has_persistent_offsets(&self) -> bool { |
| self.0.ops().has_persistent_offsets() |
| } |
| fn writes_update_seek_offset(&self) -> bool { |
| self.0.ops().writes_update_seek_offset() |
| } |
| fn is_seekable(&self) -> bool { |
| self.0.ops().is_seekable() |
| } |
| // These take &mut Locked<L> as a second argument |
| fn flush( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| ) { |
| self.0.ops().flush(locked, &self.0, current_task); |
| } |
| fn wait_async( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| waiter: &Waiter, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> Option<WaitCanceler> { |
| self.0.ops().wait_async(locked, &self.0, current_task, waiter, events, handler) |
| } |
| fn query_events( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> { |
| self.0.ops().query_events(locked, &self.0, current_task) |
| } |
| fn read( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| self.0.ops().read(locked, &self.0, current_task, offset, data) |
| } |
| fn write( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| self.0.ops().write(locked, &self.0, current_task, offset, data) |
| } |
| fn ioctl( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| self.0.ops().ioctl(locked, &self.0, current_task, request, arg) |
| } |
| fn fcntl( |
| &self, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| cmd: u32, |
| arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| self.0.ops().fcntl(&self.0, current_task, cmd, arg) |
| } |
| fn readdir( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> { |
| self.0.ops().readdir(locked, &self.0, current_task, sink) |
| } |
| fn sync(&self, _file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.0.ops().sync(&self.0, current_task) |
| } |
| fn data_sync(&self, _file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.0.ops().sync(&self.0, current_task) |
| } |
| fn get_memory( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| length: Option<usize>, |
| prot: ProtectionFlags, |
| ) -> Result<Arc<MemoryObject>, Errno> { |
| self.0.ops.get_memory(locked, &self.0, current_task, length, prot) |
| } |
| fn mmap( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| memory_offset: u64, |
| length: usize, |
| prot_flags: ProtectionFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<UserAddress, Errno> { |
| self.0.ops.mmap( |
| locked, |
| &self.0, |
| current_task, |
| addr, |
| memory_offset, |
| length, |
| prot_flags, |
| options, |
| filename, |
| ) |
| } |
| fn seek( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| current_task: &CurrentTask, |
| offset: off_t, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno> { |
| self.0.ops.seek(locked, &self.0, current_task, offset, target) |
| } |
| } |
| |
| #[derive(Debug, Default, Copy, Clone)] |
| pub enum FileAsyncOwner { |
| #[default] |
| Unowned, |
| Thread(pid_t), |
| Process(pid_t), |
| ProcessGroup(pid_t), |
| } |
| |
| impl FileAsyncOwner { |
| pub fn validate(self, current_task: &CurrentTask) -> Result<(), Errno> { |
| match self { |
| FileAsyncOwner::Unowned => (), |
| FileAsyncOwner::Thread(id) | FileAsyncOwner::Process(id) => { |
| Task::from_weak(¤t_task.get_task(id))?; |
| } |
| FileAsyncOwner::ProcessGroup(pgid) => { |
| current_task |
| .kernel() |
| .pids |
| .read() |
| .get_process_group(pgid) |
| .ok_or_else(|| errno!(ESRCH))?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct FileObjectId(u64); |
| |
| impl FileObjectId { |
| pub fn as_epoll_key(&self) -> EpollKey { |
| self.0 as EpollKey |
| } |
| } |
| |
| /// 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>, |
| state: FileObjectState, |
| } |
| |
| impl std::ops::Deref for FileObject { |
| type Target = FileObjectState; |
| fn deref(&self) -> &Self::Target { |
| &self.state |
| } |
| } |
| |
| pub struct FileObjectState { |
| /// Weak reference to the `FileHandle` of this `FileObject`. This allows to retrieve the |
| /// `FileHandle` from a `FileObject`. |
| pub weak_handle: WeakFileHandle, |
| |
| /// A unique identifier for this file object. |
| pub id: FileObjectId, |
| |
| /// The NamespaceNode associated with this FileObject. |
| /// |
| /// Represents the name the process used to open this file. |
| pub name: ActiveNamespaceNode, |
| |
| pub fs: FileSystemHandle, |
| |
| pub offset: Mutex<off_t>, |
| |
| flags: Mutex<OpenFlags>, |
| |
| async_owner: Mutex<FileAsyncOwner>, |
| |
| /// A set of epoll file descriptor numbers that tracks which `EpollFileObject`s add this |
| /// `FileObject` as the control file. |
| epoll_files: Mutex<HashMap<FileHandleKey, WeakFileHandle>>, |
| |
| /// See fcntl F_SETLEASE and F_GETLEASE. |
| lease: Mutex<FileLeaseType>, |
| |
| // This extra reference to the FsNode should not be needed, but it is needed to make |
| // Inotify.ExcludeUnlinkInodeEvents pass. |
| _mysterious_node: Option<FsNodeHandle>, |
| |
| /// Opaque security state associated this file object. |
| pub security_state: security::FileObjectState, |
| } |
| |
| pub enum FileObjectReleaserAction {} |
| impl ReleaserAction<FileObject> for FileObjectReleaserAction { |
| fn release(file_object: ReleaseGuard<FileObject>) { |
| register_delayed_release(file_object); |
| } |
| } |
| pub type FileReleaser = ObjectReleaser<FileObject, FileObjectReleaserAction>; |
| pub type FileHandle = Arc<FileReleaser>; |
| pub type WeakFileHandle = Weak<FileReleaser>; |
| pub type FileHandleKey = WeakKey<FileReleaser>; |
| |
| impl FileObjectState { |
| /// The FsNode from which this FileObject was created. |
| pub fn node(&self) -> &FsNodeHandle { |
| &self.name.entry.node |
| } |
| |
| pub fn flags(&self) -> OpenFlags { |
| *self.flags.lock() |
| } |
| |
| 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() |
| } |
| |
| /// Returns false if the file is not allowed to be executed. |
| pub fn can_exec(&self) -> bool { |
| let mounted_no_exec = self.name.to_passive().mount.flags().contains(MountFlags::NOEXEC); |
| let no_exec_seal = self |
| .node() |
| .write_guard_state |
| .lock() |
| .get_seals() |
| .map(|seals| seals.contains(SealFlags::NO_EXEC)) |
| .unwrap_or(false); |
| !(mounted_no_exec || no_exec_seal) |
| } |
| |
| // Notifies watchers on the current node and its parent about an event. |
| pub fn notify(&self, event_mask: InotifyMask) { |
| self.name.notify(event_mask) |
| } |
| } |
| |
| impl FileObject { |
| /// Create a FileObject that is not mounted in a namespace. |
| /// |
| /// In particular, this will create a new unrooted entries. This should not be used on |
| /// file system with persistent entries, as the created entry will be out of sync with the one |
| /// from the file system. |
| /// |
| /// The returned FileObject does not have a name. |
| pub fn new_anonymous<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ops: Box<dyn FileOps>, |
| node: FsNodeHandle, |
| flags: OpenFlags, |
| ) -> FileHandle |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| assert!(!node.fs().has_permanent_entries()); |
| Self::new( |
| locked, |
| current_task, |
| ops, |
| NamespaceNode::new_anonymous_unrooted(current_task, node), |
| flags, |
| ) |
| .expect("Failed to create anonymous FileObject") |
| } |
| |
| /// Create a FileObject with an associated NamespaceNode. |
| /// |
| /// This function is not typically called directly. Instead, consider |
| /// calling NamespaceNode::open. |
| pub fn new<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ops: Box<dyn FileOps>, |
| name: NamespaceNode, |
| flags: OpenFlags, |
| ) -> Result<FileHandle, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let _mysterious_node = if flags.can_write() { |
| name.entry.node.write_guard_state.lock().acquire(FileWriteGuardMode::WriteFile)?; |
| Some(name.entry.node.clone()) |
| } else { |
| None |
| }; |
| let fs = name.entry.node.fs(); |
| let id = FileObjectId(current_task.kernel.next_file_object_id.next()); |
| let security_state = security::file_alloc_security(current_task); |
| let file = FileHandle::new_cyclic(|weak_handle| { |
| Self { |
| ops, |
| state: FileObjectState { |
| weak_handle: weak_handle.clone(), |
| id, |
| name: name.into_active(), |
| fs, |
| offset: Mutex::new(0), |
| flags: Mutex::new(flags - OpenFlags::CREAT), |
| async_owner: Default::default(), |
| epoll_files: Default::default(), |
| lease: Default::default(), |
| _mysterious_node, |
| security_state, |
| }, |
| } |
| .into() |
| }); |
| file.notify(InotifyMask::OPEN); |
| |
| file.ops().open(locked.cast_locked::<FileOpsCore>(), &file, current_task)?; |
| Ok(file) |
| } |
| |
| pub fn max_access_for_memory_mapping(&self) -> Access { |
| let mut access = Access::EXIST; |
| if self.can_exec() { |
| access |= Access::EXEC; |
| } |
| let flags = self.flags.lock(); |
| if flags.can_read() { |
| access |= Access::READ; |
| } |
| if flags.can_write() { |
| access |= Access::WRITE; |
| } |
| access |
| } |
| |
| pub fn ops(&self) -> &dyn FileOps { |
| self.ops.as_ref() |
| } |
| |
| pub fn ops_type_name(&self) -> &'static str { |
| self.ops().type_name() |
| } |
| |
| pub fn is_non_blocking(&self) -> bool { |
| self.flags().contains(OpenFlags::NONBLOCK) |
| } |
| |
| /// Common implementation for blocking operations. |
| /// |
| /// This function is used to implement the blocking operations for file objects. FileOps |
| /// implementations should call this function to handle the blocking logic. |
| /// |
| /// The `op` parameter is a function that implements the non-blocking version of the operation. |
| /// The function is called once without registering a waiter in case no wait is needed. If the |
| /// operation returns EAGAIN and the file object is non-blocking, the function returns EAGAIN. |
| /// |
| /// If the operation returns EAGAIN and the file object is blocking, the function will block |
| /// until the given events are triggered. At that time, the operation is retried. Notice that |
| /// the `op` function can be called multiple times before the operation completes. |
| /// |
| /// The `deadline` parameter is the deadline for the operation. If the operation does not |
| /// complete before the deadline, the function will return ETIMEDOUT. |
| pub fn blocking_op<L, T, Op>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| events: FdEvents, |
| deadline: Option<zx::MonotonicInstant>, |
| mut op: Op, |
| ) -> Result<T, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| Op: FnMut(&mut Locked<L>) -> Result<T, Errno>, |
| { |
| // Don't return EAGAIN for directories. This can happen because glibc always opens a |
| // directory with O_NONBLOCK. |
| let can_return_eagain = self.flags().contains(OpenFlags::NONBLOCK) |
| && !self.flags().contains(OpenFlags::DIRECTORY); |
| // Run the operation a first time without registering a waiter in case no wait is needed. |
| match op(locked) { |
| Err(errno) if errno == EAGAIN && !can_return_eagain => {} |
| result => return result, |
| } |
| |
| let waiter = Waiter::new(); |
| loop { |
| // Register the waiter before running the operation to prevent a race. |
| self.wait_async(locked, current_task, &waiter, events, WaitCallback::none()); |
| match op(locked) { |
| Err(e) if e == EAGAIN => {} |
| result => return result, |
| } |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| waiter |
| .wait_until( |
| locked, |
| current_task, |
| deadline.unwrap_or(zx::MonotonicInstant::INFINITE), |
| ) |
| .map_err(|e| if e == ETIMEDOUT { errno!(EAGAIN) } else { e })?; |
| } |
| } |
| |
| pub fn is_seekable(&self) -> bool { |
| self.ops().is_seekable() |
| } |
| |
| pub fn has_persistent_offsets(&self) -> bool { |
| self.ops().has_persistent_offsets() |
| } |
| |
| /// Common implementation for `read` and `read_at`. |
| fn read_internal<R>(&self, current_task: &CurrentTask, read: R) -> Result<usize, Errno> |
| where |
| R: FnOnce() -> Result<usize, Errno>, |
| { |
| security::file_permission(current_task, self, security::PermissionFlags::READ)?; |
| |
| if !self.can_read() { |
| return error!(EBADF); |
| } |
| let bytes_read = read()?; |
| |
| // TODO(steveaustin) - omit updating time_access to allow info to be immutable |
| // and thus allow simultaneous reads. |
| self.update_atime(); |
| if bytes_read > 0 { |
| self.notify(InotifyMask::ACCESS); |
| } |
| |
| Ok(bytes_read) |
| } |
| |
| pub fn read<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| self.read_internal(current_task, || { |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| if !self.ops().has_persistent_offsets() { |
| if data.available() > MAX_LFS_FILESIZE { |
| return error!(EINVAL); |
| } |
| return self.ops.read(locked, self, current_task, 0, data); |
| } |
| |
| let mut offset_guard = self.offset.lock(); |
| let offset = *offset_guard as usize; |
| checked_add_offset_and_length(offset, data.available())?; |
| let read = self.ops.read(locked, self, current_task, offset, data)?; |
| *offset_guard += read as off_t; |
| Ok(read) |
| }) |
| } |
| |
| pub fn read_at<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| if !self.ops().is_seekable() { |
| return error!(ESPIPE); |
| } |
| checked_add_offset_and_length(offset, data.available())?; |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| self.read_internal(current_task, || self.ops.read(locked, self, current_task, offset, data)) |
| } |
| |
| /// Common checks before calling ops().write. |
| fn write_common<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| security::file_permission(current_task, self, security::PermissionFlags::WRITE)?; |
| |
| // We need to cap the size of `data` to prevent us from growing the file too large, |
| // according to <https://man7.org/linux/man-pages/man2/write.2.html>: |
| // |
| // The number of bytes written may be less than count if, for example, there is |
| // insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource |
| // limit is encountered (see setrlimit(2)), |
| checked_add_offset_and_length(offset, data.available())?; |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| self.ops().write(locked, self, current_task, offset, data) |
| } |
| |
| /// Common wrapper work for `write` and `write_at`. |
| fn write_fn<W, L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| write: W, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| W: FnOnce(&mut Locked<L>) -> Result<usize, Errno>, |
| { |
| if !self.can_write() { |
| return error!(EBADF); |
| } |
| self.node().clear_suid_and_sgid_bits(locked, current_task)?; |
| let bytes_written = write(locked)?; |
| self.node().update_ctime_mtime(); |
| |
| if bytes_written > 0 { |
| self.notify(InotifyMask::MODIFY); |
| } |
| |
| Ok(bytes_written) |
| } |
| |
| pub fn write<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| self.write_fn(locked, current_task, |locked| { |
| if !self.ops().has_persistent_offsets() { |
| return self.write_common(locked, current_task, 0, data); |
| } |
| // TODO(https://fxbug.dev/333540469): write_fn should take L: LockBefore<FsNodeAppend>, |
| // but FileOpsCore must be after FsNodeAppend |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let locked = unsafe { Unlocked::new() }; |
| let mut offset = self.offset.lock(); |
| let bytes_written = if self.flags().contains(OpenFlags::APPEND) { |
| let (_guard, locked) = self.node().append_lock.write_and(locked, current_task)?; |
| *offset = self.ops().seek( |
| locked.cast_locked::<FileOpsCore>(), |
| self, |
| current_task, |
| *offset, |
| SeekTarget::End(0), |
| )?; |
| self.write_common(locked, current_task, *offset as usize, data) |
| } else { |
| let (_guard, locked) = self.node().append_lock.read_and(locked, current_task)?; |
| self.write_common(locked, current_task, *offset as usize, data) |
| }?; |
| if self.ops().writes_update_seek_offset() { |
| *offset += bytes_written as off_t; |
| } |
| Ok(bytes_written) |
| }) |
| } |
| |
| pub fn write_at<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| mut offset: usize, |
| data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| if !self.ops().is_seekable() { |
| return error!(ESPIPE); |
| } |
| self.write_fn(locked, current_task, |_locked| { |
| // TODO(https://fxbug.dev/333540469): write_fn should take L: LockBefore<FsNodeAppend>, |
| // but FileOpsCore must be after FsNodeAppend |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let locked = unsafe { Unlocked::new() }; |
| let (_guard, locked) = self.node().append_lock.read_and(locked, current_task)?; |
| |
| // According to LTP test pwrite04: |
| // |
| // POSIX requires that opening a file with the O_APPEND flag should have no effect on the |
| // location at which pwrite() writes data. However, on Linux, if a file is opened with |
| // O_APPEND, pwrite() appends data to the end of the file, regardless of the value of offset. |
| if self.flags().contains(OpenFlags::APPEND) && self.ops().is_seekable() { |
| checked_add_offset_and_length(offset, data.available())?; |
| offset = default_eof_offset(locked, self, current_task)? as usize; |
| } |
| |
| self.write_common(locked, current_task, offset, data) |
| }) |
| } |
| |
| pub fn seek<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| target: SeekTarget, |
| ) -> Result<off_t, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| let locked = locked; |
| |
| if !self.ops().is_seekable() { |
| return error!(ESPIPE); |
| } |
| |
| if !self.ops().has_persistent_offsets() { |
| return self.ops().seek(locked, self, current_task, 0, target); |
| } |
| |
| let mut offset_guard = self.offset.lock(); |
| let new_offset = self.ops().seek(locked, self, current_task, *offset_guard, target)?; |
| *offset_guard = new_offset; |
| Ok(new_offset) |
| } |
| |
| pub fn sync(&self, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.ops().sync(self, current_task) |
| } |
| |
| pub fn data_sync(&self, current_task: &CurrentTask) -> Result<(), Errno> { |
| self.ops().data_sync(self, current_task) |
| } |
| |
| pub fn get_memory<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| length: Option<usize>, |
| prot: ProtectionFlags, |
| ) -> Result<Arc<MemoryObject>, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| if prot.contains(ProtectionFlags::READ) && !self.can_read() { |
| return error!(EACCES); |
| } |
| if prot.contains(ProtectionFlags::WRITE) && !self.can_write() { |
| return error!(EACCES); |
| } |
| if prot.contains(ProtectionFlags::EXEC) && !self.can_exec() { |
| return error!(EPERM); |
| } |
| self.ops().get_memory(locked.cast_locked::<FileOpsCore>(), self, current_task, length, prot) |
| } |
| |
| pub fn mmap<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| addr: DesiredAddress, |
| memory_offset: u64, |
| length: usize, |
| prot_flags: ProtectionFlags, |
| options: MappingOptions, |
| filename: NamespaceNode, |
| ) -> Result<UserAddress, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| if !self.can_read() { |
| return error!(EACCES); |
| } |
| if prot_flags.contains(ProtectionFlags::WRITE) |
| && !self.can_write() |
| && options.contains(MappingOptions::SHARED) |
| { |
| return error!(EACCES); |
| } |
| if prot_flags.contains(ProtectionFlags::EXEC) && !self.can_exec() { |
| return error!(EPERM); |
| } |
| self.ops().mmap( |
| locked, |
| self, |
| current_task, |
| addr, |
| memory_offset, |
| length, |
| prot_flags, |
| options, |
| filename, |
| ) |
| } |
| |
| pub fn readdir<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| sink: &mut dyn DirentSink, |
| ) -> Result<(), Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| if self.name.entry.read().is_dead() { |
| return error!(ENOENT); |
| } |
| |
| self.ops().readdir(locked, self, current_task, sink)?; |
| self.update_atime(); |
| self.notify(InotifyMask::ACCESS); |
| Ok(()) |
| } |
| |
| pub fn ioctl( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| security::check_file_ioctl_access(current_task, &self, request)?; |
| |
| if request == FIBMAP { |
| security::check_task_capable(current_task, CAP_SYS_RAWIO)?; |
| |
| // TODO: https://fxbug.dev/404795644 - eliminate this phoney response when the SELinux |
| // Test Suite no longer requires it. |
| if current_task.kernel().features.selinux_test_suite { |
| let phoney_block = 0xbadf000du32; |
| current_task.write_object(arg.into(), &phoney_block)?; |
| return Ok(SUCCESS); |
| } |
| } |
| |
| self.ops().ioctl(locked, self, current_task, request, arg) |
| } |
| |
| pub fn fcntl( |
| &self, |
| current_task: &CurrentTask, |
| cmd: u32, |
| arg: u64, |
| ) -> Result<SyscallResult, Errno> { |
| self.ops().fcntl(self, current_task, cmd, arg) |
| } |
| |
| pub fn ftruncate<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| length: u64, |
| ) -> Result<(), Errno> |
| where |
| L: LockBefore<BeforeFsNodeAppend>, |
| { |
| // The file must be opened with write permissions. Otherwise |
| // truncating it is forbidden. |
| if !self.can_write() { |
| return error!(EINVAL); |
| } |
| self.node().ftruncate(locked, current_task, length)?; |
| self.name.entry.notify_ignoring_excl_unlink(InotifyMask::MODIFY); |
| Ok(()) |
| } |
| |
| pub fn fallocate<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| mode: FallocMode, |
| offset: u64, |
| length: u64, |
| ) -> Result<(), Errno> |
| where |
| L: LockBefore<BeforeFsNodeAppend>, |
| { |
| // If the file is a pipe or FIFO, ESPIPE is returned. |
| // See https://man7.org/linux/man-pages/man2/fallocate.2.html#ERRORS |
| if self.node().is_fifo() { |
| return error!(ESPIPE); |
| } |
| |
| // Must be a regular file or directory. |
| // See https://man7.org/linux/man-pages/man2/fallocate.2.html#ERRORS |
| if !self.node().is_dir() && !self.node().is_reg() { |
| return error!(ENODEV); |
| } |
| |
| // The file must be opened with write permissions. Otherwise operation is forbidden. |
| // See https://man7.org/linux/man-pages/man2/fallocate.2.html#ERRORS |
| if !self.can_write() { |
| return error!(EBADF); |
| } |
| |
| self.node().fallocate(locked, current_task, mode, offset, length)?; |
| self.notify(InotifyMask::MODIFY); |
| Ok(()) |
| } |
| |
| pub fn to_handle( |
| &self, |
| current_task: &CurrentTask, |
| ) -> Result<Option<zx::NullableHandle>, Errno> { |
| self.ops().to_handle(self, current_task) |
| } |
| |
| pub fn as_thread_group_key(&self) -> Result<ThreadGroupKey, Errno> { |
| self.ops().as_thread_group_key(self) |
| } |
| |
| 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); |
| } |
| |
| /// Get the async owner of this file. |
| /// |
| /// See fcntl(F_GETOWN) |
| pub fn get_async_owner(&self) -> FileAsyncOwner { |
| *self.async_owner.lock() |
| } |
| |
| /// Set the async owner of this file. |
| /// |
| /// See fcntl(F_SETOWN) |
| pub fn set_async_owner(&self, owner: FileAsyncOwner) { |
| *self.async_owner.lock() = owner; |
| } |
| |
| /// See fcntl(F_GETLEASE) |
| pub fn get_lease(&self, _current_task: &CurrentTask) -> FileLeaseType { |
| *self.lease.lock() |
| } |
| |
| /// See fcntl(F_SETLEASE) |
| pub fn set_lease( |
| &self, |
| _current_task: &CurrentTask, |
| lease: FileLeaseType, |
| ) -> Result<(), Errno> { |
| if !self.node().is_reg() { |
| return error!(EINVAL); |
| } |
| if lease == FileLeaseType::Read && self.can_write() { |
| return error!(EAGAIN); |
| } |
| *self.lease.lock() = lease; |
| Ok(()) |
| } |
| |
| /// Wait on the specified events and call the EventHandler when ready |
| pub fn wait_async<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| waiter: &Waiter, |
| events: FdEvents, |
| handler: EventHandler, |
| ) -> Option<WaitCanceler> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| self.ops().wait_async( |
| locked.cast_locked::<FileOpsCore>(), |
| self, |
| current_task, |
| waiter, |
| events, |
| handler, |
| ) |
| } |
| |
| /// The events currently active on this file. |
| pub fn query_events<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ) -> Result<FdEvents, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| self.ops() |
| .query_events(locked.cast_locked::<FileOpsCore>(), self, current_task) |
| .map(FdEvents::add_equivalent_fd_events) |
| } |
| |
| pub fn record_lock( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| cmd: RecordLockCommand, |
| flock: uapi::flock, |
| ) -> Result<Option<uapi::flock>, Errno> { |
| self.node().record_lock(locked, current_task, self, cmd, flock) |
| } |
| |
| pub fn flush<L>(&self, locked: &mut Locked<L>, current_task: &CurrentTask, id: FdTableId) |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| self.name.entry.node.record_lock_release(RecordLockOwner::FdTable(id)); |
| self.ops().flush(locked.cast_locked::<FileOpsCore>(), self, current_task) |
| } |
| |
| fn update_atime(&self) { |
| if !self.flags().contains(OpenFlags::NOATIME) { |
| self.name.update_atime(); |
| } |
| } |
| |
| pub fn readahead( |
| &self, |
| current_task: &CurrentTask, |
| offset: usize, |
| length: usize, |
| ) -> Result<(), Errno> { |
| // readfile() fails with EBADF if the file was not open for read. |
| if !self.can_read() { |
| return error!(EBADF); |
| } |
| checked_add_offset_and_length(offset, length)?; |
| self.ops().readahead(self, current_task, offset, length) |
| } |
| |
| pub fn extra_fdinfo( |
| &self, |
| locked: &mut Locked<FileOpsCore>, |
| current_task: &CurrentTask, |
| ) -> Option<FsString> { |
| let file = self.weak_handle.upgrade()?; |
| self.ops().extra_fdinfo(locked, &file, current_task) |
| } |
| |
| /// Register the fd number of an `EpollFileObject` that listens to events from this |
| /// `FileObject`. |
| pub fn register_epfd(&self, file: &FileHandle) { |
| self.epoll_files.lock().insert(WeakKey::from(file), file.weak_handle.clone()); |
| } |
| |
| pub fn unregister_epfd(&self, file: &FileHandle) { |
| self.epoll_files.lock().remove(&WeakKey::from(file)); |
| } |
| } |
| |
| impl Releasable for FileObject { |
| type Context<'a> = CurrentTaskAndLocked<'a>; |
| |
| fn release<'a>(self, context: CurrentTaskAndLocked<'a>) { |
| let (locked, current_task) = context; |
| // Release all wake leases associated with this file in the corresponding `WaitObject` |
| // of each registered epfd. |
| for (_, file) in self.epoll_files.lock().drain() { |
| if let Some(file) = file.upgrade() { |
| if let Some(epoll_object) = file.downcast_file::<EpollFileObject>() { |
| current_task |
| .kernel() |
| .suspend_resume_manager |
| .remove_epoll(self.id.as_epoll_key()); |
| let _ = epoll_object.delete(&self); |
| } |
| } |
| } |
| |
| if self.can_write() { |
| self.name.entry.node.write_guard_state.lock().release(FileWriteGuardMode::WriteFile); |
| } |
| |
| let locked = locked.cast_locked::<FileOpsCore>(); |
| let ops = self.ops; |
| let state = self.state; |
| ops.close(locked, &state, current_task); |
| state.name.entry.node.on_file_closed(&state); |
| let event = |
| if state.can_write() { InotifyMask::CLOSE_WRITE } else { InotifyMask::CLOSE_NOWRITE }; |
| state.notify(event); |
| } |
| } |
| |
| impl fmt::Debug for FileObject { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("FileObject") |
| .field("name", &self.name) |
| .field("fs", &self.fs.name()) |
| .field("offset", &self.offset) |
| .field("flags", &self.flags) |
| .field("ops_ty", &self.ops().type_name()) |
| .finish() |
| } |
| } |
| |
| impl OnWakeOps for FileReleaser { |
| /// Called when the underneath `FileOps` is waken up by the power framework. |
| fn on_wake(&self, current_task: &CurrentTask, baton_lease: &zx::NullableHandle) { |
| // Activate associated wake leases in registered epfd. |
| for (_, file) in self.epoll_files.lock().iter() { |
| if let Some(file) = file.upgrade() { |
| if let Some(epoll_file) = file.downcast_file::<EpollFileObject>() { |
| if let Some(weak_handle) = self.weak_handle.upgrade() { |
| if let Err(e) = |
| epoll_file.activate_lease(current_task, &weak_handle, baton_lease) |
| { |
| log_error!("Failed to activate wake lease in epoll control file: {e}"); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// A FileObject with the type of its FileOps known. Dereferencing it returns the FileOps. |
| pub struct DowncastedFile<'a, Ops> { |
| file: &'a FileObject, |
| ops: &'a Ops, |
| } |
| impl<'a, Ops> Copy for DowncastedFile<'a, Ops> {} |
| impl<'a, Ops> Clone for DowncastedFile<'a, Ops> { |
| fn clone(&self) -> Self { |
| *self |
| } |
| } |
| |
| impl<'a, Ops> DowncastedFile<'a, Ops> { |
| pub fn file(&self) -> &'a FileObject { |
| self.file |
| } |
| } |
| |
| impl<'a, Ops> Deref for DowncastedFile<'a, Ops> { |
| type Target = &'a Ops; |
| fn deref(&self) -> &Self::Target { |
| &self.ops |
| } |
| } |
| |
| impl FileObject { |
| /// Returns the `FileObject`'s `FileOps` as a `DowncastedFile<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<'a, T>(&'a self) -> Option<DowncastedFile<'a, T>> |
| where |
| T: 'static, |
| { |
| let ops = self.ops().as_any().downcast_ref::<T>()?; |
| Some(DowncastedFile { file: self, ops }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::fs::tmpfs::TmpFs; |
| use crate::task::CurrentTask; |
| use crate::task::dynamic_thread_spawner::SpawnRequestBuilder; |
| use crate::testing::*; |
| use crate::vfs::MountInfo; |
| use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer}; |
| use starnix_sync::{Locked, Unlocked}; |
| use starnix_uapi::auth::FsCred; |
| use starnix_uapi::device_type::DeviceType; |
| use starnix_uapi::file_mode::FileMode; |
| use starnix_uapi::open_flags::OpenFlags; |
| use std::sync::Arc; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use zerocopy::{FromBytes, IntoBytes, LE, U64}; |
| |
| #[::fuchsia::test] |
| async fn test_append_truncate_race() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let kernel = current_task.kernel(); |
| let root_fs = TmpFs::new_fs(locked, &kernel); |
| let mount = MountInfo::detached(); |
| let root_node = Arc::clone(root_fs.root()); |
| let file = root_node |
| .create_entry( |
| locked, |
| ¤t_task, |
| &mount, |
| "test".into(), |
| |locked, dir, mount, name| { |
| dir.create_node( |
| locked, |
| ¤t_task, |
| mount, |
| name, |
| FileMode::IFREG | FileMode::ALLOW_ALL, |
| DeviceType::NONE, |
| FsCred::root(), |
| ) |
| }, |
| ) |
| .expect("create_node failed"); |
| let file_handle = file |
| .open_anonymous(locked, ¤t_task, OpenFlags::APPEND | OpenFlags::RDWR) |
| .expect("open failed"); |
| let done = Arc::new(AtomicBool::new(false)); |
| |
| let fh = file_handle.clone(); |
| let done_clone = done.clone(); |
| let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| { |
| for i in 0..2000 { |
| fh.write( |
| locked, |
| current_task, |
| &mut VecInputBuffer::new(U64::<LE>::new(i).as_bytes()), |
| ) |
| .expect("write failed"); |
| } |
| done_clone.store(true, Ordering::SeqCst); |
| let result: Result<(), starnix_uapi::errors::Errno> = Ok(()); |
| result |
| }; |
| let (write_thread, req) = |
| SpawnRequestBuilder::new().with_sync_closure(closure).build_with_sync_result(); |
| kernel.kthreads.spawner().spawn_from_request(req); |
| |
| let fh = file_handle.clone(); |
| let done_clone = done.clone(); |
| let closure = move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| { |
| while !done_clone.load(Ordering::SeqCst) { |
| fh.ftruncate(locked, current_task, 0).expect("truncate failed"); |
| } |
| let result: Result<(), starnix_uapi::errors::Errno> = Ok(()); |
| result |
| }; |
| let (truncate_thread, req) = |
| SpawnRequestBuilder::new().with_sync_closure(closure).build_with_sync_result(); |
| kernel.kthreads.spawner().spawn_from_request(req); |
| |
| // If we read from the file, we should always find an increasing sequence. If there are |
| // races, then we might unexpectedly see zeroes. |
| while !done.load(Ordering::SeqCst) { |
| let mut buffer = VecOutputBuffer::new(4096); |
| let amount = file_handle |
| .read_at(locked, ¤t_task, 0, &mut buffer) |
| .expect("read failed"); |
| let mut last = None; |
| let buffer = &Vec::from(buffer)[..amount]; |
| for i in |
| buffer.chunks_exact(8).map(|chunk| U64::<LE>::read_from_bytes(chunk).unwrap()) |
| { |
| if let Some(last) = last { |
| assert!(i.get() > last, "buffer: {:?}", buffer); |
| } |
| last = Some(i.get()); |
| } |
| } |
| |
| let _ = write_thread().unwrap(); |
| let _ = truncate_thread().unwrap(); |
| }) |
| .await; |
| } |
| } |