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