blob: 4f22198cfb799f6580b58e318dd9bf424b7811a5 [file] [log] [blame]
// Copyright 2020 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.
//! Utility functions for fuchsia.io nodes.
use {
fidl_fuchsia_io::{
DirectoryEvent, DirectoryObject, DirectoryProxy, FileEvent, FileObject, FileProxy,
NodeEvent, NodeInfo, NodeProxy, Vmofile,
},
fuchsia_zircon_status as zx_status,
futures::prelude::*,
thiserror::Error,
};
#[cfg(target_os = "fuchsia")]
use {fidl_fuchsia_io::NodeMarker, fuchsia_zircon as zx};
/// An error encountered while opening a node
#[derive(Debug, 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 },
}
/// An error encountered while cloning a node
#[derive(Debug, 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),
}
/// An error encountered while closing a node
#[derive(Debug, 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),
}
/// The type of a filesystem node
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Kind {
Service,
File,
Directory,
Pipe,
Vmofile,
Device,
Tty,
DatagramSocket,
StreamSocket,
}
impl Kind {
fn kind_of(info: &NodeInfo) -> Kind {
match info {
NodeInfo::Service(_) => Kind::Service,
NodeInfo::File(_) => Kind::File,
NodeInfo::Directory(_) => Kind::Directory,
NodeInfo::Pipe(_) => Kind::Pipe,
NodeInfo::Vmofile(_) => Kind::Vmofile,
NodeInfo::Device(_) => Kind::Device,
NodeInfo::Tty(_) => Kind::Tty,
NodeInfo::DatagramSocket(_) => Kind::DatagramSocket,
NodeInfo::StreamSocket(_) => Kind::StreamSocket,
}
}
fn expect_file(info: NodeInfo) -> Result<(), Kind> {
match info {
NodeInfo::File(FileObject { event: _, stream: None })
| NodeInfo::Vmofile(Vmofile { .. }) => Ok(()),
other => Err(Kind::kind_of(&other)),
}
}
fn expect_directory(info: NodeInfo) -> Result<(), Kind> {
match info {
NodeInfo::Directory(DirectoryObject) => Ok(()),
other => Err(Kind::kind_of(&other)),
}
}
}
// TODO namespace.connect is synchronous and may involve fdio making synchronous fidl calls to
// remote directories. If/when fdio exposes the root namespace mapping or an API to connect to
// nodes asynchronously, this function should be updated to use that.
/// Connect a zx::Channel to a path in the current namespace.
#[cfg(target_os = "fuchsia")]
pub fn connect_in_namespace(
path: &str,
flags: u32,
chan: zx::Channel,
) -> Result<(), zx_status::Status> {
let namespace = fdio::Namespace::installed()?;
namespace.connect(path, flags, chan)?;
Ok(())
}
/// Opens the given `path` from the current namespace as a [`NodeProxy`]. The target is not
/// verified to be any particular type and may not implement the fuchsia.io.Node protocol.
#[cfg(target_os = "fuchsia")]
pub fn open_in_namespace(path: &str, flags: u32) -> Result<NodeProxy, OpenError> {
let (node, server_end) =
fidl::endpoints::create_proxy::<NodeMarker>().map_err(OpenError::CreateProxy)?;
connect_in_namespace(path, flags, server_end.into_channel()).map_err(OpenError::Namespace)?;
Ok(node)
}
/// Gracefully closes the node proxy from the remote end.
pub async fn close(node: NodeProxy) -> Result<(), CloseError> {
let status = node.close().await.map_err(CloseError::SendCloseRequest)?;
zx_status::Status::ok(status).map_err(CloseError::CloseError)
}
/// Consume the first event from this NodeProxy's event stream, returning the proxy if it is
/// the expected type or an error otherwise.
pub(crate) async fn verify_node_describe_event(
node: NodeProxy,
) -> Result<NodeProxy, OpenError> {
let mut events = node.take_event_stream();
let NodeEvent::OnOpen_ { s: status, info } = events
.next()
.await
.ok_or(OpenError::OnOpenEventStreamClosed)?
.map_err(OpenError::OnOpenDecode)?;
let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
info.ok_or(OpenError::MissingOnOpenInfo)?;
Ok(node)
}
/// Consume the first event from this DirectoryProxy's event stream, returning the proxy if it is
/// the expected type or an error otherwise.
pub(crate) async fn verify_directory_describe_event(
node: DirectoryProxy,
) -> Result<DirectoryProxy, OpenError> {
let mut events = node.take_event_stream();
let DirectoryEvent::OnOpen_ { s: status, info } = events
.next()
.await
.ok_or(OpenError::OnOpenEventStreamClosed)?
.map_err(OpenError::OnOpenDecode)?;
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 })?;
Ok(node)
}
/// Consume the first event from this FileProxy's event stream, returning the proxy if it is the
/// expected type or an error otherwise.
pub(crate) async fn verify_file_describe_event(node: FileProxy) -> Result<FileProxy, OpenError> {
let mut events = node.take_event_stream();
let FileEvent::OnOpen_ { s: status, info } = events
.next()
.await
.ok_or(OpenError::OnOpenEventStreamClosed)?
.map_err(OpenError::OnOpenDecode)?;
let () = zx_status::Status::ok(status).map_err(OpenError::OpenError)?;
let info = info.ok_or(OpenError::MissingOnOpenInfo)?;
let _event = Kind::expect_file(*info)
.map_err(|actual| OpenError::UnexpectedNodeKind { expected: Kind::File, actual })?;
Ok(node)
}
#[cfg(test)]
mod tests {
use {super::*, crate::OPEN_RIGHT_READABLE, fuchsia_async as fasync, matches::assert_matches};
// open_in_namespace
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_real_node() {
let file_node = open_in_namespace("/pkg/data/file", OPEN_RIGHT_READABLE).unwrap();
let info = file_node.describe().await.unwrap();
assert_matches!(Kind::expect_file(info), Ok(()));
let dir_node = open_in_namespace("/pkg/data", OPEN_RIGHT_READABLE).unwrap();
let info = dir_node.describe().await.unwrap();
assert_eq!(Kind::expect_directory(info), Ok(()));
}
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_fake_node_under_of_root_namespace_entry() {
let notfound = open_in_namespace("/pkg/fake", OPEN_RIGHT_READABLE).unwrap();
// The open error is not detected until the proxy is interacted with.
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", OPEN_RIGHT_READABLE),
Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
);
}
}