[package-directory] Access blobs via trait NonMetaStorage instead of blobfs directly

Will make it easier to serve packages out of bootfs.

Fixed: 103237
Change-Id: I74b054fccdfd84e1517345531ac24cf98f24589c
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/693291
Fuchsia-Auto-Submit: Ben Keller <galbanum@google.com>
Reviewed-by: Kevin Wells <kevinwells@google.com>
Commit-Queue: Ben Keller <galbanum@google.com>
diff --git a/src/sys/pkg/lib/package-directory/src/lib.rs b/src/sys/pkg/lib/package-directory/src/lib.rs
index 3b13bf7..353c8da 100644
--- a/src/sys/pkg/lib/package-directory/src/lib.rs
+++ b/src/sys/pkg/lib/package-directory/src/lib.rs
@@ -58,17 +58,51 @@
     }
 }
 
+/// The storage that provides the non-meta files (accessed by hash) of a package-directory (e.g.
+/// blobfs).
+pub trait NonMetaStorage: Send + Sync + 'static {
+    /// Open a non-meta file by hash.
+    fn open(
+        &self,
+        blob: &fuchsia_hash::Hash,
+        flags: fio::OpenFlags,
+        mode: u32,
+        server_end: ServerEnd<fio::NodeMarker>,
+    ) -> Result<(), fuchsia_fs::node::OpenError>;
+}
+
+impl NonMetaStorage for blobfs::Client {
+    fn open(
+        &self,
+        blob: &fuchsia_hash::Hash,
+        flags: fio::OpenFlags,
+        mode: u32,
+        server_end: ServerEnd<fio::NodeMarker>,
+    ) -> Result<(), fuchsia_fs::node::OpenError> {
+        self.forward_open(blob, flags, mode, server_end)
+            .map_err(fuchsia_fs::node::OpenError::SendOpenRequest)
+    }
+}
+
 /// Serves a package directory for the package with hash `meta_far` on `server_end`.
 /// The connection rights are set by `flags`, used the same as the `flags` parameter of
 ///   fuchsia.io/Directory.Open.
 pub fn serve(
     scope: vfs::execution_scope::ExecutionScope,
-    blobfs: blobfs::Client,
+    non_meta_storage: impl NonMetaStorage,
     meta_far: fuchsia_hash::Hash,
     flags: fio::OpenFlags,
     server_end: ServerEnd<fio::DirectoryMarker>,
 ) -> impl futures::Future<Output = Result<(), Error>> {
-    serve_path(scope, blobfs, meta_far, flags, 0, VfsPath::dot(), server_end.into_channel().into())
+    serve_path(
+        scope,
+        non_meta_storage,
+        meta_far,
+        flags,
+        0,
+        VfsPath::dot(),
+        server_end.into_channel().into(),
+    )
 }
 
 /// Serves a sub-`path` of a package directory for the package with hash `meta_far` on `server_end`.
