blob: 0100f64c10625056c777df6615d69475ff7bd67c [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.
//! Typesafe wrappers around an open package directory.
use crate::{MetaContents, MetaContentsError, MetaPackage, MetaPackageError};
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_io::{DirectoryMarker, DirectoryProxy, FileProxy};
use fuchsia_hash::{Hash, ParseHashError};
use thiserror::Error;
// re-export wrapped io_util errors.
pub use io_util::{
file::ReadError,
node::{CloneError, CloseError, OpenError},
};
/// An error encountered while reading/parsing the package's hash
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum ReadHashError {
#[error("while reading 'meta/'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/'")]
Parse(#[source] ParseHashError),
}
/// An error encountered while reading/parsing the package's meta/package file
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadMetaPackageError {
#[error("while reading 'meta/package'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/package'")]
Parse(#[source] MetaPackageError),
}
/// An error encountered while reading/parsing the package's meta/contents file
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadMetaContentsError {
#[error("while reading 'meta/contents'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/contents'")]
Parse(#[source] MetaContentsError),
}
/// An open package directory
#[derive(Debug, Clone)]
pub struct PackageDirectory {
proxy: DirectoryProxy,
}
impl PackageDirectory {
/// Interprets the provided directory proxy as a package dir.
pub fn from_proxy(proxy: DirectoryProxy) -> Self {
Self { proxy }
}
/// Creates a new channel pair, returning the client end as Self and the server end as a
/// channel.
pub fn create_request() -> Result<(Self, ServerEnd<DirectoryMarker>), fidl::Error> {
let (proxy, request) = fidl::endpoints::create_proxy::<DirectoryMarker>()?;
Ok((Self::from_proxy(proxy), request))
}
/// Returns the current component's package directory.
#[cfg(target_os = "fuchsia")]
pub fn open_from_namespace() -> Result<Self, OpenError> {
let dir =
io_util::directory::open_in_namespace("/pkg", fidl_fuchsia_io::OPEN_RIGHT_READABLE)?;
Ok(Self::from_proxy(dir))
}
/// Cleanly close the package directory, consuming self.
pub async fn close(self) -> Result<(), CloseError> {
io_util::directory::close(self.proxy).await
}
/// Send request to also serve this package directory on the given directory request.
pub fn reopen(&self, dir_request: ServerEnd<DirectoryMarker>) -> Result<(), CloneError> {
io_util::directory::clone_onto_no_describe(&self.proxy, None, dir_request)
}
/// Unwraps the inner DirectoryProxy, consuming self.
pub fn into_proxy(self) -> DirectoryProxy {
self.proxy
}
/// Open the file in the package given by `path` with the given access `rights`.
pub async fn open_file(&self, path: &str, rights: OpenRights) -> Result<FileProxy, OpenError> {
io_util::directory::open_file(&self.proxy, path, rights.to_flags()).await
}
async fn read_file_to_string(&self, path: &str) -> Result<String, ReadError> {
let f = self.open_file(path, OpenRights::Read).await?;
Ok(io_util::file::read_to_string(&f).await?)
}
/// Reads the merkle root of the package.
pub async fn merkle_root(&self) -> Result<Hash, ReadHashError> {
let merkle = self.read_file_to_string("meta").await.map_err(ReadHashError::Read)?;
Ok(merkle.parse().map_err(ReadHashError::Parse)?)
}
/// Reads and parses the package's meta/contents file.
pub async fn meta_contents(&self) -> Result<MetaContents, LoadMetaContentsError> {
let meta_contents =
self.read_file_to_string("meta/contents").await.map_err(LoadMetaContentsError::Read)?;
let meta_contents = MetaContents::deserialize(meta_contents.as_bytes())
.map_err(LoadMetaContentsError::Parse)?;
Ok(meta_contents)
}
/// Reads and parses the package's meta/package file.
pub async fn meta_package(&self) -> Result<MetaPackage, LoadMetaPackageError> {
let meta_package =
self.read_file_to_string("meta/package").await.map_err(LoadMetaPackageError::Read)?;
let meta_package = MetaPackage::deserialize(meta_package.as_bytes())
.map_err(LoadMetaPackageError::Parse)?;
Ok(meta_package)
}
/// Returns an iterator of blobs needed by this package, does not include meta.far blob itself.
/// Hashes may appear more than once.
pub async fn blobs(&self) -> Result<impl Iterator<Item = Hash>, LoadMetaContentsError> {
Ok(self.meta_contents().await?.into_hashes())
}
}
/// Possible open rights when opening a file within a package.
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum OpenRights {
Read,
ReadExecute,
}
impl OpenRights {
fn to_flags(&self) -> u32 {
match self {
OpenRights::Read => fidl_fuchsia_io::OPEN_RIGHT_READABLE,
OpenRights::ReadExecute => {
fidl_fuchsia_io::OPEN_RIGHT_READABLE | fidl_fuchsia_io::OPEN_RIGHT_EXECUTABLE
}
}
}
}
#[cfg(test)]
#[cfg(target_os = "fuchsia")]
mod tests {
use super::*;
use fidl::endpoints::Proxy;
use matches::assert_matches;
#[fuchsia_async::run_singlethreaded(test)]
async fn open_close() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let () = pkg.close().await.unwrap();
}
#[fuchsia_async::run_singlethreaded(test)]
async fn reopen_is_new_connection() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let (proxy, server_end) = fidl::endpoints::create_proxy().unwrap();
assert_matches!(pkg.reopen(server_end), Ok(()));
assert_matches!(PackageDirectory::from_proxy(proxy).close().await, Ok(()));
pkg.into_proxy().into_channel().expect("no other users of the wrapped channel");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn merkle_root_is_pkg_meta() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let merkle: Hash = std::fs::read_to_string("/pkg/meta").unwrap().parse().unwrap();
assert_eq!(pkg.merkle_root().await.unwrap(), merkle);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn list_blobs() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
// listing blobs succeeds, and this package has some blobs.
let blobs = pkg.blobs().await.unwrap().collect::<Vec<_>>();
assert!(!blobs.is_empty());
// the test duplicate blob appears twice
let duplicate_blob_merkle =
fuchsia_merkle::MerkleTree::from_reader("Hello World!".as_bytes()).unwrap().root();
assert_eq!(blobs.iter().filter(|hash| *hash == &duplicate_blob_merkle).count(), 2);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn package_name_is_test_package_name() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
assert_eq!(
pkg.meta_package().await.unwrap().into_path().to_string(),
"fuchsia-pkg-tests/0"
);
}
}