| // 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. |
| |
| //! Typesafe wrappers around the /pkgfs/system filesystem. |
| |
| use { |
| anyhow::anyhow, |
| fidl::endpoints::Proxy, |
| fidl_fuchsia_io as fio, |
| fuchsia_hash::{Hash, ParseHashError}, |
| std::fs, |
| thiserror::Error, |
| }; |
| |
| /// An error encountered while opening the system image. |
| #[derive(Debug, Error)] |
| #[allow(missing_docs)] |
| pub enum SystemImageFileOpenError { |
| #[error("the file does not exist in the system image package")] |
| NotFound, |
| |
| #[error("while opening the file: {0}")] |
| Io(io_util::node::OpenError), |
| |
| #[error("unexpected error: {0}")] |
| Other(anyhow::Error), |
| } |
| |
| /// An error encountered while reading the system image hash. |
| #[derive(Debug, Error)] |
| #[allow(missing_docs)] |
| pub enum SystemImageFileHashError { |
| #[error("failed to open \"meta\" file: {0}")] |
| Open(#[from] SystemImageFileOpenError), |
| |
| #[error("while reading the file: {0}")] |
| Read(#[from] io_util::file::ReadError), |
| |
| #[error("while parsing the hash: {0}")] |
| Parse(#[from] ParseHashError), |
| } |
| |
| /// An open handle to /pkgfs/system. |
| #[derive(Debug, Clone)] |
| pub struct Client { |
| proxy: fio::DirectoryProxy, |
| } |
| |
| impl Client { |
| /// Returns a `Client` connected to pkgfs from the current component's namespace. |
| pub fn open_from_namespace() -> Result<Self, io_util::node::OpenError> { |
| Ok(Self { |
| proxy: io_util::directory::open_in_namespace( |
| "/pkgfs/system", |
| fio::OpenFlags::RIGHT_READABLE, |
| )?, |
| }) |
| } |
| |
| /// Returns a `Client` connected to pkgfs from the given pkgfs root dir. |
| pub fn open_from_pkgfs_root( |
| pkgfs: &fio::DirectoryProxy, |
| ) -> Result<Self, io_util::node::OpenError> { |
| Ok(Self { |
| proxy: io_util::directory::open_directory_no_describe( |
| pkgfs, |
| "system", |
| fio::OpenFlags::RIGHT_READABLE, |
| )?, |
| }) |
| } |
| |
| /// Open a file from the system_image package wrapped by this `Client`. |
| pub async fn open_file(&self, path: &str) -> Result<fs::File, SystemImageFileOpenError> { |
| let file_proxy = self.open_file_as_proxy(path).await?; |
| fdio::create_fd( |
| file_proxy |
| .into_channel() |
| .map_err(|_| { |
| SystemImageFileOpenError::Other(anyhow!("into_channel() on FileProxy failed")) |
| })? |
| .into_zx_channel() |
| .into(), |
| ) |
| .map_err(|e| SystemImageFileOpenError::Other(e.into())) |
| } |
| |
| /// Returns the system image hash. |
| pub async fn hash(&self) -> Result<Hash, SystemImageFileHashError> { |
| let file_proxy = self.open_file_as_proxy("meta").await?; |
| let hashstr = io_util::file::read_to_string(&file_proxy).await?; |
| let hash: Hash = hashstr.parse()?; |
| |
| Ok(hash) |
| } |
| |
| async fn open_file_as_proxy( |
| &self, |
| path: &str, |
| ) -> Result<fio::FileProxy, SystemImageFileOpenError> { |
| io_util::directory::open_file(&self.proxy, path, fio::OpenFlags::RIGHT_READABLE) |
| .await |
| .map_err(|e| match e { |
| io_util::node::OpenError::OpenError(fuchsia_zircon::Status::NOT_FOUND) => { |
| SystemImageFileOpenError::NotFound |
| } |
| other => SystemImageFileOpenError::Io(other), |
| }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, assert_matches::assert_matches, blobfs_ramdisk::BlobfsRamdisk, |
| fuchsia_pkg_testing::SystemImageBuilder, pkgfs_ramdisk::PkgfsRamdisk, std::io::Read as _, |
| }; |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn from_pkgfs_root_open_file_succeeds() { |
| let system_image_package = SystemImageBuilder::new().build().await; |
| let blobfs = BlobfsRamdisk::start().unwrap(); |
| system_image_package.write_to_blobfs_dir(&blobfs.root_dir().unwrap()); |
| let pkgfs = PkgfsRamdisk::builder() |
| .blobfs(blobfs) |
| .system_image_merkle(system_image_package.meta_far_merkle_root()) |
| .start() |
| .unwrap(); |
| let client = Client::open_from_pkgfs_root(&pkgfs.root_dir_proxy().unwrap()).unwrap(); |
| |
| let mut contents = String::new(); |
| client.open_file("meta").await.unwrap().read_to_string(&mut contents).unwrap(); |
| |
| assert_eq!(contents, "8bc6622d99b5fee3cf42c54d7f17b8b38c31b69da4573db98a647b692b760311"); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn from_pkgfs_root_open_file_fails_on_missing_file() { |
| let system_image_package = SystemImageBuilder::new().build().await; |
| let blobfs = BlobfsRamdisk::start().unwrap(); |
| system_image_package.write_to_blobfs_dir(&blobfs.root_dir().unwrap()); |
| let pkgfs = PkgfsRamdisk::builder() |
| .blobfs(blobfs) |
| .system_image_merkle(system_image_package.meta_far_merkle_root()) |
| .start() |
| .unwrap(); |
| let client = Client::open_from_pkgfs_root(&pkgfs.root_dir_proxy().unwrap()).unwrap(); |
| |
| assert_matches!( |
| client.open_file("missing-file").await, |
| Err(SystemImageFileOpenError::NotFound) |
| ); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn from_pkgfs_root_hash_succeeds() { |
| let system_image_package = SystemImageBuilder::new().build().await; |
| let blobfs = BlobfsRamdisk::start().unwrap(); |
| system_image_package.write_to_blobfs_dir(&blobfs.root_dir().unwrap()); |
| let pkgfs = PkgfsRamdisk::builder() |
| .blobfs(blobfs) |
| .system_image_merkle(system_image_package.meta_far_merkle_root()) |
| .start() |
| .unwrap(); |
| let client = Client::open_from_pkgfs_root(&pkgfs.root_dir_proxy().unwrap()).unwrap(); |
| |
| assert_eq!( |
| client.hash().await.unwrap(), |
| "8bc6622d99b5fee3cf42c54d7f17b8b38c31b69da4573db98a647b692b760311" |
| .parse::<Hash>() |
| .unwrap() |
| ); |
| } |
| } |