blob: 3a9a9231c5c62775ea9af1edc6d4b7bf32d97b64 [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 directories.
use {
crate::node::{self, CloneError, CloseError, OpenError},
fidl::endpoints::ServerEnd,
fidl_fuchsia_io::{
DirectoryMarker, DirectoryProxy, FileMarker, FileProxy, NodeMarker, NodeProxy,
},
fuchsia_zircon_status as zx_status,
};
/// Opens the given `path` from the current namespace as a [`DirectoryProxy`]. The target is not
/// verified to be any particular type and may not implement the fuchsia.io.Directory protocol.
#[cfg(target_os = "fuchsia")]
pub fn open_in_namespace(path: &str, flags: u32) -> Result<DirectoryProxy, OpenError> {
let (dir, server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().map_err(OpenError::CreateProxy)?;
let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DIRECTORY;
node::connect_in_namespace(path, flags, server_end.into_channel())
.map_err(OpenError::Namespace)?;
Ok(dir)
}
/// Opens the given `path` from the given `parent` directory as a [`DirectoryProxy`]. The target is
/// not verified to be any particular type and may not implement the fuchsia.io.Directory protocol.
pub fn open_directory_no_describe(
parent: &DirectoryProxy,
path: &str,
flags: u32,
) -> Result<DirectoryProxy, OpenError> {
let (dir, server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().map_err(OpenError::CreateProxy)?;
let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DIRECTORY;
let mode = fidl_fuchsia_io::MODE_TYPE_DIRECTORY;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
Ok(dir)
}
/// Opens the given `path` from given `parent` directory as a [`DirectoryProxy`], verifying that
/// the target implements the fuchsia.io.Directory protocol.
pub async fn open_directory(
parent: &DirectoryProxy,
path: &str,
flags: u32,
) -> Result<DirectoryProxy, OpenError> {
let (dir, server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().map_err(OpenError::CreateProxy)?;
let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DIRECTORY | fidl_fuchsia_io::OPEN_FLAG_DESCRIBE;
let mode = fidl_fuchsia_io::MODE_TYPE_DIRECTORY;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
// wait for the directory to open and report success.
node::verify_directory_describe_event(dir).await
}
/// Creates a directory named `path` within the `parent` directory.
pub async fn create_directory(
parent: &DirectoryProxy,
path: &str,
flags: u32,
) -> Result<DirectoryProxy, OpenError> {
let (dir, server_end) =
fidl::endpoints::create_proxy::<DirectoryMarker>().map_err(OpenError::CreateProxy)?;
// NB: POSIX does not allow open(2) to create dirs, but fuchsia.io does not have an equivalent
// of mkdir(2), so on Fuchsia we're expected to call open on a DirectoryMarker with (flags &
// OPEN_FLAG_CREATE) set.
// (mode & MODE_TYPE_DIRECTORY) is also required, although it is redundant (the fact that we
// opened a DirectoryMarker is the main way that the underlying filesystem understands our
// intention.)
let flags = flags
| fidl_fuchsia_io::OPEN_FLAG_CREATE
| fidl_fuchsia_io::OPEN_FLAG_DIRECTORY
| fidl_fuchsia_io::OPEN_FLAG_DESCRIBE;
let mode = fidl_fuchsia_io::MODE_TYPE_DIRECTORY;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
// wait for the directory to open and report success.
node::verify_directory_describe_event(dir).await
}
/// Opens the given `path` from the given `parent` directory as a [`FileProxy`]. The target is not
/// verified to be any particular type and may not implement the fuchsia.io.File protocol.
pub fn open_file_no_describe(
parent: &DirectoryProxy,
path: &str,
flags: u32,
) -> Result<FileProxy, OpenError> {
let (file, server_end) =
fidl::endpoints::create_proxy::<FileMarker>().map_err(OpenError::CreateProxy)?;
let mode = fidl_fuchsia_io::MODE_TYPE_FILE;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
Ok(file)
}
/// Opens the given `path` from given `parent` directory as a [`FileProxy`], verifying that the
/// target implements the fuchsia.io.File protocol.
pub async fn open_file(
parent: &DirectoryProxy,
path: &str,
flags: u32,
) -> Result<FileProxy, OpenError> {
let (file, server_end) =
fidl::endpoints::create_proxy::<FileMarker>().map_err(OpenError::CreateProxy)?;
let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DESCRIBE;
let mode = fidl_fuchsia_io::MODE_TYPE_FILE;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
// wait for the file to open and report success.
node::verify_file_describe_event(file).await
}
/// Opens the given `path` from the given `parent` directory as a [`NodeProxy`], verifying that the
/// target implements the fuchsia.io.Node protocol.
pub async fn open_node(
parent: &DirectoryProxy,
path: &str,
flags: u32,
mode: u32,
) -> Result<NodeProxy, OpenError> {
let (file, server_end) =
fidl::endpoints::create_proxy::<NodeMarker>().map_err(OpenError::CreateProxy)?;
let flags = flags | fidl_fuchsia_io::OPEN_FLAG_DESCRIBE;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
// wait for the file to open and report success.
node::verify_node_describe_event(file).await
}
/// Opens the given `path` from the given `parent` directory as a [`NodeProxy`]. The target is not
/// verified to be any particular type and may not implement the fuchsia.io.Node protocol.
pub fn open_node_no_describe(
parent: &DirectoryProxy,
path: &str,
flags: u32,
mode: u32,
) -> Result<NodeProxy, OpenError> {
let (file, server_end) =
fidl::endpoints::create_proxy::<NodeMarker>().map_err(OpenError::CreateProxy)?;
parent
.open(flags, mode, path, ServerEnd::new(server_end.into_channel()))
.map_err(OpenError::SendOpenRequest)?;
Ok(file)
}
/// Opens a new connection to the given directory using `flags` if provided, or
/// `fidl_fuchsia_io::OPEN_FLAG_SAME_RIGHTS` otherwise.
pub fn clone_no_describe(
dir: &DirectoryProxy,
flags: Option<u32>,
) -> Result<DirectoryProxy, CloneError> {
let (clone, server_end) = fidl::endpoints::create_proxy().map_err(CloneError::CreateProxy)?;
clone_onto_no_describe(dir, flags, server_end)?;
Ok(clone)
}
/// Opens a new connection to the given directory onto the given server end using `flags` if
/// provided, or `fidl_fuchsia_io::OPEN_FLAG_SAME_RIGHTS` otherwise.
pub fn clone_onto_no_describe(
dir: &DirectoryProxy,
flags: Option<u32>,
request: ServerEnd<DirectoryMarker>,
) -> Result<(), CloneError> {
let node_request = ServerEnd::new(request.into_channel());
let flags = flags.unwrap_or(fidl_fuchsia_io::CLONE_FLAG_SAME_RIGHTS);
dir.clone(flags, node_request).map_err(CloneError::SendCloneRequest)?;
Ok(())
}
/// Gracefully closes the directory proxy from the remote end.
pub async fn close(dir: DirectoryProxy) -> Result<(), CloseError> {
let status = dir.close().await.map_err(CloseError::SendCloseRequest)?;
zx_status::Status::ok(status).map_err(CloseError::CloseError)
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl_fuchsia_io as fio,
fidl_fuchsia_io::{
OPEN_FLAG_CREATE, OPEN_FLAG_CREATE_IF_ABSENT, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_async as fasync,
futures::prelude::*,
matches::assert_matches,
tempfile::TempDir,
vfs::{
directory::entry::DirectoryEntry,
execution_scope::ExecutionScope,
file::vmo::{read_only_static, read_write, simple_init_vmo_with_capacity, write_only},
pseudo_directory,
},
};
const DATA_FILE_CONTENTS: &str = "Hello World!\n";
fn open_pkg() -> DirectoryProxy {
open_in_namespace("/pkg", OPEN_RIGHT_READABLE).unwrap()
}
fn open_tmp() -> (TempDir, DirectoryProxy) {
let tempdir = TempDir::new().expect("failed to create tmp dir");
let proxy = open_in_namespace(
tempdir.path().to_str().unwrap(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.unwrap();
(tempdir, proxy)
}
// open_in_namespace
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_real_dir() {
let exists = open_in_namespace("/pkg", OPEN_RIGHT_READABLE).unwrap();
assert_matches!(close(exists).await, Ok(()));
}
#[fasync::run_singlethreaded(test)]
async fn open_in_namespace_opens_fake_subdir_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))
);
}
// open_directory_no_describe
#[fasync::run_singlethreaded(test)]
async fn open_directory_no_describe_opens_real_dir() {
let pkg = open_pkg();
let data = open_directory_no_describe(&pkg, "data", OPEN_RIGHT_READABLE).unwrap();
close(data).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn open_directory_no_describe_opens_fake_dir() {
let pkg = open_pkg();
let fake = open_directory_no_describe(&pkg, "fake", OPEN_RIGHT_READABLE).unwrap();
// The open error is not detected until the proxy is interacted with.
assert_matches!(close(fake).await, Err(_));
}
// open_directory
#[fasync::run_singlethreaded(test)]
async fn open_directory_opens_real_dir() {
let pkg = open_pkg();
let data = open_directory(&pkg, "data", OPEN_RIGHT_READABLE).await.unwrap();
close(data).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn open_directory_rejects_fake_dir() {
let pkg = open_pkg();
assert_matches!(
open_directory(&pkg, "fake", OPEN_RIGHT_READABLE).await,
Err(OpenError::OpenError(zx_status::Status::NOT_FOUND))
);
}
#[fasync::run_singlethreaded(test)]
async fn open_directory_rejects_file() {
let pkg = open_pkg();
assert_matches!(
open_directory(&pkg, "data/file", OPEN_RIGHT_READABLE).await,
Err(OpenError::OpenError(zx_status::Status::NOT_DIR))
);
}
// create_directory
#[fasync::run_singlethreaded(test)]
async fn create_directory_simple() {
let (_tmp, proxy) = open_tmp();
let dir = create_directory(&proxy, "dir", OPEN_RIGHT_READABLE).await.unwrap();
crate::directory::close(dir).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn create_directory_add_file() {
let (_tmp, proxy) = open_tmp();
let dir = create_directory(&proxy, "dir", OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE)
.await
.unwrap();
let file = open_file(
&dir,
"data",
OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_IF_ABSENT | OPEN_RIGHT_READABLE,
)
.await
.unwrap();
crate::file::close(file).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn create_directory_existing_dir_opens() {
let (_tmp, proxy) = open_tmp();
let dir = create_directory(&proxy, "dir", OPEN_RIGHT_READABLE).await.unwrap();
crate::directory::close(dir).await.unwrap();
create_directory(&proxy, "dir", OPEN_RIGHT_READABLE).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn create_directory_existing_dir_fails_if_flag_set() {
let (_tmp, proxy) = open_tmp();
let dir = create_directory(&proxy, "dir", OPEN_FLAG_CREATE_IF_ABSENT | OPEN_RIGHT_READABLE)
.await
.unwrap();
crate::directory::close(dir).await.unwrap();
assert_matches!(
create_directory(&proxy, "dir", OPEN_FLAG_CREATE_IF_ABSENT | OPEN_RIGHT_READABLE).await,
Err(_)
);
}
// open_file_no_describe
#[fasync::run_singlethreaded(test)]
async fn open_file_no_describe_opens_real_file() {
let pkg = open_pkg();
let file = open_file_no_describe(&pkg, "data/file", OPEN_RIGHT_READABLE).unwrap();
crate::file::close(file).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn open_file_no_describe_opens_fake_file() {
let pkg = open_pkg();
let fake = open_file_no_describe(&pkg, "data/fake", OPEN_RIGHT_READABLE).unwrap();
// The open error is not detected until the proxy is interacted with.
assert_matches!(crate::file::close(fake).await, Err(_));
}
// open_file
#[fasync::run_singlethreaded(test)]
async fn open_file_opens_real_file() {
let pkg = open_pkg();
let file = open_file(&pkg, "data/file", OPEN_RIGHT_READABLE).await.unwrap();
assert_eq!(
file.seek(0, fidl_fuchsia_io::SeekOrigin::End).await.unwrap(),
(zx_status::Status::OK.into_raw(), DATA_FILE_CONTENTS.len() as u64)
);
crate::file::close(file).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn open_file_rejects_fake_file() {
let pkg = open_pkg();
assert_matches!(
open_file(&pkg, "data/fake", OPEN_RIGHT_READABLE).await,
Err(OpenError::OpenError(zx_status::Status::NOT_FOUND))
);
}
#[fasync::run_singlethreaded(test)]
async fn open_file_rejects_dir() {
let pkg = open_pkg();
assert_matches!(
open_file(&pkg, "data", OPEN_RIGHT_READABLE).await,
Err(OpenError::UnexpectedNodeKind {
expected: node::Kind::File,
actual: node::Kind::Directory,
})
);
}
#[fasync::run_until_stalled(test)]
async fn open_file_flags() {
let example_dir = pseudo_directory! {
"read_only" => read_only_static("read_only"),
"read_write" => read_write(
simple_init_vmo_with_capacity("read_write".as_bytes(), 100),
|_| future::ready(()),
),
"write_only" => write_only(simple_init_vmo_with_capacity(&[], 100), |_| future::ready(())),
};
let (example_dir_proxy, example_dir_service) =
fidl::endpoints::create_proxy::<DirectoryMarker>().unwrap();
let scope = ExecutionScope::new();
example_dir.open(
scope,
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
fidl_fuchsia_io::MODE_TYPE_DIRECTORY,
vfs::path::Path::empty(),
ServerEnd::new(example_dir_service.into_channel()),
);
for (file_name, flags, should_succeed) in vec![
("read_only", OPEN_RIGHT_READABLE, true),
("read_only", OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE, false),
("read_only", OPEN_RIGHT_WRITABLE, false),
("read_write", OPEN_RIGHT_READABLE, true),
("read_write", OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE, true),
("read_write", OPEN_RIGHT_WRITABLE, true),
("write_only", OPEN_RIGHT_READABLE, false),
("write_only", OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE, false),
("write_only", OPEN_RIGHT_WRITABLE, true),
] {
// open_file_no_describe
let file = open_file_no_describe(&example_dir_proxy, file_name, flags).unwrap();
match (should_succeed, file.describe().await) {
(true, Ok(_)) => (),
(false, Err(_)) => continue,
(true, Err(e)) => {
panic!("failed to open when expected success, couldn't describe: {:?}", e)
}
(false, Ok(d)) => {
panic!("successfully opened when expected failure, could describe: {:?}", d)
}
}
if flags & OPEN_RIGHT_READABLE != 0 {
assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
}
if flags & OPEN_RIGHT_WRITABLE != 0 {
let (s, _) = file.write(b"write_only").await.unwrap();
assert_eq!(zx_status::Status::ok(s), Ok(()));
}
crate::file::close(file).await.unwrap();
// open_file
match open_file(&example_dir_proxy, file_name, flags).await {
Ok(file) if should_succeed => {
if flags & OPEN_RIGHT_READABLE != 0 {
assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
}
if flags & OPEN_RIGHT_WRITABLE != 0 {
let (s, _) = file.write(b"write_only").await.unwrap();
assert_eq!(zx_status::Status::ok(s), Ok(()));
}
crate::file::close(file).await.unwrap();
}
Ok(_) => {
panic!("successfully opened when expected failure: {:?}", (file_name, flags))
}
Err(e) if should_succeed => {
panic!("failed to open when expected success: {:?}", (e, file_name, flags))
}
Err(_) => {}
}
}
}
// open_node_no_describe
#[fasync::run_singlethreaded(test)]
async fn open_node_no_describe_opens_real_node() {
let pkg = open_pkg();
let node = open_node_no_describe(&pkg, "data", OPEN_RIGHT_READABLE, 0).unwrap();
crate::node::close(node).await.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn open_node_no_describe_opens_fake_node() {
let pkg = open_pkg();
let fake = open_node_no_describe(&pkg, "fake", OPEN_RIGHT_READABLE, 0).unwrap();
// The open error is not detected until the proxy is interacted with.
assert_matches!(crate::node::close(fake).await, Err(_));
}
// clone_no_describe
#[fasync::run_singlethreaded(test)]
async fn clone_no_describe_no_flags_same_rights() {
let (dir, mut stream) =
fidl::endpoints::create_proxy_and_stream::<DirectoryMarker>().unwrap();
clone_no_describe(&dir, None).unwrap();
assert_matches!(
stream.next().await,
Some(Ok(fio::DirectoryRequest::Clone {
flags: fidl_fuchsia_io::CLONE_FLAG_SAME_RIGHTS,
..
}))
);
}
#[fasync::run_singlethreaded(test)]
async fn clone_no_describe_flags_passed_through() {
let (dir, mut stream) =
fidl::endpoints::create_proxy_and_stream::<DirectoryMarker>().unwrap();
clone_no_describe(&dir, Some(42)).unwrap();
assert_matches!(
stream.next().await,
Some(Ok(fio::DirectoryRequest::Clone { flags: 42, .. }))
);
}
}