use futures::prelude::*;
use thiserror::Error;
use {fidl_fuchsia_io as fio, zx_status};
#[cfg(target_os = "fuchsia")]
pub use fuchsia::*;
#[cfg(target_os = "fuchsia")]
mod fuchsia {
use super::*;
pub fn open_in_namespace(path: &str, flags: fio::Flags) -> Result<fio::NodeProxy, OpenError> {
let (node, request) = fidl::endpoints::create_proxy().map_err(OpenError::CreateProxy)?;
open_channel_in_namespace(path, flags, request)?;
Ok(node)
}
pub fn open_channel_in_namespace(
path: &str,
flags: fio::Flags,
request: fidl::endpoints::ServerEnd<fio::NodeMarker>,
) -> Result<(), OpenError> {
let namespace = fdio::Namespace::installed().map_err(OpenError::Namespace)?;
namespace.open(path, flags, request.into_channel()).map_err(OpenError::Namespace)
}
}
#[derive(Debug, Clone, Error)]
#[allow(missing_docs)]
pub enum OpenError {
#[error("while making a fidl proxy: {0}")]
CreateProxy(#[source] fidl::Error),
#[error("while opening from namespace: {0}")]
Namespace(#[source] zx_status::Status),
#[error("while sending open request: {0}")]
SendOpenRequest(#[source] fidl::Error),
#[error("node event stream closed prematurely")]
OnOpenEventStreamClosed,
#[error("while reading OnOpen event: {0}")]
OnOpenDecode(#[source] fidl::Error),
#[error("open failed with status: {0}")]
OpenError(#[source] zx_status::Status),
#[error("remote responded with success but provided no node info")]
MissingOnOpenInfo,
#[error("expected node to be a {expected:?}, but got a {actual:?}")]
UnexpectedNodeKind { expected: Kind, actual: Kind },
#[error("received unknown event (ordinal = {ordinal})")]
UnknownEvent { ordinal: u64 },
}
impl OpenError {
pub fn is_not_found_error(&self) -> bool {
matches!(
self,
OpenError::OpenError(zx_status::Status::NOT_FOUND)
| OpenError::Namespace(zx_status::Status::NOT_FOUND)
)
}
}
#[derive(Debug, Clone, Error)]
#[allow(missing_docs)]
pub enum CloneError {
#[error("while making a fidl proxy: {0}")]
CreateProxy(#[source] fidl::Error),
#[error("while sending clone request: {0}")]
SendCloneRequest(#[source] fidl::Error),
}
#[derive(Debug, Clone, Error)]
#[allow(missing_docs)]
pub enum CloseError {
#[error("while sending close request: {0}")]
SendCloseRequest(#[source] fidl::Error),
#[error("close failed with status: {0}")]
CloseError(#[source] zx_status::Status),
}
#[derive(Debug, Clone, Error)]
#[allow(missing_docs)]
pub enum RenameError {
#[error("while sending rename request")]
SendRenameRequest(#[source] fidl::Error),
#[error("while sending get_token request")]
SendGetTokenRequest(#[source] fidl::Error),
#[error("rename failed with status")]
RenameError(#[source] zx_status::Status),
#[error("while opening subdirectory")]
OpenError(#[from] OpenError),
#[error("get_token failed with status")]
GetTokenError(#[source] zx_status::Status),
#[error("no handle from get token")]
NoHandleError,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Kind {
Service,
File,
Directory,
Symlink,
Unknown,
}
impl Kind {
pub(crate) fn kind_of(info: &fio::NodeInfoDeprecated) -> Kind {
match info {
fio::NodeInfoDeprecated::Service(_) => Kind::Service,
fio::NodeInfoDeprecated::File(_) => Kind::File,
fio::NodeInfoDeprecated::Directory(_) => Kind::Directory,
fio::NodeInfoDeprecated::Symlink(_) => Kind::Symlink,
}
}
fn expect_file(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
match info {
fio::NodeInfoDeprecated::File(fio::FileObject { .. }) => Ok(()),
other => Err(Kind::kind_of(&other)),
}
}
fn expect_directory(info: fio::NodeInfoDeprecated) -> Result<(), Kind> {
match info {
fio::NodeInfoDeprecated::Directory(fio::DirectoryObject) => Ok(()),
other => Err(Kind::kind_of(&other)),
}
}
pub(crate) fn kind_of2(representation: &fio::Representation) -> Kind {
match representation {
fio::Representation::Connector(_) => Kind::Service,
fio::Representation::Directory(_) => Kind::Directory,
fio::Representation::File(_) => Kind::File,
fio::Representation::Symlink(_) => Kind::Symlink,
_ => Kind::Unknown,
}
}
fn expect_file2(representation: &fio::Representation) -> Result<(), Kind> {
match representation {
fio::Representation::File(fio::FileInfo { .. }) => Ok(()),
other => Err(Kind::kind_of2(other)),
}
}
fn expect_directory2(representation: &fio::Representation) -> Result<(), Kind> {
match representation {
fio::Representation::Directory(_) => Ok(()),
other => Err(Kind::kind_of2(other)),
}
}
}
pub async fn close(node: fio::NodeProxy) -> Result<(), CloseError> {
let result = node.close().await.map_err(CloseError::SendCloseRequest)?;
result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
}
pub(crate) async fn verify_node_describe_event(
node: fio::NodeProxy,
) -> Result<fio::NodeProxy, OpenError> {
match take_on_open_event(&node).await? {
fio::NodeEvent::OnOpen_ { s: status, info } => {
let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
info.ok_or(OpenError::MissingOnOpenInfo)?;
}
fio::NodeEvent::OnRepresentation { .. } => {}
fio::NodeEvent::_UnknownEvent { ordinal, .. } => {
return Err(OpenError::UnknownEvent { ordinal })
}
}
Ok(node)
}
pub(crate) async fn verify_directory_describe_event(
node: fio::DirectoryProxy,
) -> Result<fio::DirectoryProxy, OpenError> {
match take_on_open_event(&node).await? {
fio::DirectoryEvent::OnOpen_ { s: status, info } => {
let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
let () = Kind::expect_directory(*info).map_err(|actual| {
OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
})?;
}
fio::DirectoryEvent::OnRepresentation { payload } => {
let () = Kind::expect_directory2(&payload).map_err(|actual| {
OpenError::UnexpectedNodeKind { expected: Kind::Directory, actual }
})?;
}
fio::DirectoryEvent::_UnknownEvent { ordinal, .. } => {
return Err(OpenError::UnknownEvent { ordinal })
}
}
Ok(node)
}
pub(crate) async fn verify_file_describe_event(
node: fio::FileProxy,
) -> Result<fio::FileProxy, OpenError> {
match take_on_open_event(&node).await? {
fio::FileEvent::OnOpen_ { s: status, info } => {
let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
let () = Kind::expect_file(*info)
.map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
}
fio::FileEvent::OnRepresentation { payload } => {
let () = Kind::expect_file2(&payload)
.map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
}
fio::FileEvent::_UnknownEvent { ordinal, .. } => {
return Err(OpenError::UnknownEvent { ordinal })
}
}
Ok(node)
}
pub(crate) trait OnOpenEventProducer {
type Event;
type Stream: futures::Stream<Item = Result<Self::Event, fidl::Error>> + Unpin;
fn take_event_stream(&self) -> Self::Stream;
}
macro_rules! impl_on_open_event_producer {
($proxy:ty, $event:ty, $stream:ty) => {
impl OnOpenEventProducer for $proxy {
type Event = $event;
type Stream = $stream;
fn take_event_stream(&self) -> Self::Stream {
self.take_event_stream()
}
}
};
}
impl_on_open_event_producer!(fio::NodeProxy, fio::NodeEvent, fio::NodeEventStream);
impl_on_open_event_producer!(fio::FileProxy, fio::FileEvent, fio::FileEventStream);
impl_on_open_event_producer!(fio::DirectoryProxy, fio::DirectoryEvent, fio::DirectoryEventStream);
pub(crate) async fn take_on_open_event<T>(node: &T) -> Result<T::Event, OpenError>
where
T: OnOpenEventProducer,
{
node.take_event_stream().next().await.ok_or(OpenError::OnOpenEventStreamClosed)?.map_err(|e| {
if let fidl::Error::ClientChannelClosed { status, .. } = e {
OpenError::OpenError(status)
} else {
OpenError::OnOpenDecode(e)
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fuchsia_async as fasync;
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_real_node() {
let file_node = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
let protocol = file_node.query().await.unwrap();
assert_eq!(protocol, fio::FILE_PROTOCOL_NAME.as_bytes());
let dir_node = open_in_namespace("/pkg/data", fio::PERM_READABLE).unwrap();
let protocol = dir_node.query().await.unwrap();
assert_eq!(protocol, fio::DIRECTORY_PROTOCOL_NAME.as_bytes());
}
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_fake_node_under_of_root_namespace_entry() {
let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
assert_matches!(close(notfound).await, Err(_));
}
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_rejects_fake_root_namespace_entry() {
assert_matches!(
open_in_namespace("/fake", fio::PERM_READABLE),
Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
);
}
}