blob: 5680e46457f4bd5461ed84e4ed61107deb6297ac [file] [log] [blame]
// Copyright 2019 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.
//! fuchsia.IO UTIL-ity library
//!
//! This crate provides various helper functions for iteracting with
//! `fidl_fuchsia_io::{DirectoryProxy, FileProxy, NodeProxy}` objects.
//!
//! Functions in the top-level module are deprecated. New uses of `io_util` should use the
//! `directory`, `file`, or `node` modules instead.
//!
//! Functions that contain `in_namespace` in their name operate on absolute paths in the process's
//! current namespace and utilize a blocking `fdio` call to open the proxy.
use {
anyhow::{format_err, Error},
fidl::endpoints::{create_proxy, Proxy, ServerEnd},
fidl_fuchsia_io::{
DirectoryMarker, DirectoryProxy, FileProxy, NodeProxy, MODE_TYPE_DIRECTORY,
OPEN_FLAG_CREATE, OPEN_FLAG_DIRECTORY,
},
fuchsia_zircon as zx,
std::path::{Component, Path},
};
pub mod directory;
pub mod file;
pub mod node;
// Reexported from fidl_fuchsia_io for convenience
pub const OPEN_RIGHT_READABLE: u32 = fidl_fuchsia_io::OPEN_RIGHT_READABLE;
pub const OPEN_RIGHT_WRITABLE: u32 = fidl_fuchsia_io::OPEN_RIGHT_WRITABLE;
pub const OPEN_RIGHT_EXECUTABLE: u32 = fidl_fuchsia_io::OPEN_RIGHT_EXECUTABLE;
/// open_node will return a NodeProxy opened to the node at the given path relative to the
/// given directory, or return an error if no such node exists (or some other FIDL error was
/// encountered). This function will not block.
pub fn open_node<'a>(
dir: &'a DirectoryProxy,
path: &'a Path,
flags: u32,
mode: u32,
) -> Result<NodeProxy, Error> {
let path = check_path(path)?;
let node = directory::open_node_no_describe(dir, path, flags, mode)?;
Ok(node)
}
/// open_directory will open a NodeProxy at the given path relative to the given directory, and
/// convert it into a DirectoryProxy. This function will not block.
pub fn open_directory<'a>(
dir: &'a DirectoryProxy,
path: &'a Path,
flags: u32,
) -> Result<DirectoryProxy, Error> {
let path = check_path(path)?;
let node = directory::open_directory_no_describe(dir, path, flags)?;
Ok(node)
}
/// open_file will open a NodeProxy at the given path relative to the given directory, and convert
/// it into a FileProxy. This function will not block.
pub fn open_file<'a>(
dir: &'a DirectoryProxy,
path: &'a Path,
flags: u32,
) -> Result<FileProxy, Error> {
let path = check_path(path)?;
let node = directory::open_file_no_describe(dir, path, flags)?;
Ok(node)
}
/// # Panics
///
/// Panics if any path component of `path` is not a valid utf-8 encoded string.
pub fn create_sub_directories(
root_dir: &DirectoryProxy,
path: &Path,
) -> Result<DirectoryProxy, Error> {
if path.components().next().is_none() {
return Err(format_err!("path must not be empty"));
}
let mut dir = None;
for part in path.components() {
if let Component::Normal(part) = part {
dir = Some({
let dir_ref = match dir.as_ref() {
Some(r) => r,
None => root_dir,
};
let (subdir, local_server_end) = create_proxy::<DirectoryMarker>()?;
dir_ref.open(
OPEN_FLAG_DIRECTORY
| OPEN_RIGHT_READABLE
| OPEN_RIGHT_WRITABLE
| OPEN_FLAG_CREATE,
MODE_TYPE_DIRECTORY,
part.to_str().unwrap(),
ServerEnd::new(local_server_end.into_channel()),
)?;
subdir
});
} else {
return Err(format_err!("invalid item in path: {:?}", part));
}
}
Ok(dir.unwrap())
}
// TODO: this function will block on the FDIO calls. This should be rewritten/wrapped/whatever to
// be asynchronous.
/// Connect a zx::Channel to a path in the namespace.
pub fn connect_in_namespace(
path: &str,
server_chan: zx::Channel,
flags: u32,
) -> Result<(), zx::Status> {
node::connect_in_namespace(path, flags, server_chan)
}
/// open_node_in_namespace will return a NodeProxy to the given path by using the default namespace
/// stored in fdio. The path argument must be an absolute path.
pub fn open_node_in_namespace(path: &str, flags: u32) -> Result<NodeProxy, Error> {
let node = node::open_in_namespace(path, flags)?;
Ok(node)
}
/// open_directory_in_namespace will open a NodeProxy to the given path and convert it into a
/// DirectoryProxy. The path argument must be an absolute path.
pub fn open_directory_in_namespace(path: &str, flags: u32) -> Result<DirectoryProxy, Error> {
let node = directory::open_in_namespace(path, flags)?;
Ok(node)
}
/// open_file_in_namespace will open a NodeProxy to the given path and convert it into a FileProxy.
/// The path argument must be an absolute path.
pub fn open_file_in_namespace(path: &str, flags: u32) -> Result<FileProxy, Error> {
let node = file::open_in_namespace(path, flags)?;
Ok(node)
}
pub async fn read_file_bytes(file: &FileProxy) -> Result<Vec<u8>, Error> {
let bytes = file::read(file).await?;
Ok(bytes)
}
pub async fn read_file(file: &FileProxy) -> Result<String, Error> {
let string = file::read_to_string(file).await?;
Ok(string)
}
/// Write the given bytes into a file open for writing.
pub async fn write_file_bytes(file: &FileProxy, data: &[u8]) -> Result<(), Error> {
file::write(file, data).await?;
Ok(())
}
/// Write the given string as UTF-8 bytes into a file open for writing.
pub async fn write_file(file: &FileProxy, data: &str) -> Result<(), Error> {
file::write(file, data).await?;
Ok(())
}
/// Write the given bytes into a file at `path`. The path must be an absolute path.
/// * If the file already exists, replaces existing contents.
/// * If the file does not exist, creates the file.
pub async fn write_path_bytes(path: &str, data: &[u8]) -> Result<(), Error> {
file::write_in_namespace(path, data).await?;
Ok(())
}
/// node_to_directory will convert the given NodeProxy into a DirectoryProxy. This is unsafe if the
/// type of the node is not checked first.
pub fn node_to_directory(node: NodeProxy) -> Result<DirectoryProxy, Error> {
let node_chan = node.into_channel().map_err(|e| format_err!("{:?}", e))?;
Ok(DirectoryProxy::from_channel(node_chan))
}
/// node_to_file will convert the given NodeProxy into a FileProxy. This is unsafe if the
/// type of the node is not checked first.
pub fn node_to_file(node: NodeProxy) -> Result<FileProxy, Error> {
let node_chan = node.into_channel().map_err(|e| format_err!("{:?}", e))?;
Ok(FileProxy::from_channel(node_chan))
}
/// clone_directory will create a clone of the given DirectoryProxy by calling its clone function.
/// This function will not block.
pub fn clone_directory(dir: &DirectoryProxy, flags: u32) -> Result<DirectoryProxy, Error> {
let node = directory::clone_no_describe(dir, Some(flags))?;
Ok(node)
}
/// canonicalize_path will remove a leading `/` if it exists, since it's always unnecessary and in
/// some cases disallowed (US-569).
pub fn canonicalize_path(path: &str) -> &str {
if path == "/" {
return ".";
}
if path.starts_with('/') {
return &path[1..];
}
path
}
/// Verifies path is relative, utf-8, and non-empty.
fn check_path<'a>(path: &'a Path) -> Result<&'a str, Error> {
if path.is_absolute() {
return Err(format_err!("path must be relative"));
}
let path = path.to_str().ok_or(format_err!("path contains invalid UTF-8"))?;
if path.is_empty() {
return Err(format_err!("path must not be empty"));
}
Ok(path)
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::ServerEnd,
fidl_fuchsia_io::DirectoryMarker,
fuchsia_async as fasync,
futures::future,
std::fs,
tempfile::{NamedTempFile, TempDir},
vfs::{
directory::entry::DirectoryEntry,
execution_scope::ExecutionScope,
file::pcb::{read_only_static, read_write, write_only},
pseudo_directory,
},
};
#[fasync::run_singlethreaded(test)]
async fn open_and_read_file_test() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
let data = "abc".repeat(10000);
fs::write(tempdir.path().join("myfile"), &data).expect("failed writing file");
let dir =
open_directory_in_namespace(tempdir.path().to_str().unwrap(), OPEN_RIGHT_READABLE)
.expect("could not open tmp dir");
let path = Path::new("myfile");
let file = open_file(&dir, &path, OPEN_RIGHT_READABLE).expect("could not open file");
let contents = read_file(&file).await.expect("could not read file");
assert_eq!(&contents, &data, "File contents did not match");
}
#[fasync::run_singlethreaded(test)]
async fn open_and_write_file_test() {
// Create temp dir for test.
let tempdir = TempDir::new().expect("failed to create tmp dir");
let dir = open_directory_in_namespace(
tempdir.path().to_str().unwrap(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("could not open tmp dir");
// Write contents.
let file_name = Path::new("myfile");
let data = "abc".repeat(10000);
let file = open_file(&dir, &file_name, OPEN_RIGHT_WRITABLE | OPEN_FLAG_CREATE)
.expect("could not open file");
write_file(&file, &data).await.expect("could not write file");
// Verify contents.
let contents = std::fs::read_to_string(tempdir.path().join(file_name)).unwrap();
assert_eq!(&contents, &data, "File contents did not match");
}
#[fasync::run_singlethreaded(test)]
async fn open_checks_path_validity() {
let dir =
open_directory_in_namespace("/pkg", OPEN_RIGHT_READABLE).expect("could not open /pkg");
assert!(open_file(&dir, Path::new(""), OPEN_RIGHT_READABLE).is_err());
assert!(open_file(&dir, Path::new("/"), OPEN_RIGHT_READABLE).is_err());
assert!(open_file(&dir, Path::new("/foo"), OPEN_RIGHT_READABLE).is_err());
assert!(open_directory(&dir, Path::new(""), OPEN_RIGHT_READABLE).is_err());
assert!(open_directory(&dir, Path::new("/"), OPEN_RIGHT_READABLE).is_err());
assert!(open_directory(&dir, Path::new("/foo"), OPEN_RIGHT_READABLE).is_err());
}
#[test]
fn test_canonicalize_path() {
assert_eq!(canonicalize_path("/"), ".");
assert_eq!(canonicalize_path("/foo"), "foo");
assert_eq!(canonicalize_path("/foo/bar/"), "foo/bar/");
assert_eq!(canonicalize_path("."), ".");
assert_eq!(canonicalize_path("./"), "./");
assert_eq!(canonicalize_path("foo/bar/"), "foo/bar/");
}
#[fasync::run_until_stalled(test)]
async fn flags_test() -> Result<(), Error> {
let example_dir = pseudo_directory! {
"read_only" => read_only_static("read_only"),
"read_write" => read_write(
|| future::ok("read_write".as_bytes().into()),
100,
|_| future::ok(()),
),
"write_only" => write_only(100, |_| future::ok(())),
};
let (example_dir_proxy, example_dir_service) =
fidl::endpoints::create_proxy::<DirectoryMarker>()?;
let scope = ExecutionScope::from_executor(Box::new(fasync::EHandle::local()));
example_dir.open(
scope,
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
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),
] {
let file_proxy = open_file(&example_dir_proxy, &Path::new(file_name), flags)?;
match (should_succeed, file_proxy.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!(file_name, read_file(&file_proxy).await.expect("failed to read file"));
}
if flags & OPEN_RIGHT_WRITABLE != 0 {
let (s, _) = file_proxy.write(b"write_only").await?;
assert_eq!(zx::Status::OK, zx::Status::from_raw(s));
}
assert_eq!(zx::Status::OK, zx::Status::from_raw(file_proxy.close().await?));
}
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn open_directory_in_namespace_rejects_files() {
let tempfile = NamedTempFile::new().expect("failed to create tmp file");
let dir =
open_directory_in_namespace(tempfile.path().to_str().unwrap(), OPEN_RIGHT_READABLE)
.expect("could not send open request");
let channel = dir.into_channel().expect("Could not convert to channel").into_zx_channel();
let signals = fasync::OnSignals::new(&channel, zx::Signals::CHANNEL_PEER_CLOSED);
// We should see a PEER_CLOSED because we tried to open a file as a directory
signals.await.expect("Error waiting for peer closed");
}
#[fasync::run_singlethreaded(test)]
async fn create_sub_directories_test() -> Result<(), Error> {
let tempdir = TempDir::new()?;
let path = Path::new("path/to/example/dir");
let file_name = Path::new("example_file_name");
let data = "file contents";
let root_dir = open_directory_in_namespace(
tempdir.path().to_str().unwrap(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)?;
let sub_dir = create_sub_directories(&root_dir, &path)?;
let file = open_file(
&sub_dir,
&file_name,
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE | OPEN_FLAG_CREATE,
)?;
write_file(&file, &data).await.expect("writing to the file failed");
let contents = std::fs::read_to_string(tempdir.path().join(path).join(file_name))?;
assert_eq!(&contents, &data, "File contents did not match");
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn write_path_bytes_create_test() {
// Create temp dir for test, and bind it to our namespace.
let tempdir = TempDir::new().expect("failed to create tmp dir");
let _dir =
open_directory_in_namespace(tempdir.path().to_str().unwrap(), OPEN_RIGHT_READABLE)
.expect("could not open tmp dir");
let path = tempdir.path().join(Path::new("write_path_bytes_create"));
let path_string = path.to_str().expect("converting path to string failed");
// Write contents.
let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
write_path_bytes(&path_string, data).await.expect("could not write to path");
// Verify contents.
let contents = std::fs::read(path).unwrap();
assert_eq!(&contents, &data, "Contents did not match");
}
#[fasync::run_singlethreaded(test)]
async fn write_path_bytes_replace_test() {
// Create temp dir for test, and bind it to our namespace.
let tempdir = TempDir::new().expect("failed to create tmp dir");
let _dir =
open_directory_in_namespace(tempdir.path().to_str().unwrap(), OPEN_RIGHT_READABLE)
.expect("could not open tmp dir");
let path = tempdir.path().join(Path::new("write_path_bytes_replace"));
let path_string = path.to_str().expect("converting path to string failed");
// Write contents.
let original_data = b"\x80\x81"; // Non UTF-8 data: a continuation byte as the first byte.
write_path_bytes(&path_string, original_data).await.expect("could not write to path");
// Over-write contents.
let new_data = b"\x82"; // Non UTF-8 data: a continuation byte as the first byte.
write_path_bytes(&path_string, new_data).await.expect("could not over-write to path");
// Verify contents.
let contents = std::fs::read(path).unwrap();
assert_eq!(&contents, &new_data, "Contents did not match");
}
}