blob: 22f816a0ca72134ef241b16e801b73217906b98e [file] [log] [blame]
// Copyright 2023 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 core::fmt;
use fidl::endpoints::ClientEnd;
use fidl_fuchsia_component_sandbox as fsandbox;
use fidl_fuchsia_io as fio;
use fuchsia_zircon::{self as zx, AsHandleRef};
use std::sync::Arc;
use vfs::{directory::entry::DirectoryEntry, remote::RemoteLike};
use crate::{registry, CapabilityTrait};
/// A capability that is a `fuchsia.io` directory.
///
/// The directory may optionally be backed by a future that serves its contents.
pub struct Directory {
/// The FIDL representation of this [Directory].
client_end: ClientEnd<fio::DirectoryMarker>,
}
impl Directory {
/// Create a new [Directory] capability.
///
/// Arguments:
///
/// * `client_end` - A `fuchsia.io/Directory` client endpoint.
pub fn new(client_end: ClientEnd<fio::DirectoryMarker>) -> Self {
Directory { client_end: client_end }
}
/// Turn the [Directory] into a remote VFS node.
pub(crate) fn into_remote(self) -> Arc<impl RemoteLike + DirectoryEntry> {
let client_end = ClientEnd::<fio::DirectoryMarker>::from(self);
vfs::remote::remote_dir(client_end.into_proxy().unwrap())
}
}
impl fmt::Debug for Directory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Directory").field("client_end", &self.client_end).finish()
}
}
impl Clone for Directory {
fn clone(&self) -> Self {
// Call `fuchsia.io/Directory.Clone` without converting the ClientEnd into a proxy.
// This is necessary because we the conversion consumes the ClientEnd, but we can't take
// it out of non-mut `&self`.
let (clone_client_end, clone_server_end) = zx::Channel::create();
let raw_handle = self.client_end.as_handle_ref().raw_handle();
// SAFETY: the channel is forgotten at the end of scope so it is not double closed.
unsafe {
let borrowed: zx::Channel = zx::Handle::from_raw(raw_handle).into();
let directory = fio::DirectorySynchronousProxy::new(borrowed);
let _ = directory.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, clone_server_end.into());
std::mem::forget(directory.into_channel());
}
let client_end: ClientEnd<fio::DirectoryMarker> = clone_client_end.into();
Self { client_end: client_end }
}
}
impl CapabilityTrait for Directory {}
impl From<ClientEnd<fio::DirectoryMarker>> for Directory {
fn from(client_end: ClientEnd<fio::DirectoryMarker>) -> Self {
Directory { client_end: client_end }
}
}
impl From<Directory> for ClientEnd<fio::DirectoryMarker> {
/// Return a channel to the Directory and store the channel in
/// the registry.
fn from(directory: Directory) -> Self {
let Directory { client_end } = directory;
// Create a null directory for the registry.
let new_dict =
Directory::new(ClientEnd::<fio::DirectoryMarker>::new(zx::Handle::invalid().into()));
// Move this capability into the registry.
let koid = client_end.get_koid().unwrap();
registry::insert(new_dict.into(), koid);
client_end
}
}
impl From<Directory> for fsandbox::Capability {
fn from(directory: Directory) -> Self {
Self::Directory(directory.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Open;
use fidl::endpoints::{create_endpoints, ServerEnd};
use fidl_fuchsia_io as fio;
use fuchsia_zircon as zx;
use futures::channel::mpsc;
use futures::StreamExt;
use vfs::{
directory::{
entry::{EntryInfo, OpenRequest},
entry_container::Directory as VfsDirectory,
},
execution_scope::ExecutionScope,
path::Path,
pseudo_directory,
};
fn serve_vfs_dir(root: Arc<impl VfsDirectory>) -> ClientEnd<fio::DirectoryMarker> {
let scope = ExecutionScope::new();
let (client, server) = create_endpoints::<fio::DirectoryMarker>();
root.open(
scope.clone(),
fio::OpenFlags::RIGHT_READABLE,
vfs::path::Path::dot(),
ServerEnd::new(server.into_channel()),
);
client
}
#[fuchsia::test]
async fn test_clone() {
let (open_tx, mut open_rx) = mpsc::channel::<()>(1);
struct MockDir(mpsc::Sender<()>);
impl DirectoryEntry for MockDir {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
request.open_remote(self)
}
}
impl RemoteLike for MockDir {
fn open(
self: Arc<Self>,
_scope: ExecutionScope,
flags: fio::OpenFlags,
relative_path: Path,
_server_end: ServerEnd<fio::NodeMarker>,
) {
assert_eq!(relative_path.into_string(), "");
assert_eq!(flags, fio::OpenFlags::DIRECTORY);
self.0.clone().try_send(()).unwrap();
}
}
let open = Open::new(Arc::new(MockDir(open_tx)));
let fs = pseudo_directory! {
"foo" => open.try_into_directory_entry().unwrap(),
};
// Create a Directory capability, and a clone.
let dir = Directory::from(serve_vfs_dir(fs));
let dir_clone = dir.clone();
// Open the original directory.
let client_end: ClientEnd<fio::DirectoryMarker> = dir.into();
let dir_proxy = client_end.into_proxy().unwrap();
let (_client_end, server_end) = create_endpoints::<fio::NodeMarker>();
dir_proxy
.open(fio::OpenFlags::DIRECTORY, fio::ModeType::empty(), "foo", server_end)
.unwrap();
// The Open capability should receive the Open request.
open_rx.next().await.unwrap();
// Open the clone.
let client_end: ClientEnd<fio::DirectoryMarker> = dir_clone.into();
let clone_proxy = client_end.into_proxy().unwrap();
let (_client_end, server_end) = create_endpoints::<fio::NodeMarker>();
clone_proxy
.open(fio::OpenFlags::DIRECTORY, fio::ModeType::empty(), "foo", server_end)
.unwrap();
// The Open capability should receive the Open request from the clone.
open_rx.next().await.unwrap();
}
}