blob: d72bb2876eb71c79300b08ea2f1eacbb17575fc6 [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.
// TODO(fxr/66157): Migrate to the new library that substitutes io_utils and fuchsia_fs::directory.
// Ask for host-side support on the new library (fxr/467217).
use {
anyhow::{format_err, Result},
fidl_fuchsia_io as fio,
fuchsia_fs::directory::readdir,
fuchsia_fs::{
directory::{clone_no_describe, open_directory_no_describe, open_file_no_describe},
file::{close, read, read_to_string, write},
},
fuchsia_zircon_status::Status,
futures::lock::Mutex,
std::path::{Path, PathBuf},
};
// A convenience wrapper over a FIDL DirectoryProxy.
pub struct Directory {
path: PathBuf,
proxy: fio::DirectoryProxy,
// The `fuchsia.io.Directory` protocol is stateful in readdir, and the associated `fuchsia_fs::directory`
// library used for enumerating the directory has no mechanism for synchronization of readdir
// operations, as such this mutex must be held throughout directory enumeration in order to
// avoid race conditions from concurrent rewinds and reads.
readdir_mutex: Mutex<()>,
}
impl Directory {
// Connect to a directory in the namespace
#[cfg(target_os = "fuchsia")]
pub fn from_namespace<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_str = path
.as_ref()
.as_os_str()
.to_str()
.ok_or_else(|| format_err!("could not convert path to string"))?;
let proxy =
fuchsia_fs::directory::open_in_namespace(path_str, fio::OpenFlags::RIGHT_READABLE)?;
let path = path.as_ref().to_path_buf();
Ok(Self { path, proxy, readdir_mutex: Mutex::new(()) })
}
// Create a Directory object from a proxy
pub fn from_proxy(proxy: fio::DirectoryProxy) -> Self {
let path = PathBuf::from(".");
Self { path, proxy, readdir_mutex: Mutex::new(()) }
}
// Open a directory at the given `relative_path` as readable.
pub fn open_dir_readable<P: AsRef<Path>>(&self, relative_path: P) -> Result<Self> {
self.open_dir(relative_path, fio::OpenFlags::RIGHT_READABLE)
}
// Open a directory at the given `relative_path` with the provided flags.
pub fn open_dir<P: AsRef<Path>>(
&self,
relative_path: P,
flags: fio::OpenFlags,
) -> Result<Self> {
let path = self.path.join(relative_path.as_ref());
let relative_path = match relative_path.as_ref().to_str() {
Some(relative_path) => relative_path,
None => return Err(format_err!("could not convert relative path to &str")),
};
match open_directory_no_describe(&self.proxy, relative_path, flags) {
Ok(proxy) => Ok(Self { path, proxy, readdir_mutex: Mutex::new(()) }),
Err(e) => Err(format_err!("could not open dir `{}`: {}", path.as_path().display(), e)),
}
}
// Read the contents of a file at the given `relative_path` as a string
pub async fn read_file<P: AsRef<Path>>(&self, relative_path: P) -> Result<String> {
let path = self.path.join(relative_path.as_ref());
let relative_path = match relative_path.as_ref().to_str() {
Some(relative_path) => relative_path,
None => return Err(format_err!("relative path is not valid unicode")),
};
let proxy =
match open_file_no_describe(&self.proxy, relative_path, fio::OpenFlags::RIGHT_READABLE)
{
Ok(proxy) => proxy,
Err(e) => {
return Err(format_err!(
"could not open file `{}`: {}",
path.as_path().display(),
e
))
}
};
match read_to_string(&proxy).await {
Ok(data) => Ok(data),
Err(e) => Err(format_err!(
"could not read file `{}` as string: {}",
path.as_path().display(),
e
)),
}
}
// Read the contents of a file at the given `relative_path` as bytes
pub async fn read_file_bytes<P: AsRef<Path>>(&self, relative_path: P) -> Result<Vec<u8>> {
let path = self.path.join(relative_path.as_ref());
let relative_path = match relative_path.as_ref().to_str() {
Some(relative_path) => relative_path,
None => return Err(format_err!("relative path is not valid unicode")),
};
let proxy =
match open_file_no_describe(&self.proxy, relative_path, fio::OpenFlags::RIGHT_READABLE)
{
Ok(proxy) => proxy,
Err(e) => {
return Err(format_err!(
"could not open file `{}`: {}",
path.as_path().display(),
e
))
}
};
match read(&proxy).await {
Ok(data) => Ok(data),
Err(e) => Err(format_err!("could not read file `{}`: {}", path.as_path().display(), e)),
}
}
// Checks if a file with the given `filename` exists in this directory.
// Function is not recursive. Does not check subdirectories.
pub async fn exists(&self, filename: &str) -> Result<bool> {
match self.entries().await {
Ok(entries) => Ok(entries.iter().any(|s| s == filename)),
Err(e) => Err(format_err!(
"could not check if `{}` exists in `{}`: {}",
filename,
self.path.as_path().display(),
e
)),
}
}
// Remove the given `filename` from the directory. Note that while the file will be removed from
// the directory, it will be destroyed only if there are no other references to it.
pub async fn remove(&self, filename: &str) -> Result<()> {
let options = fio::UnlinkOptions::EMPTY;
match self.proxy.unlink(filename, options).await {
Ok(r) => match r {
Ok(()) => Ok(()),
Err(e) => Err(format_err!(
"could not delete `{}` from `{}`: {}",
filename,
self.path.as_path().display(),
e
)),
},
Err(e) => Err(format_err!(
"proxy error while deleting `{}` from `{}`: {}",
filename,
self.path.as_path().display(),
e
)),
}
}
// Attempts to create a new file at the given |relative_path|.
// Replaces the file contents if it already exists.
pub async fn create_file<P: AsRef<Path>>(&self, relative_path: P, data: &[u8]) -> Result<()> {
let path = self.path.join(relative_path.as_ref());
let relative_path = match relative_path.as_ref().to_str() {
Some(relative_path) => relative_path,
None => return Err(format_err!("relative path is not valid unicode")),
};
let file = match open_file_no_describe(
&self.proxy,
relative_path,
fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::CREATE,
) {
Ok(proxy) => proxy,
Err(e) => {
return Err(format_err!(
"could not open file `{}`: {}",
path.as_path().display(),
e
))
}
};
let () = file
.resize(0)
.await
.map_err(|e| {
format_err!("could not truncate file `{}`: {}", path.as_path().display(), e)
})?
.map_err(Status::from_raw)
.map_err(|status| {
format_err!("could not truncate file `{}`: {}", path.as_path().display(), status)
})?;
match write(&file, data).await {
Ok(()) => {}
Err(e) => {
return Err(format_err!(
"could not write to file `{}`: {}",
path.as_path().display(),
e
))
}
}
match close(file).await {
Ok(()) => Ok(()),
Err(e) => {
Err(format_err!("could not close file `{}`: {}", path.as_path().display(), e))
}
}
}
// Return a list of directory entries in the directory
pub async fn entries(&self) -> Result<Vec<String>> {
let _lock = self.readdir_mutex.lock().await;
match readdir(&self.proxy).await {
Ok(entries) => Ok(entries.into_iter().map(|e| e.name).collect()),
Err(e) => Err(format_err!(
"could not get entries of `{}`: {}",
self.path.as_path().display(),
e
)),
}
}
pub fn clone(&self) -> Result<Self> {
let proxy = clone_no_describe(&self.proxy, Some(fio::OpenFlags::RIGHT_READABLE))?;
Ok(Self { path: self.path.clone(), proxy, readdir_mutex: Mutex::new(()) })
}
}