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");
}
}