blob: ce396722d836a3c45a2db80080dbe4be0b6df55a [file] [log] [blame]
// Copyright 2019 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 crate::{
common::{inherit_rights_for_clone, send_on_open_with_error, GET_FLAGS_VISIBLE},
directory::{
common::{check_child_connection_flags, POSIX_DIRECTORY_PROTECTION_ATTRIBUTES},
connection::util::OpenDirectory,
entry::DirectoryEntry,
entry_container::{AsyncGetEntry, Directory},
read_dirents,
traversal_position::TraversalPosition,
},
execution_scope::ExecutionScope,
path::Path,
};
use {
anyhow::Error,
fidl::{endpoints::ServerEnd, Event, Handle},
fidl_fuchsia_io::{
DirectoryAdminCloseResponder, DirectoryAdminControlHandle, DirectoryAdminDescribeResponder,
DirectoryAdminGetAttrResponder, DirectoryAdminGetDevicePathResponder,
DirectoryAdminGetTokenResponder, DirectoryAdminLinkResponder,
DirectoryAdminMountAndCreateResponder, DirectoryAdminMountResponder,
DirectoryAdminNodeGetFlagsResponder, DirectoryAdminNodeSetFlagsResponder,
DirectoryAdminQueryFilesystemResponder, DirectoryAdminReadDirentsResponder,
DirectoryAdminRename2Responder, DirectoryAdminRenameResponder, DirectoryAdminRequest,
DirectoryAdminRequestStream, DirectoryAdminRewindResponder, DirectoryAdminSetAttrResponder,
DirectoryAdminSyncResponder, DirectoryAdminUnlink2Responder, DirectoryAdminUnlinkResponder,
DirectoryAdminUnmountNodeResponder, DirectoryAdminUnmountResponder,
DirectoryAdminWatchResponder, DirectoryObject, NodeAttributes, NodeInfo, NodeMarker,
INO_UNKNOWN, MODE_TYPE_DIRECTORY, OPEN_FLAG_CREATE, OPEN_FLAG_DIRECTORY,
OPEN_FLAG_NODE_REFERENCE, OPEN_FLAG_NOT_DIRECTORY, OPEN_FLAG_POSIX,
OPEN_FLAG_POSIX_EXECUTABLE, OPEN_FLAG_POSIX_WRITABLE, OPEN_RIGHT_WRITABLE,
},
fidl_fuchsia_io2::UnlinkOptions,
fuchsia_async::Channel,
fuchsia_zircon::{
sys::{ZX_ERR_INVALID_ARGS, ZX_ERR_NOT_SUPPORTED, ZX_OK},
Status,
},
futures::{future::BoxFuture, stream::StreamExt},
std::{default::Default, sync::Arc},
};
/// Return type for `BaseConnection::handle_request` and [`DerivedConnection::handle_request`].
pub enum ConnectionState {
/// Connection is still alive.
Alive,
/// Connection have received Node::Close message and should be closed.
Closed,
}
/// This is an API a derived directory connection needs to implement, in order for the
/// `BaseConnection` to be able to interact with it.
pub trait DerivedConnection: Send + Sync {
type Directory: BaseConnectionClient + ?Sized;
fn new(scope: ExecutionScope, directory: OpenDirectory<Self::Directory>, flags: u32) -> Self;
/// Initializes a directory connection, checking the flags and sending `OnOpen` event if
/// necessary. Then either runs this connection inside of the specified `scope` or, in case of
/// an error, sends an appropriate `OnOpen` event (if requested) over the `server_end`
/// connection.
/// If an error occurs, create_connection() must call close() on the directory.
fn create_connection(
scope: ExecutionScope,
directory: OpenDirectory<Self::Directory>,
flags: u32,
server_end: ServerEnd<NodeMarker>,
);
fn entry_not_found(
scope: ExecutionScope,
parent: Arc<dyn DirectoryEntry>,
flags: u32,
mode: u32,
name: &str,
path: &Path,
) -> Result<Arc<dyn DirectoryEntry>, Status>;
fn handle_request(
&mut self,
request: DirectoryAdminRequest,
) -> BoxFuture<'_, Result<ConnectionState, Error>>;
}
/// This is an API a directory needs to implement, in order for the `BaseConnection` to be able to
/// interact with it.
pub trait BaseConnectionClient: DirectoryEntry + Directory + Send + Sync {}
impl<T> BaseConnectionClient for T where T: DirectoryEntry + Directory + Send + Sync + 'static {}
/// Handles functionality shared between mutable and immutable FIDL connections to a directory. A
/// single directory may contain multiple connections. Instances of the `BaseConnection`
/// will also hold any state that is "per-connection". Currently that would be the access flags
/// and the seek position.
pub(in crate::directory) struct BaseConnection<Connection>
where
Connection: DerivedConnection + 'static,
{
/// Execution scope this connection and any async operations and connections it creates will
/// use.
pub(in crate::directory) scope: ExecutionScope,
pub(in crate::directory) directory: OpenDirectory<Connection::Directory>,
/// Flags set on this connection when it was opened or cloned.
pub(in crate::directory) flags: u32,
/// Seek position for this connection to the directory. We just store the element that was
/// returned last by ReadDirents for this connection. Next call will look for the next element
/// in alphabetical order and resume from there.
///
/// An alternative is to use an intrusive tree to have a dual index in both names and IDs that
/// are assigned to the entries in insertion order. Then we can store an ID instead of the
/// full entry name. This is what the C++ version is doing currently.
///
/// It should be possible to do the same intrusive dual-indexing using, for example,
///
/// https://docs.rs/intrusive-collections/0.7.6/intrusive_collections/
///
/// but, as, I think, at least for the pseudo directories, this approach is fine, and it simple
/// enough.
seek: TraversalPosition,
}
/// Subset of the [`DirectoryRequest`] protocol that is handled by the
/// [`BaseConnection::handle_request`] method.
pub(in crate::directory) enum BaseDirectoryRequest {
Clone {
flags: u32,
object: ServerEnd<NodeMarker>,
#[allow(unused)]
control_handle: DirectoryAdminControlHandle,
},
Close {
responder: DirectoryAdminCloseResponder,
},
Describe {
responder: DirectoryAdminDescribeResponder,
},
GetAttr {
responder: DirectoryAdminGetAttrResponder,
},
GetFlags {
responder: DirectoryAdminNodeGetFlagsResponder,
},
SetFlags {
#[allow(unused)]
flags: u32,
responder: DirectoryAdminNodeSetFlagsResponder,
},
AdvisoryLock {
#[allow(unused)]
request: fidl_fuchsia_io2::AdvisoryLockRequest,
responder: fidl_fuchsia_io::DirectoryAdminAdvisoryLockResponder,
},
Open {
flags: u32,
mode: u32,
path: String,
object: ServerEnd<NodeMarker>,
#[allow(unused)]
control_handle: DirectoryAdminControlHandle,
},
AddInotifyFilter {
#[allow(unused)]
path: String,
#[allow(unused)]
filter: fidl_fuchsia_io2::InotifyWatchMask,
#[allow(unused)]
watch_descriptor: u32,
#[allow(unused)]
socket: fidl::Socket,
#[allow(unused)]
responder: fidl_fuchsia_io::DirectoryAdminAddInotifyFilterResponder,
},
ReadDirents {
max_bytes: u64,
responder: DirectoryAdminReadDirentsResponder,
},
Rewind {
responder: DirectoryAdminRewindResponder,
},
Link {
src: String,
dst_parent_token: Handle,
dst: String,
responder: DirectoryAdminLinkResponder,
},
Watch {
mask: u32,
options: u32,
watcher: fidl::Channel,
responder: DirectoryAdminWatchResponder,
},
QueryFilesystem {
responder: DirectoryAdminQueryFilesystemResponder,
},
Mount {
responder: DirectoryAdminMountResponder,
},
MountAndCreate {
responder: DirectoryAdminMountAndCreateResponder,
},
Unmount {
responder: DirectoryAdminUnmountResponder,
},
UnmountNode {
responder: DirectoryAdminUnmountNodeResponder,
},
GetDevicePath {
responder: DirectoryAdminGetDevicePathResponder,
},
}
pub(in crate::directory) enum DerivedDirectoryRequest {
Unlink {
path: String,
responder: DirectoryAdminUnlinkResponder,
},
Unlink2 {
name: String,
options: UnlinkOptions,
responder: DirectoryAdminUnlink2Responder,
},
GetToken {
responder: DirectoryAdminGetTokenResponder,
},
Rename {
src: String,
dst_parent_token: Handle,
dst: String,
responder: DirectoryAdminRenameResponder,
},
Rename2 {
src: String,
dst_parent_token: Event,
dst: String,
responder: DirectoryAdminRename2Responder,
},
SetAttr {
flags: u32,
attributes: NodeAttributes,
responder: DirectoryAdminSetAttrResponder,
},
Sync {
responder: DirectoryAdminSyncResponder,
},
}
pub(in crate::directory) enum DirectoryRequestType {
Base(BaseDirectoryRequest),
Derived(DerivedDirectoryRequest),
}
impl From<DirectoryAdminRequest> for DirectoryRequestType {
fn from(request: DirectoryAdminRequest) -> Self {
use {BaseDirectoryRequest::*, DerivedDirectoryRequest::*, DirectoryRequestType::*};
match request {
DirectoryAdminRequest::Clone { flags, object, control_handle } => {
Base(Clone { flags, object, control_handle })
}
DirectoryAdminRequest::Close { responder } => Base(Close { responder }),
DirectoryAdminRequest::Describe { responder } => Base(Describe { responder }),
DirectoryAdminRequest::Sync { responder } => Derived(Sync { responder }),
DirectoryAdminRequest::GetAttr { responder } => Base(GetAttr { responder }),
DirectoryAdminRequest::SetAttr { flags, attributes, responder } => {
Derived(SetAttr { flags, attributes, responder })
}
DirectoryAdminRequest::NodeGetFlags { responder } => Base(GetFlags { responder }),
DirectoryAdminRequest::NodeSetFlags { flags, responder } => {
Base(SetFlags { flags, responder })
}
DirectoryAdminRequest::Open { flags, mode, path, object, control_handle } => {
Base(Open { flags, mode, path, object, control_handle })
}
DirectoryAdminRequest::AddInotifyFilter {
path,
filter,
watch_descriptor,
socket,
responder,
} => Base(AddInotifyFilter { path, filter, watch_descriptor, socket, responder }),
DirectoryAdminRequest::AdvisoryLock { request, responder } => {
Base(AdvisoryLock { request, responder })
}
DirectoryAdminRequest::Unlink { path, responder } => {
Derived(Unlink { path, responder })
}
DirectoryAdminRequest::Unlink2 { name, options, responder } => {
Derived(Unlink2 { name, options, responder })
}
DirectoryAdminRequest::ReadDirents { max_bytes, responder } => {
Base(ReadDirents { max_bytes, responder })
}
DirectoryAdminRequest::Rewind { responder } => Base(Rewind { responder }),
DirectoryAdminRequest::GetToken { responder } => Derived(GetToken { responder }),
DirectoryAdminRequest::Rename { src, dst_parent_token, dst, responder } => {
Derived(Rename { src, dst_parent_token, dst, responder })
}
DirectoryAdminRequest::Rename2 { src, dst_parent_token, dst, responder } => {
Derived(Rename2 { src, dst_parent_token, dst, responder })
}
DirectoryAdminRequest::Link { src, dst_parent_token, dst, responder } => {
Base(Link { src, dst_parent_token, dst, responder })
}
DirectoryAdminRequest::Watch { mask, options, watcher, responder } => {
Base(Watch { mask, options, watcher, responder })
}
DirectoryAdminRequest::QueryFilesystem { responder } => {
Base(BaseDirectoryRequest::QueryFilesystem { responder })
}
DirectoryAdminRequest::Mount { responder, .. } => {
Base(BaseDirectoryRequest::Mount { responder })
}
DirectoryAdminRequest::MountAndCreate { responder, .. } => {
Base(BaseDirectoryRequest::MountAndCreate { responder })
}
DirectoryAdminRequest::Unmount { responder } => {
Base(BaseDirectoryRequest::Unmount { responder })
}
DirectoryAdminRequest::UnmountNode { responder } => {
Base(BaseDirectoryRequest::UnmountNode { responder })
}
DirectoryAdminRequest::GetDevicePath { responder } => {
Base(BaseDirectoryRequest::GetDevicePath { responder })
}
}
}
}
#[must_use = "handle_requests() returns an async task that needs to be run"]
pub(in crate::directory) async fn handle_requests<Connection>(
mut requests: DirectoryAdminRequestStream,
mut connection: Connection,
) where
Connection: DerivedConnection,
{
while let Some(request_or_err) = requests.next().await {
match request_or_err {
Err(_) => {
// FIDL level error, such as invalid message format and alike. Close the
// connection on any unexpected error.
// TODO: Send an epitaph.
break;
}
Ok(request) => match connection.handle_request(request).await {
Ok(ConnectionState::Alive) => (),
Ok(ConnectionState::Closed) => break,
Err(_) => {
// Protocol level error. Close the connection on any unexpected error.
// TODO: Send an epitaph.
break;
}
},
}
}
// The underlying directory will be closed automatically when the OpenDirectory is dropped.
}
impl<Connection> BaseConnection<Connection>
where
Connection: DerivedConnection,
{
/// Constructs an instance of `BaseConnection` - to be used by derived connections, when they
/// need to create a nested `BaseConnection` "sub-object". But when implementing
/// `create_connection`, derived connections should use the [`create_connection`] call.
pub(in crate::directory) fn new(
scope: ExecutionScope,
directory: OpenDirectory<Connection::Directory>,
flags: u32,
) -> Self {
BaseConnection { scope, directory, flags, seek: Default::default() }
}
/// Handle a [`DirectoryRequest`]. This function is responsible for handing all the basic
/// directory operations.
// TODO(fxbug.dev/37419): Remove default handling after methods landed.
#[allow(unreachable_patterns)]
pub(in crate::directory) async fn handle_request(
&mut self,
request: BaseDirectoryRequest,
) -> Result<ConnectionState, Error> {
match request {
BaseDirectoryRequest::Clone { flags, object, control_handle: _ } => {
fuchsia_trace::duration!("storage", "Directory::Clone");
self.handle_clone(flags, 0, object);
}
BaseDirectoryRequest::Close { responder } => {
fuchsia_trace::duration!("storage", "Directory::Close");
let status = match self.directory.close() {
Ok(()) => Status::OK,
Err(e) => e,
};
responder.send(status.into_raw())?;
return Ok(ConnectionState::Closed);
}
BaseDirectoryRequest::Describe { responder } => {
fuchsia_trace::duration!("storage", "Directory::Describe");
let mut info = NodeInfo::Directory(DirectoryObject);
responder.send(&mut info)?;
}
BaseDirectoryRequest::GetAttr { responder } => {
fuchsia_trace::duration!("storage", "Directory::GetAttr");
let (mut attrs, status) = match self.directory.get_attrs().await {
Ok(attrs) => (attrs, ZX_OK),
Err(status) => (
NodeAttributes {
mode: MODE_TYPE_DIRECTORY | POSIX_DIRECTORY_PROTECTION_ATTRIBUTES,
id: INO_UNKNOWN,
content_size: 0,
storage_size: 0,
link_count: 1,
creation_time: 0,
modification_time: 0,
},
status.into_raw(),
),
};
attrs.mode = MODE_TYPE_DIRECTORY | POSIX_DIRECTORY_PROTECTION_ATTRIBUTES;
responder.send(status, &mut attrs)?;
}
BaseDirectoryRequest::GetFlags { responder } => {
fuchsia_trace::duration!("storage", "Directory::GetFlags");
responder.send(ZX_OK, self.flags & GET_FLAGS_VISIBLE)?;
}
BaseDirectoryRequest::SetFlags { flags: _, responder } => {
fuchsia_trace::duration!("storage", "Directory::SetFlags");
responder.send(ZX_ERR_NOT_SUPPORTED)?;
}
BaseDirectoryRequest::Open { flags, mode, path, object, control_handle: _ } => {
fuchsia_trace::duration!("storage", "Directory::Open");
self.handle_open(flags, mode, path, object);
}
BaseDirectoryRequest::AddInotifyFilter { .. } => {
fuchsia_trace::duration!("storage", "Directory::AddInotifyFilter");
}
BaseDirectoryRequest::AdvisoryLock { request: _, responder } => {
fuchsia_trace::duration!("storage", "Directory::AdvisoryLock");
responder.send(&mut Err(ZX_ERR_NOT_SUPPORTED))?;
}
BaseDirectoryRequest::ReadDirents { max_bytes, responder } => {
fuchsia_trace::duration!("storage", "Directory::ReadDirents");
self.handle_read_dirents(max_bytes, |status, entries| {
responder.send(status.into_raw(), entries)
})
.await?;
}
BaseDirectoryRequest::Rewind { responder } => {
fuchsia_trace::duration!("storage", "Directory::Rewind");
self.seek = Default::default();
responder.send(ZX_OK)?;
}
BaseDirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
fuchsia_trace::duration!("storage", "Directory::Link");
self.handle_link(src, dst_parent_token, dst, |status| {
responder.send(status.into_raw())
})
.await?;
}
BaseDirectoryRequest::Watch { mask, options, watcher, responder } => {
fuchsia_trace::duration!("storage", "Directory::Watch");
if options != 0 {
responder.send(ZX_ERR_INVALID_ARGS)?;
} else {
let channel = Channel::from_channel(watcher)?;
self.handle_watch(mask, channel, |status| responder.send(status.into_raw()))?;
}
}
BaseDirectoryRequest::QueryFilesystem { responder } => {
fuchsia_trace::duration!("storage", "Directory::QueryFilesystem");
match self.directory.query_filesystem() {
Err(status) => responder.send(status.into_raw(), None)?,
Ok(mut info) => responder.send(0, Some(&mut info))?,
}
}
BaseDirectoryRequest::Mount { responder } => responder.send(ZX_ERR_NOT_SUPPORTED)?,
BaseDirectoryRequest::MountAndCreate { responder } => {
responder.send(ZX_ERR_NOT_SUPPORTED)?
}
BaseDirectoryRequest::Unmount { responder } => responder.send(ZX_ERR_NOT_SUPPORTED)?,
BaseDirectoryRequest::UnmountNode { responder } => {
responder.send(ZX_ERR_NOT_SUPPORTED, None)?
}
BaseDirectoryRequest::GetDevicePath { responder } => {
responder.send(ZX_ERR_NOT_SUPPORTED, None)?
}
_ => {}
}
Ok(ConnectionState::Alive)
}
fn handle_clone(&self, flags: u32, mode: u32, server_end: ServerEnd<NodeMarker>) {
let flags = match inherit_rights_for_clone(self.flags, flags) {
Ok(updated) => updated,
Err(status) => {
send_on_open_with_error(flags, server_end, status);
return;
}
};
self.directory.clone().open(self.scope.clone(), flags, mode, Path::dot(), server_end);
}
fn handle_open(
&self,
mut flags: u32,
mode: u32,
path: String,
server_end: ServerEnd<NodeMarker>,
) {
if self.flags & OPEN_FLAG_NODE_REFERENCE != 0 {
send_on_open_with_error(flags, server_end, Status::BAD_HANDLE);
return;
}
let path = match Path::validate_and_split(path) {
Ok(path) => path,
Err(status) => {
send_on_open_with_error(flags, server_end, status);
return;
}
};
if path.is_dir() {
flags |= OPEN_FLAG_DIRECTORY;
}
let (mut flags, mode) = match check_child_connection_flags(self.flags, flags, mode) {
Ok(updated) => updated,
Err(status) => {
send_on_open_with_error(flags, server_end, status);
return;
}
};
if path.is_dot() {
// Note that we reject both OPEN_FLAG_CREATE and OPEN_FLAG_CREATE_IF_ABSENT, rather
// than just OPEN_FLAG_CREATE_IF_ABSENT. This matches the behaviour of the C++
// filesystems.
if flags & (OPEN_FLAG_CREATE | OPEN_FLAG_NOT_DIRECTORY) != 0 {
send_on_open_with_error(flags, server_end, Status::INVALID_ARGS);
return;
}
// TODO(fxb/37534): Add support for execute rights.
flags &= !OPEN_FLAG_POSIX_EXECUTABLE;
// Clone ignores the POSIX flag, but open shouldn't. The flag would have been stripped
// above by check_child_connection_flags if we had insufficient privileges.
if flags & (OPEN_FLAG_POSIX | OPEN_FLAG_POSIX_WRITABLE) != 0 {
flags &= !(OPEN_FLAG_POSIX | OPEN_FLAG_POSIX_WRITABLE);
flags |= OPEN_RIGHT_WRITABLE;
}
self.handle_clone(flags, mode, server_end);
return;
}
// It is up to the open method to handle OPEN_FLAG_DESCRIBE from this point on.
let directory = self.directory.clone();
directory.open(self.scope.clone(), flags, mode, path, server_end);
}
async fn handle_read_dirents<R>(
&mut self,
max_bytes: u64,
responder: R,
) -> Result<(), fidl::Error>
where
R: FnOnce(Status, &[u8]) -> Result<(), fidl::Error>,
{
if self.flags & OPEN_FLAG_NODE_REFERENCE != 0 {
return responder(Status::BAD_HANDLE, &[]);
}
let done_or_err =
match self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await
{
Ok((new_pos, sealed)) => {
self.seek = new_pos;
sealed.open().downcast::<read_dirents::Done>()
}
Err(status) => return responder(status, &[]),
};
match done_or_err {
Ok(done) => responder(done.status, &done.buf),
Err(_) => {
debug_assert!(
false,
"`read_dirents()` returned a `dirents_sink::Sealed` instance that is not \
an instance of the `read_dirents::Done`. This is a bug in the \
`read_dirents()` implementation."
);
responder(Status::NOT_SUPPORTED, &[])
}
}
}
async fn handle_link<R>(
&self,
src: String,
dst_parent_token: Handle,
dst: String,
responder: R,
) -> Result<(), fidl::Error>
where
R: FnOnce(Status) -> Result<(), fidl::Error>,
{
let token_registry = match self.scope.token_registry() {
None => return responder(Status::NOT_SUPPORTED),
Some(registry) => registry,
};
if self.flags & OPEN_RIGHT_WRITABLE == 0 {
return responder(Status::BAD_HANDLE);
}
let res = {
let directory = self.directory.clone();
match directory.get_entry(src) {
AsyncGetEntry::Immediate(res) => res,
AsyncGetEntry::Future(fut) => fut.await,
}
};
let entry = match res {
Err(status) => return responder(status),
Ok(entry) => entry,
};
if !entry.can_hardlink() {
return responder(Status::NOT_FILE);
}
let dst_parent = match token_registry.get_container(dst_parent_token) {
Err(status) => return responder(status),
Ok(None) => return responder(Status::NOT_FOUND),
Ok(Some(entry)) => entry,
};
match dst_parent.link(dst, entry).await {
Ok(()) => responder(Status::OK),
Err(status) => responder(status),
}
}
fn handle_watch<R>(
&mut self,
mask: u32,
channel: Channel,
responder: R,
) -> Result<(), fidl::Error>
where
R: FnOnce(Status) -> Result<(), fidl::Error>,
{
let directory = self.directory.clone();
responder(
directory
.register_watcher(self.scope.clone(), mask, channel)
.err()
.unwrap_or(Status::OK),
)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::directory::immutable::simple::simple,
fidl_fuchsia_io::{
DirectoryMarker, NodeEvent, MODE_TYPE_DIRECTORY, MODE_TYPE_FILE, OPEN_FLAG_DESCRIBE,
OPEN_FLAG_DIRECTORY, OPEN_RIGHT_READABLE,
},
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::prelude::*,
matches::assert_matches,
};
#[fasync::run_singlethreaded(test)]
async fn test_open_not_found() {
let (dir_proxy, dir_server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("Create proxy to succeed");
let dir = simple();
dir.open(
ExecutionScope::new(),
OPEN_FLAG_DIRECTORY | OPEN_RIGHT_READABLE,
MODE_TYPE_DIRECTORY,
Path::dot(),
ServerEnd::new(dir_server_end.into_channel()),
);
let (node_proxy, node_server_end) =
fidl::endpoints::create_proxy().expect("Create proxy to succeed");
// Try to open a file that doesn't exist.
assert_matches!(
dir_proxy.open(OPEN_RIGHT_READABLE, MODE_TYPE_FILE, "foo", node_server_end),
Ok(())
);
// The channel also be closed with a NOT_FOUND epitaph.
assert_matches!(
node_proxy.describe().await,
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::NOT_FOUND,
protocol_name: "(anonymous) Node",
})
);
}
#[fasync::run_singlethreaded(test)]
async fn test_open_not_found_event_stream() {
let (dir_proxy, dir_server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("Create proxy to succeed");
let dir = simple();
dir.open(
ExecutionScope::new(),
OPEN_FLAG_DIRECTORY | OPEN_RIGHT_READABLE,
MODE_TYPE_DIRECTORY,
Path::dot(),
ServerEnd::new(dir_server_end.into_channel()),
);
let (node_proxy, node_server_end) =
fidl::endpoints::create_proxy().expect("Create proxy to succeed");
// Try to open a file that doesn't exist.
assert_matches!(
dir_proxy.open(OPEN_RIGHT_READABLE, MODE_TYPE_FILE, "foo", node_server_end),
Ok(())
);
// The event stream should be closed with the epitaph.
let mut event_stream = node_proxy.take_event_stream();
assert_matches!(
event_stream.try_next().await,
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::NOT_FOUND,
protocol_name: "(anonymous) Node",
})
);
assert_matches!(event_stream.try_next().await, Ok(None));
}
#[fasync::run_singlethreaded(test)]
async fn test_open_with_describe_not_found() {
let (dir_proxy, dir_server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("Create proxy to succeed");
let dir = simple();
dir.open(
ExecutionScope::new(),
OPEN_FLAG_DIRECTORY | OPEN_RIGHT_READABLE,
MODE_TYPE_DIRECTORY,
Path::dot(),
ServerEnd::new(dir_server_end.into_channel()),
);
let (node_proxy, node_server_end) =
fidl::endpoints::create_proxy().expect("Create proxy to succeed");
// Try to open a file that doesn't exist.
assert_matches!(
dir_proxy.open(
OPEN_FLAG_DESCRIBE | OPEN_RIGHT_READABLE,
MODE_TYPE_FILE,
"foo",
node_server_end,
),
Ok(())
);
// The channel should be closed with a NOT_FOUND epitaph.
assert_matches!(
node_proxy.describe().await,
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::NOT_FOUND,
protocol_name: "(anonymous) Node",
})
);
}
#[fasync::run_singlethreaded(test)]
async fn test_open_describe_not_found_event_stream() {
let (dir_proxy, dir_server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().expect("Create proxy to succeed");
let dir = simple();
dir.open(
ExecutionScope::new(),
OPEN_FLAG_DIRECTORY | OPEN_RIGHT_READABLE,
MODE_TYPE_DIRECTORY,
Path::dot(),
ServerEnd::new(dir_server_end.into_channel()),
);
let (node_proxy, node_server_end) =
fidl::endpoints::create_proxy().expect("Create proxy to succeed");
// Try to open a file that doesn't exist.
assert_matches!(
dir_proxy.open(
OPEN_FLAG_DESCRIBE | OPEN_RIGHT_READABLE,
MODE_TYPE_FILE,
"foo",
node_server_end,
),
Ok(())
);
// The event stream should return that the file does not exist.
let mut event_stream = node_proxy.take_event_stream();
assert_matches!(
event_stream.try_next().await,
Ok(Some(NodeEvent::OnOpen_ {
s,
info: None,
}))
if Status::from_raw(s) == Status::NOT_FOUND
);
assert_matches!(
event_stream.try_next().await,
Err(fidl::Error::ClientChannelClosed {
status: zx::Status::NOT_FOUND,
protocol_name: "(anonymous) Node",
})
);
assert_matches!(event_stream.try_next().await, Ok(None));
}
}