914 lines
29 KiB
Rust
914 lines
29 KiB
Rust
use core::{
|
|
any::Any,
|
|
fmt,
|
|
mem::MaybeUninit,
|
|
task::{Context, Poll},
|
|
};
|
|
|
|
use alloc::{
|
|
boxed::Box,
|
|
collections::{btree_map::Entry, BTreeMap},
|
|
sync::Arc,
|
|
};
|
|
use async_trait::async_trait;
|
|
use device::{BlockFile, CharFile};
|
|
use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider};
|
|
use libk_util::{
|
|
io::{ReadAt, WriteAt},
|
|
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
|
};
|
|
use yggdrasil_abi::{
|
|
error::Error,
|
|
io::{
|
|
options::{self, FileOptionVariant},
|
|
DirectoryEntry, FileMode, OpenOptions, RawFd, SeekFrom, TerminalOptions, TerminalSize,
|
|
TimerOptions,
|
|
},
|
|
net::{MessageHeader, MessageHeaderMut},
|
|
option::{OptionValue, RequestValue},
|
|
process::{ProcessGroupId, ProcessWait, WaitFlags},
|
|
};
|
|
|
|
use crate::{
|
|
device::{block::BlockDeviceFile, char::CharDeviceFile},
|
|
task::process::Process,
|
|
vfs::{
|
|
node::NodeRef,
|
|
traits::{Read, Seek, Write},
|
|
FdPoll, FileReadiness, Node, SharedMemory, TimerFile,
|
|
},
|
|
};
|
|
|
|
use self::{directory::DirectoryFile, pipe::PipeEnd, regular::RegularFile};
|
|
|
|
use super::{
|
|
pid::PidFile,
|
|
pty::{self, PseudoTerminalMaster, PseudoTerminalSlave},
|
|
socket::SocketWrapper,
|
|
Metadata,
|
|
};
|
|
|
|
mod device;
|
|
mod directory;
|
|
mod pipe;
|
|
mod regular;
|
|
|
|
/// Per-file optional instance data created when a regular file is opened
|
|
pub type InstanceData = Arc<dyn Any + Send + Sync>;
|
|
|
|
/// Describes the starting position of the directory
|
|
pub enum DirectoryOpenPosition {
|
|
/// Contents should be fetched from the directory impl with given offset
|
|
FromPhysical(u64),
|
|
/// Contents should be fetched from the tree cache
|
|
FromCache,
|
|
}
|
|
|
|
/// Wrapper type for a [File] shared reference
|
|
pub type FileRef = Arc<File>;
|
|
|
|
#[derive(Clone)]
|
|
struct FileOptions {
|
|
non_blocking: bool,
|
|
}
|
|
|
|
pub struct File {
|
|
options: IrqSafeRwLock<FileOptions>,
|
|
inner: FileInner,
|
|
}
|
|
|
|
enum FileInner {
|
|
Directory(DirectoryFile),
|
|
Regular(RegularFile),
|
|
Block(BlockFile),
|
|
Char(CharFile),
|
|
Socket(SocketWrapper),
|
|
AnonymousPipe(PipeEnd),
|
|
Poll(FdPoll),
|
|
Timer(TimerFile),
|
|
SharedMemory(Arc<SharedMemory>),
|
|
PtySlave(TerminalHalfWrapper<PseudoTerminalSlave>),
|
|
PtyMaster(TerminalHalfWrapper<PseudoTerminalMaster>),
|
|
Pid(PidFile),
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait TerminalHalf {
|
|
async fn read(&self, buf: &mut [u8]) -> Result<usize, Error>;
|
|
fn read_nonblocking(&self, buf: &mut [u8]) -> Result<usize, Error>;
|
|
fn write(&self, buf: &[u8]) -> Result<usize, Error>;
|
|
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>>;
|
|
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error>;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct TerminalHalfWrapper<T: TerminalHalf> {
|
|
half: Arc<T>,
|
|
node: NodeRef,
|
|
}
|
|
|
|
/// Contains a per-process fd -> FileRef map
|
|
pub struct FileSet {
|
|
map: BTreeMap<RawFd, FileRef>,
|
|
}
|
|
|
|
impl File {
|
|
fn from_inner(inner: FileInner) -> Arc<Self> {
|
|
Arc::new(Self {
|
|
options: IrqSafeRwLock::new(FileOptions {
|
|
non_blocking: false,
|
|
}),
|
|
inner,
|
|
})
|
|
}
|
|
|
|
/// Constructs a pipe pair, returning its `(read, write)` ends
|
|
pub fn new_pipe_pair(capacity: usize) -> (Arc<Self>, Arc<Self>) {
|
|
let (read, write) = PipeEnd::new_pair(capacity);
|
|
let read = FileInner::AnonymousPipe(read);
|
|
let write = FileInner::AnonymousPipe(write);
|
|
(Self::from_inner(read), Self::from_inner(write))
|
|
}
|
|
|
|
/// Constructs a new poll channel file
|
|
pub fn new_poll_channel() -> Arc<Self> {
|
|
Self::from_inner(FileInner::Poll(FdPoll::new()))
|
|
}
|
|
|
|
/// Creates a buffer of shared memory and associates a [File] with it
|
|
pub fn new_shared_memory(size: usize) -> Result<Arc<Self>, Error> {
|
|
let shm = SharedMemory::new(size)?;
|
|
Ok(Self::from_inner(FileInner::SharedMemory(Arc::new(shm))))
|
|
}
|
|
|
|
/// Creates a pair of PTY master/slave
|
|
pub fn new_pseudo_terminal(
|
|
config: TerminalOptions,
|
|
size: TerminalSize,
|
|
) -> Result<(Arc<Self>, Arc<Self>), Error> {
|
|
let (master, slave) = pty::create(config, size)?;
|
|
let master = Arc::new(master);
|
|
let slave = Arc::new(slave);
|
|
let (master_node, slave_node) =
|
|
Node::pseudo_terminal_nodes(&master, &slave, Metadata::now_root(FileMode::new(0o644)));
|
|
|
|
let master = Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper {
|
|
half: master,
|
|
node: master_node,
|
|
}));
|
|
let slave = Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper {
|
|
half: slave,
|
|
node: slave_node,
|
|
}));
|
|
Ok((master, slave))
|
|
}
|
|
|
|
pub fn open_pty_master(pty: Arc<PseudoTerminalMaster>, node: Arc<Node>) -> Arc<Self> {
|
|
Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper {
|
|
half: pty,
|
|
node,
|
|
}))
|
|
}
|
|
|
|
pub fn open_pty_slave(pty: Arc<PseudoTerminalSlave>, node: Arc<Node>) -> Arc<Self> {
|
|
Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper { half: pty, node }))
|
|
}
|
|
|
|
/// Creates a new [TimerFile]-backed File
|
|
pub fn new_timer(options: TimerOptions) -> FileRef {
|
|
let repeat = options.contains(TimerOptions::REPEAT);
|
|
let timer = TimerFile::new(repeat);
|
|
Self::from_inner(FileInner::Timer(timer))
|
|
}
|
|
|
|
/// Creates a new [PidFile]-backed file
|
|
pub fn new_pid(
|
|
parent: Arc<Process>,
|
|
wait: &ProcessWait,
|
|
flags: WaitFlags,
|
|
) -> Result<FileRef, Error> {
|
|
PidFile::new(parent, wait, flags)
|
|
.map(FileInner::Pid)
|
|
.map(Self::from_inner)
|
|
}
|
|
|
|
/// Constructs a [File] from a [PacketSocket], [ConnectionSocket] or a [ListenerSocket].
|
|
pub fn from_socket<S: Into<SocketWrapper>>(socket: S) -> Arc<Self> {
|
|
Self::from_inner(FileInner::Socket(socket.into()))
|
|
}
|
|
|
|
pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
|
|
let position = IrqSafeSpinlock::new(position.into());
|
|
Self::from_inner(FileInner::Directory(DirectoryFile { node, position }))
|
|
}
|
|
|
|
pub(crate) fn regular(
|
|
node: NodeRef,
|
|
position: u64,
|
|
instance_data: Option<InstanceData>,
|
|
opts: OpenOptions,
|
|
) -> Arc<Self> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
Self::from_inner(FileInner::Regular(RegularFile {
|
|
node,
|
|
read,
|
|
write,
|
|
instance_data,
|
|
position: IrqSafeSpinlock::new(position),
|
|
}))
|
|
}
|
|
|
|
pub(crate) fn block(
|
|
device: BlockDeviceFile,
|
|
node: NodeRef,
|
|
opts: OpenOptions,
|
|
) -> Result<Arc<Self>, Error> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
if read && !device.is_readable() {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
if write && !device.is_writeable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Self::from_inner(FileInner::Block(BlockFile {
|
|
device,
|
|
node,
|
|
position: IrqSafeSpinlock::new(0),
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
pub(crate) fn char(
|
|
device: CharDeviceFile,
|
|
node: NodeRef,
|
|
opts: OpenOptions,
|
|
) -> Result<Arc<Self>, Error> {
|
|
let read = opts.contains(OpenOptions::READ);
|
|
let write = opts.contains(OpenOptions::WRITE);
|
|
|
|
if read && !device.is_readable() {
|
|
return Err(Error::InvalidOperation);
|
|
}
|
|
if write && !device.is_writeable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Self::from_inner(FileInner::Char(CharFile {
|
|
device,
|
|
node,
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
pub fn is_non_blocking(&self) -> bool {
|
|
self.options.read().non_blocking
|
|
}
|
|
|
|
/// Clones an open file for sending it to another process
|
|
pub fn send(self: &Arc<Self>) -> Result<Arc<Self>, Error> {
|
|
let options = self.options.read().clone();
|
|
Ok(Arc::new(Self {
|
|
inner: self.inner.send()?,
|
|
options: IrqSafeRwLock::new(options),
|
|
}))
|
|
}
|
|
|
|
pub fn set_option(&self, option: u32, buffer: &[u8]) -> Result<(), Error> {
|
|
let option = FileOptionVariant::try_from(option)?;
|
|
let mut options = self.options.write();
|
|
match option {
|
|
FileOptionVariant::NonBlocking => {
|
|
options.non_blocking = options::NonBlocking::load(buffer)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_option(&self, option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
|
|
let option = FileOptionVariant::try_from(option)?;
|
|
let options = self.options.read();
|
|
match option {
|
|
FileOptionVariant::NonBlocking => {
|
|
options::NonBlocking::store(&options.non_blocking, buffer)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reads entries from the directory
|
|
pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
|
|
match &self.inner {
|
|
FileInner::Directory(dir) => dir.read_entries(entries),
|
|
_ => Err(Error::NotADirectory),
|
|
}
|
|
}
|
|
|
|
/// Returns the underlying [Node] the file contains
|
|
pub fn node(&self) -> Option<&NodeRef> {
|
|
match &self.inner {
|
|
FileInner::Directory(file) => Some(&file.node),
|
|
FileInner::Regular(file) => Some(&file.node),
|
|
FileInner::Block(file) => Some(&file.node),
|
|
FileInner::Char(file) => Some(&file.node),
|
|
FileInner::PtyMaster(half) => Some(&half.node),
|
|
FileInner::PtySlave(half) => Some(&half.node),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Polls a file for "read-readiness"
|
|
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
|
match &self.inner {
|
|
FileInner::Char(f) => f.device.poll_read(cx),
|
|
FileInner::Poll(ch) => ch.poll_read(cx),
|
|
FileInner::PtyMaster(half) => half.half.poll_read(cx),
|
|
FileInner::PtySlave(half) => half.half.poll_read(cx),
|
|
FileInner::Socket(socket) => socket.poll_read(cx),
|
|
FileInner::Timer(timer) => timer.poll_read(cx),
|
|
FileInner::Pid(pid) => pid.poll_read(cx),
|
|
FileInner::AnonymousPipe(pipe) => pipe.poll_read(cx),
|
|
// Polling not implemented, return ready immediately (XXX ?)
|
|
_ => Poll::Ready(Err(Error::NotImplemented)),
|
|
}
|
|
}
|
|
|
|
/// Performs a device-specific request
|
|
pub fn device_request(
|
|
&self,
|
|
option: u32,
|
|
buffer: &mut [u8],
|
|
len: usize,
|
|
) -> Result<usize, Error> {
|
|
match &self.inner {
|
|
FileInner::Char(f) => f.device.device_request(option, buffer, len),
|
|
FileInner::Block(f) => f.device.device_request(option, buffer, len),
|
|
FileInner::PtySlave(half) => half.half.device_request(option, buffer, len),
|
|
FileInner::PtyMaster(half) => half.half.device_request(option, buffer, len),
|
|
_ => {
|
|
log::warn!("device_request {option:#x}: not a device");
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Interprets the file as a poll channel
|
|
pub fn as_poll_channel(&self) -> Result<&FdPoll, Error> {
|
|
if let FileInner::Poll(poll) = &self.inner {
|
|
Ok(poll)
|
|
} else {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
|
|
/// Interprets the file as a socket
|
|
pub fn as_socket(&self) -> Result<&SocketWrapper, Error> {
|
|
match &self.inner {
|
|
FileInner::Socket(socket) => Ok(socket),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
pub fn is_terminal(&self) -> bool {
|
|
match &self.inner {
|
|
FileInner::Char(dev) => dev.is_terminal(),
|
|
FileInner::PtySlave(_) | FileInner::PtyMaster(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn set_terminal_group(&self, group_id: ProcessGroupId) -> Result<(), Error> {
|
|
use yggdrasil_abi::io::device as abi_device;
|
|
|
|
let mut buffer = [0; 8];
|
|
let len = abi_device::SetTerminalGroup::store_request(&group_id, &mut buffer)?;
|
|
self.device_request(
|
|
abi_device::SetTerminalGroup::VARIANT.into(),
|
|
&mut buffer,
|
|
len,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl PageProvider for File {
|
|
fn get_page(&self, offset: u64) -> Result<PhysicalAddress, Error> {
|
|
match &self.inner {
|
|
FileInner::Block(f) => f.device.get_page(offset),
|
|
FileInner::SharedMemory(f) => f.get_page(offset),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> {
|
|
match &self.inner {
|
|
FileInner::Block(f) => f.device.release_page(offset, phys),
|
|
FileInner::SharedMemory(f) => f.release_page(offset, phys),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
fn clone_page(
|
|
&self,
|
|
_offset: u64,
|
|
_src_phys: PhysicalAddress,
|
|
_src_attrs: MapAttributes,
|
|
) -> Result<PhysicalAddress, Error> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl Read for File {
|
|
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
|
let non_blocking = self.options.read().non_blocking;
|
|
match &self.inner {
|
|
FileInner::Regular(file) => file.read(buf),
|
|
FileInner::Block(file) => file.read(buf),
|
|
FileInner::Char(file) => file.read(buf, non_blocking),
|
|
FileInner::AnonymousPipe(pipe) => pipe.read(buf, non_blocking),
|
|
FileInner::PtySlave(half) => half.read(buf, non_blocking),
|
|
FileInner::PtyMaster(half) => half.read(buf, non_blocking),
|
|
FileInner::Timer(timer) => timer.read(buf, non_blocking),
|
|
FileInner::Pid(pid) => pid.read(buf, non_blocking),
|
|
// TODO allow reads from sockets
|
|
FileInner::Socket(socket) => {
|
|
let mut header = MessageHeaderMut {
|
|
source: None,
|
|
ancillary: None,
|
|
ancillary_len: 0,
|
|
payload: buf,
|
|
};
|
|
socket.receive_message(&mut header, non_blocking)
|
|
}
|
|
FileInner::Directory(_) | FileInner::Poll(_) | FileInner::SharedMemory(_) => {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Write for File {
|
|
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
|
let non_blocking = self.options.read().non_blocking;
|
|
match &self.inner {
|
|
FileInner::Regular(file) => file.write(buf),
|
|
FileInner::Block(file) => file.write(buf),
|
|
FileInner::Char(file) => file.write(buf, non_blocking),
|
|
FileInner::AnonymousPipe(pipe) => pipe.write(buf),
|
|
FileInner::PtySlave(half) => half.write(buf),
|
|
FileInner::PtyMaster(half) => half.write(buf),
|
|
FileInner::Timer(timer) => timer.write(buf),
|
|
FileInner::Socket(socket) => {
|
|
let header = MessageHeader {
|
|
destination: None,
|
|
payload: buf,
|
|
ancillary: &[],
|
|
};
|
|
socket.send_message(&header, non_blocking)
|
|
}
|
|
FileInner::Pid(_)
|
|
| FileInner::Poll(_)
|
|
| FileInner::SharedMemory(_)
|
|
| FileInner::Directory(_) => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ReadAt for File {
|
|
fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
|
|
// let non_blocking = self.options.read().non_blocking;
|
|
match &self.inner {
|
|
FileInner::Regular(file) => file.read_at(pos, buf),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WriteAt for File {
|
|
fn write_at(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
|
|
// let non_blocking = self.options.read().non_blocking;
|
|
match &self.inner {
|
|
FileInner::Regular(file) => file.write_at(pos, buf),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Seek for File {
|
|
fn tell(&self) -> Result<u64, Error> {
|
|
match &self.inner {
|
|
FileInner::Regular(file) => Ok(*file.position.lock()),
|
|
FileInner::Block(file) => Ok(*file.position.lock()),
|
|
FileInner::Directory(_) => Err(Error::IsADirectory),
|
|
_ => Ok(0),
|
|
}
|
|
}
|
|
|
|
fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
|
if from == SeekFrom::Current(0) {
|
|
return self.tell();
|
|
}
|
|
|
|
match &self.inner {
|
|
FileInner::Regular(file) => file.seek(from),
|
|
FileInner::Block(file) => file.seek(from),
|
|
FileInner::Directory(_) => Err(Error::IsADirectory),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for File {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match &self.inner {
|
|
FileInner::Regular(file) => f
|
|
.debug_struct("RegularFile")
|
|
.field("position", &*file.position.lock())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
FileInner::Block(file) => f
|
|
.debug_struct("BlockFile")
|
|
.field("position", &*file.position.lock())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
FileInner::Char(file) => f
|
|
.debug_struct("CharFile")
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
FileInner::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
|
|
FileInner::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(),
|
|
FileInner::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(),
|
|
FileInner::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(),
|
|
FileInner::PtySlave(_) => f.debug_struct("PtySlave").finish_non_exhaustive(),
|
|
FileInner::PtyMaster(_) => f.debug_struct("PtyMaster").finish_non_exhaustive(),
|
|
FileInner::Socket(socket) => fmt::Debug::fmt(socket, f),
|
|
FileInner::Pid(pid) => fmt::Debug::fmt(pid, f),
|
|
FileInner::Timer(_) => f.debug_struct("Timer").finish_non_exhaustive(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FileInner {
|
|
pub fn send(&self) -> Result<Self, Error> {
|
|
match self {
|
|
Self::Char(char) => Ok(Self::Char(char.clone())),
|
|
Self::Regular(file) => Ok(Self::Regular(file.clone())),
|
|
Self::SharedMemory(shm) => Ok(Self::SharedMemory(shm.clone())),
|
|
Self::Pid(pid) => Ok(Self::Pid(pid.clone())),
|
|
Self::PtySlave(half) => Ok(Self::PtySlave(half.clone())),
|
|
Self::PtyMaster(half) => Ok(Self::PtyMaster(half.clone())),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: TerminalHalf> TerminalHalfWrapper<T> {
|
|
pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result<usize, Error> {
|
|
if non_blocking {
|
|
self.half.read_nonblocking(buf)
|
|
} else {
|
|
block!(self.half.read(buf).await)?
|
|
}
|
|
}
|
|
|
|
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
|
self.half.write(buf)
|
|
}
|
|
}
|
|
|
|
impl FileSet {
|
|
/// Creates an empty [FileSet]
|
|
pub fn new() -> Self {
|
|
Self {
|
|
map: BTreeMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Returns the [FileRef] associated with given `fd` or an error if it does not exist
|
|
pub fn file(&self, fd: RawFd) -> Result<&FileRef, Error> {
|
|
self.map.get(&fd).ok_or(Error::InvalidFile)
|
|
}
|
|
|
|
/// Associates a `file` with `fd`, returning an error if requested `fd` is already taken
|
|
pub fn set_file(&mut self, fd: RawFd, file: FileRef) -> Result<(), Error> {
|
|
if self.map.contains_key(&fd) {
|
|
return Err(Error::AlreadyExists);
|
|
}
|
|
self.map.insert(fd, file);
|
|
Ok(())
|
|
}
|
|
|
|
/// Associates a `file` with any available [RawFd] and returns it
|
|
pub fn place_file(&mut self, file: FileRef, skip_stdio: bool) -> Result<RawFd, Error> {
|
|
let start = if skip_stdio { 3 } else { 0 };
|
|
for idx in start..64 {
|
|
let fd = RawFd::from(idx);
|
|
|
|
if let Entry::Vacant(e) = self.map.entry(fd) {
|
|
e.insert(file);
|
|
return Ok(fd);
|
|
}
|
|
}
|
|
|
|
// TODO OutOfFiles
|
|
Err(Error::OutOfMemory)
|
|
}
|
|
|
|
/// Removes and closes a [FileRef] from the struct
|
|
pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> {
|
|
// Do nothing, file will be dropped and closed
|
|
// TODO call File's close() and return its status
|
|
let _ = self.take_file(fd)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn take_file(&mut self, fd: RawFd) -> Result<FileRef, Error> {
|
|
self.map.remove(&fd).ok_or(Error::InvalidFile)
|
|
}
|
|
|
|
/// Removes all [FileRef]s from the struct which do not pass the `predicate` check
|
|
pub fn retain<F: Fn(&RawFd, &mut FileRef) -> bool>(&mut self, predicate: F) {
|
|
self.map.retain(predicate);
|
|
}
|
|
|
|
/// Returns an iterator over the file set
|
|
pub fn iter(&self) -> impl Iterator<Item = (&RawFd, &FileRef)> {
|
|
self.map.iter()
|
|
}
|
|
|
|
/// Closes all of the files
|
|
pub fn close_all(&mut self) {
|
|
self.map.clear();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use core::{
|
|
mem::MaybeUninit,
|
|
str::FromStr,
|
|
task::{Context, Poll},
|
|
};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use async_trait::async_trait;
|
|
use yggdrasil_abi::{
|
|
error::Error,
|
|
io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
|
|
util::FixedString,
|
|
};
|
|
|
|
use crate::vfs::{
|
|
file::DirectoryOpenPosition,
|
|
impls::const_value_node,
|
|
node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
|
|
traits::{Read, Seek, Write},
|
|
FileReadiness, Filename, InstanceData,
|
|
};
|
|
|
|
#[test]
|
|
fn file_send_sync() {
|
|
fn file_send<T: Send>(_f: &T) {}
|
|
fn file_sync<T: Sync>(_f: &T) {}
|
|
|
|
let node = const_value_node("1234");
|
|
let file = node
|
|
.open(OpenOptions::READ, AccessToken::test_authorized())
|
|
.unwrap();
|
|
|
|
file_send(&file);
|
|
file_sync(&file);
|
|
}
|
|
|
|
#[test]
|
|
fn physical_dir_read() {
|
|
struct D {
|
|
entries: Vec<(String, NodeRef)>,
|
|
}
|
|
struct F;
|
|
|
|
impl CommonImpl for D {}
|
|
impl DirectoryImpl for D {
|
|
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
|
Ok(DirectoryOpenPosition::FromPhysical(0))
|
|
}
|
|
|
|
fn read_entries(
|
|
&self,
|
|
_node: &NodeRef,
|
|
pos: u64,
|
|
entries: &mut [MaybeUninit<DirectoryEntry>],
|
|
) -> Result<(usize, u64), Error> {
|
|
let pos = pos as usize;
|
|
if pos == self.entries.len() {
|
|
return Ok((0, pos as u64));
|
|
}
|
|
|
|
let count = core::cmp::min(entries.len(), self.entries.len() - pos);
|
|
for i in 0..count {
|
|
let (name, node) = &self.entries[i];
|
|
let entry = DirectoryEntry {
|
|
name: FixedString::from_str(name)?,
|
|
ty: Some(node.ty()),
|
|
};
|
|
|
|
entries[i].write(entry);
|
|
}
|
|
|
|
Ok((count, (pos + count) as u64))
|
|
}
|
|
}
|
|
|
|
impl CommonImpl for F {}
|
|
impl RegularImpl for F {}
|
|
|
|
let d = Node::directory(
|
|
D {
|
|
entries: Vec::from_iter([
|
|
(
|
|
"f1".to_owned(),
|
|
Node::regular(F, NodeFlags::empty(), None, None),
|
|
),
|
|
(
|
|
"f2".to_owned(),
|
|
Node::regular(F, NodeFlags::empty(), None, None),
|
|
),
|
|
(
|
|
"f3".to_owned(),
|
|
Node::regular(F, NodeFlags::empty(), None, None),
|
|
),
|
|
]),
|
|
},
|
|
NodeFlags::empty(),
|
|
None,
|
|
None,
|
|
);
|
|
|
|
let f = d.open_directory(AccessToken::test_authorized()).unwrap();
|
|
|
|
let mut entries = [MaybeUninit::uninit(); 16];
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 3);
|
|
|
|
unsafe {
|
|
assert_eq!(
|
|
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
|
&[
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f1").unwrap(),
|
|
ty: Some(FileType::File),
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f2").unwrap(),
|
|
ty: Some(FileType::File),
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f3").unwrap(),
|
|
ty: Some(FileType::File)
|
|
}
|
|
]
|
|
);
|
|
}
|
|
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cache_dir_read() {
|
|
struct D;
|
|
|
|
impl CommonImpl for D {}
|
|
impl DirectoryImpl for D {
|
|
fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
|
|
Ok(DirectoryOpenPosition::FromCache)
|
|
}
|
|
}
|
|
|
|
let d = Node::directory(D, NodeFlags::empty(), None, None);
|
|
let child = Node::directory(D, NodeFlags::empty(), None, None);
|
|
|
|
d.add_child(Filename::new("child1").unwrap(), child)
|
|
.unwrap();
|
|
|
|
let f = d.open_directory(AccessToken::test_authorized()).unwrap();
|
|
|
|
let mut entries = [MaybeUninit::uninit(); 16];
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 3);
|
|
unsafe {
|
|
assert_eq!(
|
|
MaybeUninit::slice_assume_init_ref(&entries[..count]),
|
|
&[
|
|
DirectoryEntry {
|
|
name: FixedString::from_str(".").unwrap(),
|
|
ty: Some(FileType::Directory)
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("..").unwrap(),
|
|
ty: Some(FileType::Directory)
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("child1").unwrap(),
|
|
ty: Some(FileType::Directory)
|
|
}
|
|
]
|
|
);
|
|
}
|
|
|
|
let count = f.read_dir(&mut entries).unwrap();
|
|
assert_eq!(count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn file_read_write() {
|
|
struct F {
|
|
data: Arc<Mutex<Vec<u8>>>,
|
|
}
|
|
|
|
impl CommonImpl for F {
|
|
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
|
|
Ok(self.data.lock().unwrap().len() as _)
|
|
}
|
|
}
|
|
impl RegularImpl for F {
|
|
fn open(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_opts: OpenOptions,
|
|
) -> Result<(u64, Option<InstanceData>), Error> {
|
|
Ok((0, None))
|
|
}
|
|
|
|
fn read(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_instance: Option<&InstanceData>,
|
|
pos: u64,
|
|
buf: &mut [u8],
|
|
) -> Result<usize, Error> {
|
|
let pos = pos as usize;
|
|
let data = self.data.lock().unwrap();
|
|
if pos >= data.len() {
|
|
return Ok(0);
|
|
}
|
|
let count = core::cmp::min(data.len() - pos, buf.len());
|
|
buf[..count].copy_from_slice(&data[pos..pos + count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn write(
|
|
&self,
|
|
_node: &NodeRef,
|
|
_instance: Option<&InstanceData>,
|
|
pos: u64,
|
|
buf: &[u8],
|
|
) -> Result<usize, Error> {
|
|
let pos = pos as usize;
|
|
let mut data = self.data.lock().unwrap();
|
|
data.resize(pos + buf.len(), 0);
|
|
data[pos..pos + buf.len()].copy_from_slice(buf);
|
|
Ok(buf.len())
|
|
}
|
|
}
|
|
|
|
let data = Arc::new(Mutex::new(vec![]));
|
|
let node = Node::regular(F { data: data.clone() }, NodeFlags::empty(), None, None);
|
|
let file = node
|
|
.open(
|
|
OpenOptions::READ | OpenOptions::WRITE,
|
|
AccessToken::test_authorized(),
|
|
)
|
|
.unwrap();
|
|
let mut buf = [0; 512];
|
|
|
|
assert_eq!(&*data.lock().unwrap(), b"");
|
|
assert_eq!(file.tell().unwrap(), 0);
|
|
|
|
assert_eq!(file.write(b"Hello").unwrap(), 5);
|
|
assert_eq!(file.tell().unwrap(), 5);
|
|
assert_eq!(&*data.lock().unwrap(), b"Hello");
|
|
|
|
assert_eq!(file.seek(SeekFrom::End(-2)).unwrap(), 3);
|
|
assert_eq!(file.tell().unwrap(), 3);
|
|
|
|
assert_eq!(file.write(b"123456").unwrap(), 6);
|
|
assert_eq!(file.tell().unwrap(), 9);
|
|
assert_eq!(&*data.lock().unwrap(), b"Hel123456");
|
|
|
|
assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
|
|
assert_eq!(file.read(&mut buf).unwrap(), 7);
|
|
assert_eq!(file.tell().unwrap(), 9);
|
|
assert_eq!(&buf[..7], b"l123456");
|
|
}
|
|
}
|