blob: c12c60076911e2ae3b87a57f04410992e94912f6 [file] [log] [blame]
// Copyright 2021 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 anyhow::Error;
use fdio::Namespace;
use fidl_fuchsia_io as fio;
use std::collections::{hash_map::Entry, HashMap};
use std::sync::Arc;
use vfs::directory::{
self, entry::DirectoryEntry, helper::DirectlyMutable, mutable::simple as pfs,
};
use vfs::execution_scope::ExecutionScope;
type Directory = Arc<pfs::Simple>;
/// Helper for binding entries in the namespace of the current process.
///
/// Namespace entries for served protocols are unbound when `NamespaceBinder` is dropped.
pub struct NamespaceBinder {
scope: ExecutionScope,
dirs: HashMap<String, Directory>,
}
impl NamespaceBinder {
pub fn new(scope: ExecutionScope) -> NamespaceBinder {
NamespaceBinder { scope, dirs: HashMap::new() }
}
/// Serves a protocol at the given path in the namespace of the current process.
///
/// `path` must be absolute, e.g. "/foo/bar", containing no "." nor ".." entries.
/// It is relative to the root of the namespace.
pub fn bind_at_path(
&mut self,
path: &str,
entry: Arc<dyn DirectoryEntry>,
) -> Result<(), Error> {
let ns = Namespace::installed()?;
let (dir_path, entry_name) =
path.rsplit_once('/').ok_or_else(|| format_err!("path must be absolute"))?;
let dir = match self.dirs.entry(dir_path.to_string()) {
Entry::Occupied(map_entry) => map_entry.into_mut(),
Entry::Vacant(map_entry) => {
let dir = pfs::simple();
let (client, server) = fidl::endpoints::create_endpoints();
directory::entry_container::Directory::open(
dir.clone(),
self.scope.clone(),
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::DIRECTORY,
vfs::path::Path::dot(),
fidl::endpoints::ServerEnd::new(server.into_channel()),
);
ns.bind(dir_path, client)?;
map_entry.insert(dir)
}
};
dir.add_entry(entry_name, entry)?;
Ok(())
}
}
impl Drop for NamespaceBinder {
fn drop(&mut self) {
let ns = Namespace::installed().unwrap();
for path in self.dirs.keys() {
ns.unbind(path).unwrap();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::ClientEnd;
use fidl_test_placeholders as echo;
use fuchsia_zircon as zx;
use futures::TryStreamExt;
#[fuchsia_async::run_singlethreaded(test)]
async fn test_bind_at_path() -> Result<(), Error> {
const NAMESPACE_PATH: &str = "/some/path/in/the/namespace";
let scope = ExecutionScope::new();
let mut ns = NamespaceBinder::new(scope);
let echo_entry =
vfs::service::host(move |mut stream: echo::EchoRequestStream| async move {
while let Ok(Some(request)) = stream.try_next().await {
let echo::EchoRequest::EchoString { value, responder } = request;
responder.send(Some(&value.unwrap())).expect("responder failed");
}
});
ns.bind_at_path(NAMESPACE_PATH, echo_entry)?;
let (client, server) = zx::Channel::create();
fdio::service_connect(NAMESPACE_PATH, server)?;
let proxy = ClientEnd::<echo::EchoMarker>::new(client).into_proxy()?;
let res = proxy.echo_string(Some("hello world")).await?;
assert_eq!(res, Some("hello world".to_string()));
Ok(())
}
}