#![warn(missing_docs)]
use crate::common::IntoAny;
use crate::directory::entry_container::Directory;
use crate::execution_scope::ExecutionScope;
use crate::file::{self, FileLike};
use crate::object_request::ObjectRequestSend;
use crate::path::Path;
use crate::service::{self, ServiceLike};
use crate::symlink::{self, Symlink};
use crate::{ObjectRequestRef, ToObjectRequest};
use fidl::endpoints::{create_endpoints, ClientEnd};
use fidl_fuchsia_io as fio;
use std::fmt;
use std::future::Future;
use std::sync::Arc;
use zx_status::Status;
#[derive(PartialEq, Eq, Clone)]
pub struct EntryInfo(u64, fio::DirentType);
impl EntryInfo {
pub fn new(inode: u64, type_: fio::DirentType) -> Self {
Self(inode, type_)
}
pub fn inode(&self) -> u64 {
let Self(inode, _type) = self;
*inode
}
pub fn type_(&self) -> fio::DirentType {
let Self(_inode, type_) = self;
*type_
}
}
impl fmt::Debug for EntryInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self(inode, type_) = self;
if *inode == fio::INO_UNKNOWN {
write!(f, "{:?}(fio::INO_UNKNOWN)", type_)
} else {
write!(f, "{:?}({})", type_, inode)
}
}
}
pub trait GetEntryInfo {
fn entry_info(&self) -> EntryInfo;
}
pub trait DirectoryEntry: GetEntryInfo + IntoAny + Sync + Send + 'static {
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status>;
}
pub trait DirectoryEntryAsync: DirectoryEntry {
fn open_entry_async(
self: Arc<Self>,
request: OpenRequest<'_>,
) -> impl Future<Output = Result<(), Status>> + Send;
}
#[derive(Debug)]
pub struct OpenRequest<'a> {
scope: ExecutionScope,
request_flags: RequestFlags,
path: Path,
object_request: ObjectRequestRef<'a>,
}
#[derive(Debug)]
pub enum RequestFlags {
Open1(fio::OpenFlags),
Open3(fio::Flags),
}
impl From<fio::OpenFlags> for RequestFlags {
fn from(value: fio::OpenFlags) -> Self {
RequestFlags::Open1(value)
}
}
impl From<fio::Flags> for RequestFlags {
fn from(value: fio::Flags) -> Self {
RequestFlags::Open3(value)
}
}
impl<'a> OpenRequest<'a> {
pub fn new(
scope: ExecutionScope,
request_flags: impl Into<RequestFlags>,
path: Path,
object_request: ObjectRequestRef<'a>,
) -> Self {
Self { scope, request_flags: request_flags.into(), path, object_request }
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn prepend_path(&mut self, prefix: &Path) {
self.path = self.path.with_prefix(prefix);
}
pub fn set_path(&mut self, path: Path) {
self.path = path;
}
pub async fn wait_till_ready(&self) -> bool {
self.object_request.wait_till_ready().await
}
pub fn requires_event(&self) -> bool {
self.object_request.what_to_send() != ObjectRequestSend::Nothing
}
pub fn open_dir(self, dir: Arc<impl Directory>) -> Result<(), Status> {
match self {
OpenRequest {
scope,
request_flags: RequestFlags::Open1(flags),
path,
object_request,
} => {
dir.open(scope, flags, path, object_request.take().into_server_end());
Ok(())
}
OpenRequest {
scope,
request_flags: RequestFlags::Open3(flags),
path,
object_request,
} => dir.open3(scope, path, flags, object_request),
}
}
pub fn open_file(self, file: Arc<impl FileLike>) -> Result<(), Status> {
match self {
OpenRequest {
scope,
request_flags: RequestFlags::Open1(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
file::serve(file, scope, &flags, object_request)
}
OpenRequest {
scope,
request_flags: RequestFlags::Open3(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
file::serve(file, scope, &flags, object_request)
}
}
}
pub fn open_symlink(self, service: Arc<impl Symlink>) -> Result<(), Status> {
match self {
OpenRequest {
scope,
request_flags: RequestFlags::Open1(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
symlink::serve(service, scope, &flags, object_request)
}
OpenRequest {
scope,
request_flags: RequestFlags::Open3(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
symlink::serve(service, scope, &flags, object_request)
}
}
}
pub fn open_service(self, service: Arc<impl ServiceLike>) -> Result<(), Status> {
match self {
OpenRequest {
scope,
request_flags: RequestFlags::Open1(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
service::serve(service, scope, &flags, object_request)
}
OpenRequest {
scope,
request_flags: RequestFlags::Open3(flags),
path,
object_request,
} => {
if !path.is_empty() {
return Err(Status::NOT_DIR);
}
service::serve(service, scope, &flags, object_request)
}
}
}
pub fn open_remote(
self,
remote: Arc<impl crate::remote::RemoteLike + Send + Sync + 'static>,
) -> Result<(), Status> {
match self {
OpenRequest {
scope,
request_flags: RequestFlags::Open1(flags),
path,
object_request,
} => {
if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
{
let object_request = object_request.take();
scope.clone().spawn(async move {
if object_request.wait_till_ready().await {
remote.open(scope, flags, path, object_request.into_server_end());
}
});
} else {
remote.open(scope, flags, path, object_request.take().into_server_end());
}
Ok(())
}
OpenRequest {
scope,
request_flags: RequestFlags::Open3(flags),
path,
object_request,
} => {
if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
{
let object_request = object_request.take();
scope.clone().spawn(async move {
object_request.handle(|object_request| {
remote.open3(scope, path, flags, object_request)
});
});
Ok(())
} else {
remote.open3(scope, path, flags, object_request)
}
}
}
}
pub fn spawn(self, entry: Arc<impl DirectoryEntryAsync>) {
let OpenRequest { scope, request_flags, path, object_request } = self;
let mut object_request = object_request.take();
match request_flags {
RequestFlags::Open1(flags) => {
scope.clone().spawn(async move {
match entry
.open_entry_async(OpenRequest::new(
scope,
RequestFlags::Open1(flags),
path,
&mut object_request,
))
.await
{
Ok(()) => {}
Err(s) => object_request.shutdown(s),
}
});
}
RequestFlags::Open3(flags) => {
scope.clone().spawn(async move {
match entry
.open_entry_async(OpenRequest::new(
scope,
RequestFlags::Open3(flags),
path,
&mut object_request,
))
.await
{
Ok(()) => {}
Err(s) => object_request.shutdown(s),
}
});
}
}
}
pub fn scope(&self) -> &ExecutionScope {
&self.scope
}
pub fn set_scope(&mut self, scope: ExecutionScope) {
self.scope = scope;
}
}
pub struct SubNode<T: ?Sized> {
parent: Arc<T>,
path: Path,
entry_type: fio::DirentType,
}
impl<T: DirectoryEntry + ?Sized> SubNode<T> {
pub fn new(parent: Arc<T>, path: Path, entry_type: fio::DirentType) -> SubNode<T> {
assert_eq!(parent.entry_info().type_(), fio::DirentType::Directory);
Self { parent, path, entry_type }
}
}
impl<T: DirectoryEntry + ?Sized> GetEntryInfo for SubNode<T> {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, self.entry_type)
}
}
impl<T: DirectoryEntry + ?Sized> DirectoryEntry for SubNode<T> {
fn open_entry(self: Arc<Self>, mut request: OpenRequest<'_>) -> Result<(), Status> {
request.path = request.path.with_prefix(&self.path);
self.parent.clone().open_entry(request)
}
}
pub fn serve_directory(
dir: Arc<impl DirectoryEntry + ?Sized>,
scope: &ExecutionScope,
flags: fio::OpenFlags,
) -> Result<ClientEnd<fio::DirectoryMarker>, Status> {
assert_eq!(dir.entry_info().type_(), fio::DirentType::Directory);
let (client, server) = create_endpoints::<fio::DirectoryMarker>();
flags
.to_object_request(server)
.handle(|object_request| {
Ok(dir.open_entry(OpenRequest::new(scope.clone(), flags, Path::dot(), object_request)))
})
.unwrap()?;
Ok(client)
}
#[cfg(test)]
mod tests {
use super::{
DirectoryEntry, DirectoryEntryAsync, EntryInfo, OpenRequest, RequestFlags, SubNode,
};
use crate::directory::entry::GetEntryInfo;
use crate::directory::entry_container::Directory;
use crate::execution_scope::ExecutionScope;
use crate::file::read_only;
use crate::path::Path;
use crate::{assert_read, pseudo_directory, ObjectRequest, ToObjectRequest};
use assert_matches::assert_matches;
use fidl::endpoints::{create_endpoints, create_proxy, ClientEnd};
use fidl_fuchsia_io as fio;
use futures::StreamExt;
use std::sync::Arc;
use zx_status::Status;
#[fuchsia::test]
async fn sub_node() {
let root = pseudo_directory!(
"a" => pseudo_directory!(
"b" => pseudo_directory!(
"c" => pseudo_directory!(
"d" => read_only(b"foo")
)
)
)
);
let sub_node = Arc::new(SubNode::new(
root,
Path::validate_and_split("a/b").unwrap(),
fio::DirentType::Directory,
));
let scope = ExecutionScope::new();
let (client, server) = create_endpoints();
let root2 = pseudo_directory!(
"e" => sub_node
);
root2.open(
scope.clone(),
fio::OpenFlags::RIGHT_READABLE,
Path::validate_and_split("e/c/d").unwrap(),
server,
);
assert_read!(
ClientEnd::<fio::FileMarker>::from(client.into_channel()).into_proxy().unwrap(),
"foo"
);
}
#[fuchsia::test]
async fn object_request_spawn() {
struct MockNode<F: Send + Sync + 'static>
where
for<'a> F: Fn(OpenRequest<'a>) -> Status,
{
callback: F,
}
impl<F: Send + Sync + 'static> DirectoryEntry for MockNode<F>
where
for<'a> F: Fn(OpenRequest<'a>) -> Status,
{
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
request.spawn(self);
Ok(())
}
}
impl<F: Send + Sync + 'static> GetEntryInfo for MockNode<F>
where
for<'a> F: Fn(OpenRequest<'a>) -> Status,
{
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Unknown)
}
}
impl<F: Send + Sync + 'static> DirectoryEntryAsync for MockNode<F>
where
for<'a> F: Fn(OpenRequest<'a>) -> Status,
{
async fn open_entry_async(
self: Arc<Self>,
request: OpenRequest<'_>,
) -> Result<(), Status> {
Err((self.callback)(request))
}
}
let scope = ExecutionScope::new();
let (proxy, server) = create_proxy::<fio::NodeMarker>().unwrap();
let flags = fio::OpenFlags::DIRECTORY | fio::OpenFlags::RIGHT_READABLE;
let mut object_request = flags.to_object_request(server);
let flags_copy = flags;
Arc::new(MockNode {
callback: move |request| {
assert_matches!(
request,
OpenRequest {
request_flags: RequestFlags::Open1(f),
path,
..
} if f == flags_copy && path.as_ref() == "a/b/c"
);
Status::BAD_STATE
},
})
.open_entry(OpenRequest::new(
scope.clone(),
flags,
"a/b/c".try_into().unwrap(),
&mut object_request,
))
.unwrap();
assert_matches!(
proxy.take_event_stream().next().await,
Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
if status == Status::BAD_STATE
);
let (proxy, server) = create_proxy::<fio::NodeMarker>().unwrap();
let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FILE_APPEND;
let mut object_request =
ObjectRequest::new3(flags, &Default::default(), server.into_channel());
Arc::new(MockNode {
callback: move |request| {
assert_matches!(
request,
OpenRequest {
request_flags: RequestFlags::Open3(f),
path,
..
} if f == flags && path.as_ref() == "a/b/c"
);
Status::BAD_STATE
},
})
.open_entry(OpenRequest::new(
scope.clone(),
flags,
"a/b/c".try_into().unwrap(),
&mut object_request,
))
.unwrap();
assert_matches!(
proxy.take_event_stream().next().await,
Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
if status == Status::BAD_STATE
);
}
}