@@ -78,14 +112,14 @@
 ///   response with an error status if requested.
 pub async fn serve_path(
     scope: vfs::execution_scope::ExecutionScope,
-    blobfs: blobfs::Client,
+    non_meta_storage: impl NonMetaStorage,
     meta_far: fuchsia_hash::Hash,
     flags: fio::OpenFlags,
     mode: u32,
     path: VfsPath,
     server_end: ServerEnd<fio::NodeMarker>,
 ) -> Result<(), Error> {
-    let root_dir = match RootDir::new(blobfs, meta_far).await {
+    let root_dir = match RootDir::new(non_meta_storage, meta_far).await {
         Ok(d) => d,
         Err(e) => {
             let () = send_on_open_with_error(flags, server_end, e.to_zx_status());
diff --git a/src/sys/pkg/lib/package-directory/src/meta_as_dir.rs b/src/sys/pkg/lib/package-directory/src/meta_as_dir.rs
index 2779361..7f3e0b3 100644
--- a/src/sys/pkg/lib/package-directory/src/meta_as_dir.rs
+++ b/src/sys/pkg/lib/package-directory/src/meta_as_dir.rs
@@ -19,17 +19,17 @@
     },
 };
 
-pub(crate) struct MetaAsDir {
-    root_dir: Arc<RootDir>,
+pub(crate) struct MetaAsDir<S: crate::NonMetaStorage> {
+    root_dir: Arc<RootDir<S>>,
 }
 
-impl MetaAsDir {
-    pub(crate) fn new(root_dir: Arc<RootDir>) -> Self {
+impl<S: crate::NonMetaStorage> MetaAsDir<S> {
+    pub(crate) fn new(root_dir: Arc<RootDir<S>>) -> Self {
         MetaAsDir { root_dir }
     }
 }
 
-impl vfs::directory::entry::DirectoryEntry for MetaAsDir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for MetaAsDir<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -104,7 +104,7 @@
 }
 
 #[async_trait]
-impl vfs::directory::entry_container::Directory for MetaAsDir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaAsDir<S> {
     async fn read_dirents<'a>(
         &'a self,
         pos: &'a TraversalPosition,
@@ -171,7 +171,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, MetaAsDir) {
+        async fn new() -> (Self, MetaAsDir<blobfs::Client>) {
             let pkg = PackageBuilder::new("pkg")
                 .add_resource_at("meta/dir/file", &b"contents"[..])
                 .build()
diff --git a/src/sys/pkg/lib/package-directory/src/meta_as_file.rs b/src/sys/pkg/lib/package-directory/src/meta_as_file.rs
index 597e1dc..fa4469d 100644
--- a/src/sys/pkg/lib/package-directory/src/meta_as_file.rs
+++ b/src/sys/pkg/lib/package-directory/src/meta_as_file.rs
@@ -14,12 +14,12 @@
     },
 };
 
-pub(crate) struct MetaAsFile {
-    root_dir: Arc<RootDir>,
+pub(crate) struct MetaAsFile<S: crate::NonMetaStorage> {
+    root_dir: Arc<RootDir<S>>,
 }
 
-impl MetaAsFile {
-    pub(crate) fn new(root_dir: Arc<RootDir>) -> Self {
+impl<S: crate::NonMetaStorage> MetaAsFile<S> {
+    pub(crate) fn new(root_dir: Arc<RootDir<S>>) -> Self {
         MetaAsFile { root_dir }
     }
 
@@ -28,7 +28,7 @@
     }
 }
 
-impl vfs::directory::entry::DirectoryEntry for MetaAsFile {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for MetaAsFile<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -73,7 +73,7 @@
 }
 
 #[async_trait]
-impl vfs::file::File for MetaAsFile {
+impl<S: crate::NonMetaStorage> vfs::file::File for MetaAsFile<S> {
     async fn open(&self, _flags: fio::OpenFlags) -> Result<(), zx::Status> {
         Ok(())
     }
@@ -159,7 +159,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, MetaAsFile) {
+        async fn new() -> (Self, MetaAsFile<blobfs::Client>) {
             let pkg = PackageBuilder::new("pkg").build().await.unwrap();
             let (metafar_blob, _) = pkg.contents();
             let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
diff --git a/src/sys/pkg/lib/package-directory/src/meta_file.rs b/src/sys/pkg/lib/package-directory/src/meta_file.rs
index dfea693..1bb46f7 100644
--- a/src/sys/pkg/lib/package-directory/src/meta_file.rs
+++ b/src/sys/pkg/lib/package-directory/src/meta_file.rs
@@ -25,14 +25,14 @@
     pub(crate) length: u64,
 }
 
-pub(crate) struct MetaFile {
-    root_dir: Arc<RootDir>,
+pub(crate) struct MetaFile<S: crate::NonMetaStorage> {
+    root_dir: Arc<RootDir<S>>,
     location: MetaFileLocation,
     vmo: OnceCell<zx::Vmo>,
 }
 
-impl MetaFile {
-    pub(crate) fn new(root_dir: Arc<RootDir>, location: MetaFileLocation) -> Self {
+impl<S: crate::NonMetaStorage> MetaFile<S> {
+    pub(crate) fn new(root_dir: Arc<RootDir<S>>, location: MetaFileLocation) -> Self {
         MetaFile { root_dir, location, vmo: OnceCell::new() }
     }
 
@@ -61,7 +61,7 @@
     }
 }
 
-impl vfs::directory::entry::DirectoryEntry for MetaFile {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for MetaFile<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -106,7 +106,7 @@
 }
 
 #[async_trait]
-impl vfs::file::File for MetaFile {
+impl<S: crate::NonMetaStorage> vfs::file::File for MetaFile<S> {
     async fn open(&self, _flags: fio::OpenFlags) -> Result<(), zx::Status> {
         Ok(())
     }
@@ -201,7 +201,7 @@
             mode: fio::MODE_TYPE_FILE
                 | vfs::common::rights_to_posix_mode_bits(
                     true,  // read
-                    true, // write
+                    true,  // write
                     false, // execute
                 ),
             id: 1,
@@ -249,7 +249,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, MetaFile) {
+        async fn new() -> (Self, MetaFile<blobfs::Client>) {
             let pkg = PackageBuilder::new("pkg")
                 .add_resource_at("meta/file", &TEST_FILE_CONTENTS[..])
                 .build()
diff --git a/src/sys/pkg/lib/package-directory/src/meta_subdir.rs b/src/sys/pkg/lib/package-directory/src/meta_subdir.rs
index 0fc83f5..6b0ec63 100644
--- a/src/sys/pkg/lib/package-directory/src/meta_subdir.rs
+++ b/src/sys/pkg/lib/package-directory/src/meta_subdir.rs
@@ -19,20 +19,20 @@
     },
 };
 
-pub(crate) struct MetaSubdir {
-    root_dir: Arc<RootDir>,
+pub(crate) struct MetaSubdir<S: crate::NonMetaStorage> {
+    root_dir: Arc<RootDir<S>>,
     // The object relative path expression of the subdir relative to the package root with a
     // trailing slash appended.
     path: String,
 }
 
-impl MetaSubdir {
-    pub(crate) fn new(root_dir: Arc<RootDir>, path: String) -> Self {
+impl<S: crate::NonMetaStorage> MetaSubdir<S> {
+    pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Self {
         MetaSubdir { root_dir, path }
     }
 }
 
-impl vfs::directory::entry::DirectoryEntry for MetaSubdir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for MetaSubdir<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -105,7 +105,7 @@
 }
 
 #[async_trait]
-impl vfs::directory::entry_container::Directory for MetaSubdir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaSubdir<S> {
     async fn read_dirents<'a>(
         &'a self,
         pos: &'a TraversalPosition,
@@ -176,7 +176,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, MetaSubdir) {
+        async fn new() -> (Self, MetaSubdir<blobfs::Client>) {
             let pkg = PackageBuilder::new("pkg")
                 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
                 .build()
diff --git a/src/sys/pkg/lib/package-directory/src/non_meta_subdir.rs b/src/sys/pkg/lib/package-directory/src/non_meta_subdir.rs
index a8808b0..ac66bbc 100644
--- a/src/sys/pkg/lib/package-directory/src/non_meta_subdir.rs
+++ b/src/sys/pkg/lib/package-directory/src/non_meta_subdir.rs
@@ -21,20 +21,20 @@
         path::Path as VfsPath,
     },
 };
-pub(crate) struct NonMetaSubdir {
-    root_dir: Arc<RootDir>,
+pub(crate) struct NonMetaSubdir<S: crate::NonMetaStorage> {
+    root_dir: Arc<RootDir<S>>,
     // The object relative path expression of the subdir relative to the package root with a
     // trailing slash appended.
     path: String,
 }
 
-impl NonMetaSubdir {
-    pub(crate) fn new(root_dir: Arc<RootDir>, path: String) -> Self {
+impl<S: crate::NonMetaStorage> NonMetaSubdir<S> {
+    pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Self {
         NonMetaSubdir { root_dir, path }
     }
 }
 
-impl vfs::directory::entry::DirectoryEntry for NonMetaSubdir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for NonMetaSubdir<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -81,8 +81,8 @@
         if let Some(blob) = self.root_dir.non_meta_files.get(&file_path) {
             let () = self
                 .root_dir
-                .blobfs
-                .forward_open(blob, flags, mode, server_end)
+                .non_meta_storage
+                .open(blob, flags, mode, server_end)
                 .unwrap_or_else(|e| {
                     fx_log_err!("Error forwarding content blob open to blobfs: {:#}", anyhow!(e))
                 });
@@ -107,7 +107,7 @@
 }
 
 #[async_trait]
-impl vfs::directory::entry_container::Directory for NonMetaSubdir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for NonMetaSubdir<S> {
     async fn read_dirents<'a>(
         &'a self,
         pos: &'a TraversalPosition,
@@ -177,7 +177,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, NonMetaSubdir) {
+        async fn new() -> (Self, NonMetaSubdir<blobfs::Client>) {
             let pkg = PackageBuilder::new("pkg")
                 .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
                 .build()
diff --git a/src/sys/pkg/lib/package-directory/src/root_dir.rs b/src/sys/pkg/lib/package-directory/src/root_dir.rs
index e2e7073..1f10ad4 100644
--- a/src/sys/pkg/lib/package-directory/src/root_dir.rs
+++ b/src/sys/pkg/lib/package-directory/src/root_dir.rs
@@ -11,7 +11,7 @@
         non_meta_subdir::NonMetaSubdir,
         Error,
     },
-    anyhow::{anyhow, Context as _},
+    anyhow::Context as _,
     async_trait::async_trait,
     async_utils::async_once::Once,
     fidl::endpoints::ServerEnd,
@@ -35,8 +35,8 @@
 
 /// The root directory of Fuchsia package.
 #[derive(Debug)]
-pub struct RootDir {
-    pub(crate) blobfs: blobfs::Client,
+pub struct RootDir<S: crate::NonMetaStorage> {
+    pub(crate) non_meta_storage: S,
     pub(crate) hash: fuchsia_hash::Hash,
     pub(crate) meta_far: fio::FileProxy,
     // The keys are object relative path expressions.
@@ -46,11 +46,12 @@
     meta_far_vmo: Once<zx::Vmo>,
 }
 
-impl RootDir {
-    /// Loads the package metadata given by `hash` from `blobfs`, returning an object representing
-    /// the package, backed by `blobfs`.
-    pub async fn new(blobfs: blobfs::Client, hash: fuchsia_hash::Hash) -> Result<Self, Error> {
-        let meta_far = blobfs.open_blob_for_read_no_describe(&hash).map_err(Error::OpenMetaFar)?;
+impl<S: crate::NonMetaStorage> RootDir<S> {
+    /// Loads the package metadata given by `hash` from `non_meta_storage`, returning an object
+    /// representing the package, backed by `non_meta_storage`.
+    pub async fn new(non_meta_storage: S, hash: fuchsia_hash::Hash) -> Result<Self, Error> {
+        let meta_far =
+            open_for_read_no_describe(&non_meta_storage, &hash).map_err(Error::OpenMetaFar)?;
 
         let reader = fuchsia_fs::file::AsyncFile::from_proxy(Clone::clone(&meta_far));
         let mut async_reader = AsyncReader::new(fuchsia_fs::file::BufferedAsyncReadAt::new(reader))
@@ -85,18 +86,16 @@
 
         let meta_far_vmo = Default::default();
 
-        Ok(RootDir { blobfs, hash, meta_far, meta_files, non_meta_files, meta_far_vmo })
+        Ok(RootDir { non_meta_storage, hash, meta_far, meta_files, non_meta_files, meta_far_vmo })
     }
 
     /// Returns the contents, if present, of the file at object relative path expression `path`.
     /// https://fuchsia.dev/fuchsia-src/concepts/process/namespaces?hl=en#object_relative_path_expressions
     pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadFileError> {
         if let Some(hash) = self.non_meta_files.get(path) {
-            let blob = self
-                .blobfs
-                .open_blob_for_read_no_describe(hash)
-                .map_err(ReadFileError::BlobfsOpen)?;
-            return fuchsia_fs::file::read(&blob).await.map_err(ReadFileError::BlobfsRead);
+            let blob = open_for_read_no_describe(&self.non_meta_storage, hash)
+                .map_err(ReadFileError::Open)?;
+            return fuchsia_fs::file::read(&blob).await.map_err(ReadFileError::Read);
         }
 
         if let Some(location) = self.meta_files.get(path) {
@@ -148,10 +147,10 @@
 #[derive(thiserror::Error, Debug)]
 pub enum ReadFileError {
     #[error("opening blob")]
-    BlobfsOpen(#[source] fuchsia_fs::node::OpenError),
+    Open(#[source] fuchsia_fs::node::OpenError),
 
     #[error("reading blob")]
-    BlobfsRead(#[source] fuchsia_fs::file::ReadError),
+    Read(#[source] fuchsia_fs::file::ReadError),
 
     #[error("reading part of a blob")]
     PartialBlobRead(#[source] std::io::Error),
@@ -160,7 +159,7 @@
     NoFileAtPath { path: String },
 }
 
-impl vfs::directory::entry::DirectoryEntry for RootDir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for RootDir<S> {
     fn open(
         self: Arc<Self>,
         scope: ExecutionScope,
@@ -254,9 +253,13 @@
         }
 
         if let Some(blob) = self.non_meta_files.get(canonical_path) {
-            let () = self.blobfs.forward_open(blob, flags, mode, server_end).unwrap_or_else(|e| {
-                fx_log_err!("Error forwarding content blob open to blobfs: {:#}", anyhow!(e))
-            });
+            let () =
+                self.non_meta_storage.open(blob, flags, mode, server_end).unwrap_or_else(|e| {
+                    fx_log_err!(
+                        "Error forwarding content blob open to blobfs: {:#}",
+                        anyhow::anyhow!(e)
+                    )
+                });
             return;
         }
 
@@ -283,7 +286,7 @@
 }
 
 #[async_trait]
-impl vfs::directory::entry_container::Directory for RootDir {
+impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for RootDir<S> {
     async fn read_dirents<'a>(
         &'a self,
         pos: &'a TraversalPosition,
@@ -347,10 +350,27 @@
     open_as_file || !open_as_dir
 }
 
+// Open a non-meta file by hash with flags of OPEN_RIGHT_READABLE and return the proxy.
+fn open_for_read_no_describe(
+    non_meta_storage: &impl crate::NonMetaStorage,
+    blob: &fuchsia_hash::Hash,
+) -> Result<fio::FileProxy, fuchsia_fs::node::OpenError> {
+    let (file, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>()
+        .map_err(fuchsia_fs::node::OpenError::CreateProxy)?;
+    let () = non_meta_storage.open(
+        blob,
+        fio::OpenFlags::RIGHT_READABLE,
+        0,
+        server_end.into_channel().into(),
+    )?;
+    Ok(file)
+}
+
 #[cfg(test)]
 mod tests {
     use {
         super::*,
+        anyhow::anyhow,
         assert_matches::assert_matches,
         fidl::endpoints::{create_proxy, Proxy as _},
         fuchsia_pkg_testing::{blobfs::Fake as FakeBlobfs, PackageBuilder},
@@ -365,7 +385,7 @@
     }
 
     impl TestEnv {
-        async fn new() -> (Self, RootDir) {
+        async fn new() -> (Self, RootDir<blobfs::Client>) {
             let pkg = PackageBuilder::new("base-package-0")
                 .add_resource_at("resource", "blob-contents".as_bytes())
                 .add_resource_at("dir/file", "bloblob".as_bytes())
diff --git a/src/sys/pkg/lib/system-image/src/system_image.rs b/src/sys/pkg/lib/system-image/src/system_image.rs
index 76c625c..fd9b7289 100644
--- a/src/sys/pkg/lib/system-image/src/system_image.rs
+++ b/src/sys/pkg/lib/system-image/src/system_image.rs
@@ -24,7 +24,7 @@
 
 /// System image package.
 pub struct SystemImage {
-    root_dir: RootDir,
+    root_dir: RootDir<blobfs::Client>,
 }
 
 impl SystemImage {
@@ -39,7 +39,7 @@
     }
 
     /// Make a `SystemImage` from a `RootDir` for the `system_image` package.
-    pub fn from_root_dir(root_dir: RootDir) -> Self {
+    pub fn from_root_dir(root_dir: RootDir<blobfs::Client>) -> Self {
         Self { root_dir }
     }
 
@@ -100,7 +100,7 @@
     }
 
     /// Consume self and return the contained `package_directory::RootDir`.
-    pub fn into_root_dir(self) -> RootDir {
+    pub fn into_root_dir(self) -> RootDir<blobfs::Client> {
         self.root_dir
     }
 }