860 lines
28 KiB
Rust
860 lines
28 KiB
Rust
use core::{
|
|
any::Any,
|
|
fmt,
|
|
mem::MaybeUninit,
|
|
task::{Context, Poll},
|
|
};
|
|
|
|
use alloc::{
|
|
collections::{btree_map::Entry, BTreeMap},
|
|
sync::Arc,
|
|
};
|
|
use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider};
|
|
use libk_util::sync::IrqSafeSpinlock;
|
|
use yggdrasil_abi::{
|
|
error::Error,
|
|
io::{
|
|
DeviceRequest, DirectoryEntry, OpenOptions, RawFd, SeekFrom, TerminalOptions, TerminalSize,
|
|
},
|
|
net::SocketAddr,
|
|
};
|
|
|
|
use crate::vfs::{
|
|
channel::ChannelDescriptor,
|
|
device::{BlockDeviceWrapper, CharDeviceWrapper},
|
|
node::NodeRef,
|
|
socket::{ConnectionSocketWrapper, ListenerSocketWrapper, PacketSocketWrapper},
|
|
traits::{Read, Seek, Write},
|
|
ConnectionSocket, FdPoll, FileReadiness, ListenerSocket, Node, PacketSocket,
|
|
PseudoTerminalMaster, PseudoTerminalSlave, SharedMemory, Socket, TimerFile,
|
|
};
|
|
|
|
use self::{
|
|
device::{BlockFile, CharFile},
|
|
directory::DirectoryFile,
|
|
pipe::PipeEnd,
|
|
regular::RegularFile,
|
|
};
|
|
|
|
use super::pty;
|
|
|
|
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>;
|
|
|
|
// TODO some kind of a mutex instead?
|
|
/// Describes an open file
|
|
#[allow(missing_docs)]
|
|
pub enum File {
|
|
Directory(DirectoryFile),
|
|
Regular(RegularFile),
|
|
Block(BlockFile),
|
|
Char(CharFile),
|
|
|
|
PacketSocket(Arc<PacketSocketWrapper>),
|
|
ListenerSocket(Arc<ListenerSocketWrapper>),
|
|
StreamSocket(Arc<ConnectionSocketWrapper>),
|
|
|
|
AnonymousPipe(PipeEnd),
|
|
Poll(FdPoll),
|
|
Timer(TimerFile),
|
|
Channel(ChannelDescriptor),
|
|
SharedMemory(Arc<SharedMemory>),
|
|
PtySlave(Arc<PseudoTerminalSlave>, NodeRef),
|
|
PtyMaster(Arc<PseudoTerminalMaster>, NodeRef),
|
|
}
|
|
|
|
/// Contains a per-process fd -> FileRef map
|
|
pub struct FileSet {
|
|
map: BTreeMap<RawFd, FileRef>,
|
|
}
|
|
|
|
impl File {
|
|
/// 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);
|
|
(
|
|
Arc::new(Self::AnonymousPipe(read)),
|
|
Arc::new(Self::AnonymousPipe(write)),
|
|
)
|
|
}
|
|
|
|
/// Constructs a new poll channel file
|
|
pub fn new_poll_channel() -> Arc<Self> {
|
|
Arc::new(Self::Poll(FdPoll::new()))
|
|
}
|
|
|
|
/// Opens a new message channel, optionally subscribing to it as well
|
|
pub fn new_message_channel(name: &str, with_sub: bool) -> Arc<Self> {
|
|
let channel = ChannelDescriptor::open(name, with_sub);
|
|
Arc::new(Self::Channel(channel))
|
|
}
|
|
|
|
/// 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(Arc::new(Self::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.clone(), slave.clone());
|
|
Ok((
|
|
Arc::new(Self::PtyMaster(master, master_node)),
|
|
Arc::new(Self::PtySlave(slave, slave_node)),
|
|
))
|
|
}
|
|
|
|
/// Creates a new [TimerFile]-backed File
|
|
pub fn new_timer(repeat: bool) -> FileRef {
|
|
Arc::new(Self::Timer(TimerFile::new(repeat)))
|
|
}
|
|
|
|
/// Constructs a [File] from a [PacketSocket]
|
|
pub fn from_packet_socket(socket: Arc<dyn PacketSocket>) -> Arc<Self> {
|
|
Arc::new(Self::PacketSocket(Arc::new(PacketSocketWrapper(socket))))
|
|
}
|
|
|
|
/// Constructs a [File] from a [ListenerSocket]
|
|
pub fn from_listener_socket(socket: Arc<dyn ListenerSocket>) -> Arc<Self> {
|
|
Arc::new(Self::ListenerSocket(Arc::new(ListenerSocketWrapper(
|
|
socket,
|
|
))))
|
|
}
|
|
|
|
/// Constructs a [File] from a [ConnectionSocket]
|
|
pub fn from_stream_socket(socket: Arc<dyn ConnectionSocket>) -> Arc<Self> {
|
|
Arc::new(Self::StreamSocket(Arc::new(ConnectionSocketWrapper(
|
|
socket,
|
|
))))
|
|
}
|
|
|
|
pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
|
|
let position = IrqSafeSpinlock::new(position.into());
|
|
Arc::new(Self::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);
|
|
|
|
Arc::new(Self::Regular(RegularFile {
|
|
node,
|
|
read,
|
|
write,
|
|
instance_data,
|
|
position: IrqSafeSpinlock::new(position),
|
|
}))
|
|
}
|
|
|
|
pub(crate) fn block(
|
|
device: BlockDeviceWrapper,
|
|
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_writable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Arc::new(Self::Block(BlockFile {
|
|
device,
|
|
node,
|
|
position: IrqSafeSpinlock::new(0),
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
pub(crate) fn char(
|
|
device: CharDeviceWrapper,
|
|
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_writable() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
Ok(Arc::new(Self::Char(CharFile {
|
|
device,
|
|
node,
|
|
read,
|
|
write,
|
|
})))
|
|
}
|
|
|
|
/// Clones an open file for sending it to another process
|
|
pub fn send(self: &Arc<Self>) -> Result<Arc<Self>, Error> {
|
|
match self.as_ref() {
|
|
Self::Char(_) => Ok(self.clone()),
|
|
Self::Block(_) => todo!(),
|
|
Self::Regular(file) => Ok(Arc::new(Self::Regular(file.clone()))),
|
|
Self::SharedMemory(shm) => Ok(Arc::new(Self::SharedMemory(shm.clone()))),
|
|
Self::PtySlave(pt, pt_node) => {
|
|
Ok(Arc::new(Self::PtySlave(pt.clone(), pt_node.clone())))
|
|
}
|
|
Self::PtyMaster(pt, pt_node) => {
|
|
Ok(Arc::new(Self::PtyMaster(pt.clone(), pt_node.clone())))
|
|
}
|
|
_ => {
|
|
log::info!("Invalid file send(): {:?}", self);
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reads entries from the directory
|
|
pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
|
|
match self {
|
|
Self::Directory(dir) => dir.read_entries(entries),
|
|
_ => Err(Error::NotADirectory),
|
|
}
|
|
}
|
|
|
|
/// Returns the underlying [Node] the file contains
|
|
pub fn node(&self) -> Option<&NodeRef> {
|
|
match self {
|
|
Self::Directory(file) => Some(&file.node),
|
|
Self::Regular(file) => Some(&file.node),
|
|
Self::Block(file) => Some(&file.node),
|
|
Self::Char(file) => Some(&file.node),
|
|
Self::PtyMaster(_, node) => Some(node),
|
|
Self::PtySlave(_, node) => Some(node),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Polls a file for "read-readiness"
|
|
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
|
match self {
|
|
Self::Char(f) => f.device.0.poll_read(cx),
|
|
Self::Channel(ch) => ch.poll_read(cx),
|
|
Self::Poll(ch) => ch.poll_read(cx),
|
|
Self::PtyMaster(f, _) => f.poll_read(cx),
|
|
Self::PtySlave(f, _) => f.poll_read(cx),
|
|
Self::PacketSocket(sock) => sock.poll_read(cx),
|
|
Self::StreamSocket(sock) => sock.poll_read(cx),
|
|
Self::ListenerSocket(sock) => sock.poll_read(cx),
|
|
Self::Timer(timer) => timer.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, req: &mut DeviceRequest) -> Result<(), Error> {
|
|
match self {
|
|
Self::Char(f) => f.device.0.device_request(req),
|
|
Self::Block(f) => f.device.0.device_request(req),
|
|
Self::PtySlave(f, _) => f.device_request(req),
|
|
Self::PtyMaster(f, _) => f.device_request(req),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
/// Interprets the file as a poll channel
|
|
pub fn as_poll_channel(&self) -> Result<&FdPoll, Error> {
|
|
if let Self::Poll(poll) = self {
|
|
Ok(poll)
|
|
} else {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
|
|
/// Interprets the file as a message channel
|
|
pub fn as_message_channel(&self) -> Result<&ChannelDescriptor, Error> {
|
|
if let Self::Channel(ch) = self {
|
|
Ok(ch)
|
|
} else {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
}
|
|
|
|
/// Interprets the file as a socket
|
|
pub fn as_socket(&self) -> Result<&dyn Socket, Error> {
|
|
match self {
|
|
Self::PacketSocket(socket) => Ok(socket.0.as_ref()),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
/// Sends data to a socket
|
|
pub fn send_to(&self, buffer: &[u8], recepient: Option<SocketAddr>) -> Result<usize, Error> {
|
|
match (self, recepient) {
|
|
(Self::PacketSocket(socket), recepient) => socket.send(recepient, buffer),
|
|
(Self::StreamSocket(socket), None) => socket.send(buffer),
|
|
(_, _) => todo!(),
|
|
}
|
|
}
|
|
|
|
/// Receives data from a socket
|
|
pub fn receive_from(
|
|
&self,
|
|
buffer: &mut [u8],
|
|
remote: &mut MaybeUninit<SocketAddr>,
|
|
) -> Result<usize, Error> {
|
|
match self {
|
|
Self::PacketSocket(socket) => {
|
|
let (addr, len) = socket.receive(buffer)?;
|
|
remote.write(addr);
|
|
Ok(len)
|
|
}
|
|
Self::StreamSocket(socket) => {
|
|
// Always the same
|
|
remote.write(socket.remote_address().unwrap());
|
|
socket.receive(buffer)
|
|
}
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
/// Waits for incoming connection to be accepted by the listener
|
|
pub fn accept(&self, remote: &mut MaybeUninit<SocketAddr>) -> Result<FileRef, Error> {
|
|
match self {
|
|
Self::ListenerSocket(socket) => {
|
|
let (address, incoming) = socket.accept()?;
|
|
remote.write(address);
|
|
Ok(File::from_stream_socket(incoming))
|
|
}
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PageProvider for File {
|
|
fn get_page(&self, offset: u64) -> Result<PhysicalAddress, Error> {
|
|
match self {
|
|
Self::Block(f) => f.device.0.get_page(offset),
|
|
Self::SharedMemory(f) => f.get_page(offset),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> {
|
|
match self {
|
|
Self::Block(f) => f.device.0.release_page(offset, phys),
|
|
Self::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> {
|
|
match self {
|
|
Self::Regular(file) => file.read(buf),
|
|
Self::Block(file) => file.read(buf),
|
|
Self::Char(file) => file.read(buf),
|
|
Self::AnonymousPipe(pipe) => pipe.read(buf),
|
|
Self::PtySlave(pt, _) => pt.read(buf),
|
|
Self::PtyMaster(pt, _) => pt.read(buf),
|
|
// TODO maybe allow reading trigger count?
|
|
Self::Timer(_) => Err(Error::InvalidOperation),
|
|
// TODO maybe allow reading FDs from poll channels as if they were regular streams?
|
|
Self::Poll(_) => Err(Error::InvalidOperation),
|
|
// TODO maybe allow reading messages from Channels?
|
|
Self::Channel(_) => Err(Error::InvalidOperation),
|
|
Self::SharedMemory(_) => Err(Error::InvalidOperation),
|
|
// TODO maybe allow reading messages from Packet/Stream sockets?
|
|
Self::PacketSocket(_) | Self::ListenerSocket(_) | Self::StreamSocket(_) => {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Write for File {
|
|
fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
|
match self {
|
|
Self::Regular(file) => file.write(buf),
|
|
Self::Block(file) => file.write(buf),
|
|
Self::Char(file) => file.write(buf),
|
|
Self::AnonymousPipe(pipe) => pipe.write(buf),
|
|
Self::PtySlave(pt, _) => pt.write(buf),
|
|
Self::PtyMaster(pt, _) => pt.write(buf),
|
|
Self::Timer(timer) => timer.write(buf),
|
|
// TODO maybe allow adding FDs to poll channels this way
|
|
Self::Poll(_) => Err(Error::InvalidOperation),
|
|
// TODO maybe allow writing messages to Channels?
|
|
Self::Channel(_) => Err(Error::InvalidOperation),
|
|
Self::SharedMemory(_) => Err(Error::InvalidOperation),
|
|
// TODO maybe allow writing messages to Packet/Stream sockets?
|
|
Self::PacketSocket(_) | Self::ListenerSocket(_) | Self::StreamSocket(_) => {
|
|
Err(Error::InvalidOperation)
|
|
}
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Seek for File {
|
|
fn tell(&self) -> Result<u64, Error> {
|
|
match self {
|
|
Self::Regular(file) => Ok(*file.position.lock()),
|
|
Self::Block(file) => Ok(*file.position.lock()),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
|
|
fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
|
|
match self {
|
|
Self::Regular(file) => file.seek(from),
|
|
Self::Block(file) => file.seek(from),
|
|
Self::Directory(_) => Err(Error::IsADirectory),
|
|
_ => Err(Error::InvalidOperation),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for File {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Regular(file) => f
|
|
.debug_struct("RegularFile")
|
|
.field("position", &*file.position.lock())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Block(file) => f
|
|
.debug_struct("BlockFile")
|
|
.field("position", &*file.position.lock())
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Char(file) => f
|
|
.debug_struct("CharFile")
|
|
.field("read", &file.read)
|
|
.field("write", &file.write)
|
|
.finish_non_exhaustive(),
|
|
Self::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
|
|
Self::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(),
|
|
Self::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(),
|
|
Self::Channel(_) => f.debug_struct("Channel").finish_non_exhaustive(),
|
|
Self::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(),
|
|
Self::PtySlave(_, _) => f.debug_struct("PtySlave").finish_non_exhaustive(),
|
|
Self::PtyMaster(_, _) => f.debug_struct("PtyMaster").finish_non_exhaustive(),
|
|
Self::PacketSocket(sock) => f
|
|
.debug_struct("PacketSocket")
|
|
.field("local", &sock.local_address())
|
|
.field("remote", &sock.remote_address())
|
|
.finish_non_exhaustive(),
|
|
Self::StreamSocket(sock) => f
|
|
.debug_struct("StreamSocket")
|
|
.field("local", &sock.local_address())
|
|
.field("remote", &sock.remote_address())
|
|
.finish_non_exhaustive(),
|
|
Self::ListenerSocket(sock) => f
|
|
.debug_struct("ListenerSocket")
|
|
.field("local", &sock.local_address())
|
|
.finish_non_exhaustive(),
|
|
Self::Timer(_) => f.debug_struct("Timer").finish_non_exhaustive(),
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
if self.map.remove(&fd).is_some() {
|
|
Ok(())
|
|
} else {
|
|
Err(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 yggdrasil_abi::{
|
|
error::Error,
|
|
io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
|
|
util::FixedString,
|
|
};
|
|
|
|
use crate::vfs::{
|
|
device::CharDevice,
|
|
file::DirectoryOpenPosition,
|
|
impls::const_value_node,
|
|
node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
|
|
traits::{Read, Seek, Write},
|
|
FileReadiness, 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: 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())),
|
|
("f2".to_owned(), Node::regular(F, NodeFlags::empty())),
|
|
("f3".to_owned(), Node::regular(F, NodeFlags::empty())),
|
|
]),
|
|
},
|
|
NodeFlags::empty(),
|
|
);
|
|
|
|
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: FileType::File,
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f2").unwrap(),
|
|
ty: FileType::File,
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("f3").unwrap(),
|
|
ty: 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());
|
|
let child = Node::directory(D, NodeFlags::empty());
|
|
|
|
d.add_child("child1", 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: FileType::Directory
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("..").unwrap(),
|
|
ty: FileType::Directory
|
|
},
|
|
DirectoryEntry {
|
|
name: FixedString::from_str("child1").unwrap(),
|
|
ty: 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());
|
|
let file = node
|
|
.open(
|
|
OpenOptions::READ | OpenOptions::WRITE,
|
|
AccessToken::test_authorized(),
|
|
)
|
|
.unwrap();
|
|
let mut buf = [0; 512];
|
|
|
|
assert_eq!(&*data.lock().unwrap(), &[]);
|
|
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");
|
|
}
|
|
|
|
#[test]
|
|
fn char_device() {
|
|
struct C;
|
|
|
|
impl FileReadiness for C {
|
|
fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
impl CharDevice for C {
|
|
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
|
buf.fill(b'@');
|
|
Ok(buf.len())
|
|
}
|
|
|
|
fn is_writable(&self) -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
static DEV: C = C;
|
|
|
|
let node = Node::char(&DEV, NodeFlags::empty());
|
|
let mut buf = [0; 512];
|
|
|
|
let err = node
|
|
.open(OpenOptions::WRITE, AccessToken::test_authorized())
|
|
.unwrap_err();
|
|
assert_eq!(err, Error::ReadOnly);
|
|
|
|
let file = node
|
|
.open(OpenOptions::READ, AccessToken::test_authorized())
|
|
.unwrap();
|
|
assert_eq!(file.tell().unwrap_err(), Error::InvalidOperation);
|
|
assert_eq!(
|
|
file.seek(SeekFrom::Start(10)).unwrap_err(),
|
|
Error::InvalidOperation
|
|
);
|
|
assert_eq!(file.read(&mut buf).unwrap(), 512);
|
|
assert_eq!(buf, [b'@'; 512]);
|
|
|
|
assert_eq!(file.write(b"1234").unwrap_err(), Error::ReadOnly);
|
|
}
|
|
}
|