blob: 5ce14cfb676bd01304f222b7b6f3960edb90a38c [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 bitflags::bitflags;
use fuchsia_zircon as zx;
use parking_lot::{Mutex, RwLock};
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use crate::not_implemented;
use crate::task::*;
use crate::uapi::*;
pub use starnix_macros::FileObject;
#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone)]
pub struct FdNumber(i32);
impl FdNumber {
pub fn from_raw(n: i32) -> FdNumber {
FdNumber(n)
}
pub fn raw(&self) -> i32 {
self.0
}
}
/// Corresponds to struct file_operations in Linux, plus any filesystem-specific data.
pub trait FileObject: Deref<Target = FileCommon> {
/// Read from the file without an offset. If your file is seekable, consider implementing this
/// with fd_impl_seekable.
fn read(&self, task: &Task, data: &[iovec_t]) -> Result<usize, Errno>;
/// Read from the file at an offset. If your file is seekable, consider implementing this with
/// fd_impl_nonseekable!.
fn read_at(&self, _task: &Task, _offset: usize, _data: &[iovec_t]) -> Result<usize, Errno>;
/// Write to the file without an offset. If your file is seekable, consider implementing this
/// with fd_impl_seekable!.
fn write(&self, task: &Task, data: &[iovec_t]) -> Result<usize, Errno>;
/// Write to the file at a offset. If your file is nonseekable, consider implementing this with
/// fd_impl_nonseekable!.
fn write_at(&self, _task: &Task, _offset: usize, data: &[iovec_t]) -> Result<usize, Errno>;
/// Responds to an mmap call by returning a VMO. 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.
fn get_vmo(&self, _task: &Task, _prot: zx::VmarFlags, _flags: u32) -> Result<zx::Vmo, Errno> {
Err(ENODEV)
}
// TODO(tbodt): This is actually an operation of the filesystem and not the file descriptor: if
// you open a device file, fstat will go to the filesystem, not to the device. It's only here
// because we don't have such a thing yet. Will need to be moved.
fn fstat(&self, task: &Task) -> Result<stat_t, Errno>;
fn ioctl(
&self,
task: &Task,
request: u32,
in_addr: UserAddress,
out_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
self.deref().ioctl(task, request, in_addr, out_addr)
}
}
/// Implements FileDesc methods in a way that makes sense for nonseekable files. You must implement
/// read and write.
#[macro_export]
macro_rules! fd_impl_nonseekable {
() => {
fn read_at(&self, _task: &Task, _offset: usize, _data: &[iovec_t]) -> Result<usize, Errno> {
Err(ESPIPE)
}
fn write_at(
&self,
_task: &Task,
_offset: usize,
_data: &[iovec_t],
) -> Result<usize, Errno> {
Err(ESPIPE)
}
};
}
/// Implements FileDesc methods in a way that makes sense for seekable files. You must implement
/// read_at and write_at.
#[macro_export]
macro_rules! fd_impl_seekable {
() => {
fn read(&self, task: &Task, data: &[iovec_t]) -> Result<usize, Errno> {
let mut offset = self.offset.lock();
let size = self.read_at(task, *offset, data)?;
*offset += size;
Ok(size)
}
fn write(&self, task: &Task, data: &[iovec_t]) -> Result<usize, Errno> {
let mut offset = self.offset.lock();
let size = self.write_at(task, *offset, data)?;
*offset += size;
Ok(size)
}
};
}
/// Corresponds to struct file in Linux.
#[derive(Default)]
pub struct FileCommon {
pub offset: Mutex<usize>,
pub async_owner: Mutex<pid_t>,
}
impl FileCommon {
/// 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;
}
pub fn ioctl(
&self,
_task: &Task,
request: u32,
_in_addr: UserAddress,
_out_addr: UserAddress,
) -> Result<SyscallResult, Errno> {
not_implemented!("ioctl: request=0x{:x}", request);
Err(ENOTTY)
}
}
pub type FileHandle = Arc<dyn FileObject + Send + Sync>;
bitflags! {
pub struct FdFlags: u32 {
const CLOEXEC = FD_CLOEXEC;
}
}
struct FdTableEntry {
file: FileHandle,
// Rather than using a separate "flags" field, we could maintain this data
// as a bitfield over the file descriptors because there is only one flag
// currently (CLOEXEC) and file descriptor numbers tend to cluster near 0.
flags: FdFlags,
}
pub struct FdTable {
table: RwLock<HashMap<FdNumber, FdTableEntry>>,
}
impl FdTable {
pub fn new() -> FdTable {
FdTable { table: RwLock::new(HashMap::new()) }
}
pub fn install_fd(&self, file: FileHandle) -> Result<FdNumber, Errno> {
let mut table = self.table.write();
let mut fd = FdNumber::from_raw(0);
while table.contains_key(&fd) {
fd = FdNumber::from_raw(fd.raw() + 1);
}
table.insert(fd, FdTableEntry { file, flags: FdFlags::empty() });
Ok(fd)
}
pub fn get(&self, fd: FdNumber) -> Result<FileHandle, Errno> {
let table = self.table.read();
table.get(&fd).map(|entry| entry.file.clone()).ok_or_else(|| EBADF)
}
pub fn get_flags(&self, fd: FdNumber) -> Result<FdFlags, Errno> {
let table = self.table.read();
table.get(&fd).map(|entry| entry.flags).ok_or_else(|| EBADF)
}
pub fn set_flags(&self, fd: FdNumber, flags: FdFlags) -> Result<(), Errno> {
let mut table = self.table.write();
table
.get_mut(&fd)
.map(|entry| {
entry.flags = flags;
})
.ok_or_else(|| EBADF)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::fs::SyslogFile;
#[test]
fn test_fd_table_install() {
let table = FdTable::new();
let test_file = SyslogFile::new();
let fd_num = table.install_fd(test_file.clone()).unwrap();
assert_eq!(fd_num.raw(), 0);
let fd_num2 = table.install_fd(test_file.clone()).unwrap();
assert_eq!(fd_num2.raw(), 1);
assert!(Arc::ptr_eq(&table.get(fd_num).unwrap(), &test_file));
assert!(Arc::ptr_eq(&table.get(fd_num2).unwrap(), &test_file));
assert_eq!(table.get(FdNumber::from_raw(fd_num2.raw() + 1)).map(|_| ()), Err(EBADF));
}
}