blob: f6d61a915d5fff5b52904cd238926961c4c6c5c6 [file] [log] [blame]
// 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(&current_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,
&current_task,
&mount,
"test".into(),
|locked, dir, mount, name| {
dir.create_node(
locked,
&current_task,
mount,
name,
FileMode::IFREG | FileMode::ALLOW_ALL,
DeviceType::NONE,
FsCred::root(),
)
},
)
.expect("create_node failed");
let file_handle = file
.open_anonymous(locked, &current_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, &current_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;
}
}