Skip to main content

fuchsia_pkg/
package_directory.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Typesafe wrappers around an open package directory.
6
7use crate::{
8    MetaContents, MetaContentsError, MetaPackage, MetaPackageError, MetaSubpackages,
9    MetaSubpackagesError,
10};
11use fidl::endpoints::ServerEnd;
12use fidl_fuchsia_io as fio;
13use fuchsia_hash::{Hash, ParseHashError};
14use thiserror::Error;
15use version_history::AbiRevision;
16use zx_status;
17
18// re-export wrapped fuchsia_fs errors.
19pub use fuchsia_fs::file::ReadError;
20pub use fuchsia_fs::node::{CloneError, CloseError, OpenError};
21
22/// An error encountered while reading/parsing the package's hash
23#[derive(Debug, Error)]
24#[allow(missing_docs)]
25pub enum ReadHashError {
26    #[error("while reading 'meta/'")]
27    Read(#[source] ReadError),
28
29    #[error("while parsing 'meta/'")]
30    Parse(#[source] ParseHashError),
31}
32
33/// An error encountered while reading/parsing the package's meta/package file
34#[derive(Debug, Error)]
35#[allow(missing_docs)]
36pub enum LoadMetaPackageError {
37    #[error("while reading 'meta/package'")]
38    Read(#[source] ReadError),
39
40    #[error("while parsing 'meta/package'")]
41    Parse(#[source] MetaPackageError),
42}
43
44/// An error encountered while reading/parsing the package's
45/// meta/fuchsia.pkg/subpackages file
46#[derive(Debug, Error)]
47#[allow(missing_docs)]
48pub enum LoadMetaSubpackagesError {
49    #[error("while reading '{}'", MetaSubpackages::PATH)]
50    Read(#[source] ReadError),
51
52    #[error("while parsing '{}'", MetaSubpackages::PATH)]
53    Parse(#[source] MetaSubpackagesError),
54}
55
56/// An error encountered while reading/parsing the package's meta/contents file
57#[derive(Debug, Error)]
58#[allow(missing_docs)]
59pub enum LoadMetaContentsError {
60    #[error("while reading 'meta/contents'")]
61    Read(#[source] ReadError),
62
63    #[error("while parsing 'meta/contents'")]
64    Parse(#[source] MetaContentsError),
65}
66
67/// An error encountered while reading/parsing the package's `AbiRevision`.
68#[derive(Debug, Error)]
69#[allow(missing_docs)]
70pub enum LoadAbiRevisionError {
71    #[error("while opening '{}'", AbiRevision::PATH)]
72    Open(#[from] OpenError),
73
74    #[error("while reading '{}'", AbiRevision::PATH)]
75    Read(#[from] ReadError),
76
77    #[error("while parsing '{}'", AbiRevision::PATH)]
78    Parse(#[from] std::array::TryFromSliceError),
79}
80
81/// An open package directory
82#[derive(Debug, Clone)]
83pub struct PackageDirectory {
84    proxy: fio::DirectoryProxy,
85}
86
87impl PackageDirectory {
88    /// Interprets the provided directory proxy as a package dir.
89    pub fn from_proxy(proxy: fio::DirectoryProxy) -> Self {
90        Self { proxy }
91    }
92
93    /// Creates a new channel pair, returning the client end as Self and the
94    /// server end as a channel.
95    pub fn create_request() -> Result<(Self, ServerEnd<fio::DirectoryMarker>), fidl::Error> {
96        let (proxy, request) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
97        Ok((Self::from_proxy(proxy), request))
98    }
99
100    /// Returns the current component's package directory.
101    #[cfg(target_os = "fuchsia")]
102    pub fn open_from_namespace() -> Result<Self, OpenError> {
103        let dir = fuchsia_fs::directory::open_in_namespace("/pkg", fio::PERM_READABLE)?;
104        Ok(Self::from_proxy(dir))
105    }
106
107    /// Cleanly close the package directory, consuming self.
108    pub async fn close(self) -> Result<(), CloseError> {
109        fuchsia_fs::directory::close(self.proxy).await
110    }
111
112    /// Send request to also serve this package directory on the given directory request.
113    pub fn reopen(&self, dir_request: ServerEnd<fio::DirectoryMarker>) -> Result<(), CloneError> {
114        fuchsia_fs::directory::clone_onto(&self.proxy, dir_request)
115    }
116
117    /// Unwraps the inner DirectoryProxy, consuming self.
118    pub fn into_proxy(self) -> fio::DirectoryProxy {
119        self.proxy
120    }
121
122    /// Read the file in the package given by `path`, and return its contents as
123    /// a UTF-8 decoded string.
124    async fn read_file_to_string(&self, path: &str) -> Result<String, ReadError> {
125        fuchsia_fs::directory::read_file_to_string(&self.proxy, path).await
126    }
127
128    /// Read the file in the package given by `path`, and return its contents.
129    pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadError> {
130        fuchsia_fs::directory::read_file(&self.proxy, path).await
131    }
132
133    /// Reads the merkle root of the package.
134    pub async fn merkle_root(&self) -> Result<Hash, ReadHashError> {
135        let merkle = self.read_file_to_string("meta").await.map_err(ReadHashError::Read)?;
136        merkle.parse().map_err(ReadHashError::Parse)
137    }
138
139    /// Reads and parses the package's meta/contents file.
140    pub async fn meta_contents(&self) -> Result<MetaContents, LoadMetaContentsError> {
141        let meta_contents =
142            self.read_file("meta/contents").await.map_err(LoadMetaContentsError::Read)?;
143        let meta_contents = MetaContents::deserialize(meta_contents.as_slice())
144            .map_err(LoadMetaContentsError::Parse)?;
145        Ok(meta_contents)
146    }
147
148    /// Reads and parses the package's meta/package file.
149    pub async fn meta_package(&self) -> Result<MetaPackage, LoadMetaPackageError> {
150        let meta_package =
151            self.read_file("meta/package").await.map_err(LoadMetaPackageError::Read)?;
152        let meta_package = MetaPackage::deserialize(meta_package.as_slice())
153            .map_err(LoadMetaPackageError::Parse)?;
154        Ok(meta_package)
155    }
156
157    /// Reads and parses the package's meta/fuchsia.pkg/subpackages file. If the file
158    /// doesn't exist, an empty `MetaSubpackages` is returned.
159    pub async fn meta_subpackages(&self) -> Result<MetaSubpackages, LoadMetaSubpackagesError> {
160        match self.read_file(MetaSubpackages::PATH).await {
161            Ok(file) => Ok(MetaSubpackages::deserialize(file.as_slice())
162                .map_err(LoadMetaSubpackagesError::Parse)?),
163            Err(ReadError::Open(OpenError::OpenError(zx_status::Status::NOT_FOUND))) => {
164                Ok(MetaSubpackages::default())
165            }
166            Err(err) => Err(LoadMetaSubpackagesError::Read(err)),
167        }
168    }
169
170    /// Reads and parses the package's meta/fuchsia.abi/abi-revision file.
171    pub async fn abi_revision(&self) -> Result<AbiRevision, LoadAbiRevisionError> {
172        let abi_revision_bytes = self.read_file(AbiRevision::PATH).await?;
173        Ok(AbiRevision::try_from(abi_revision_bytes.as_slice())?)
174    }
175
176    /// Returns an iterator of blobs needed by this package, does not include meta.far blob itself.
177    /// Hashes may appear more than once.
178    pub async fn blobs(&self) -> Result<impl Iterator<Item = Hash>, LoadMetaContentsError> {
179        Ok(self.meta_contents().await?.into_hashes_undeduplicated())
180    }
181}
182
183#[cfg(test)]
184#[cfg(target_os = "fuchsia")]
185mod tests {
186    use super::*;
187    use assert_matches::assert_matches;
188    use fidl::endpoints::Proxy;
189
190    #[fuchsia::test]
191    async fn open_close() {
192        let pkg = PackageDirectory::open_from_namespace().unwrap();
193        let () = pkg.close().await.unwrap();
194    }
195
196    #[fuchsia::test]
197    async fn reopen_is_new_connection() {
198        let pkg = PackageDirectory::open_from_namespace().unwrap();
199
200        let (proxy, server_end) = fidl::endpoints::create_proxy();
201        assert_matches!(pkg.reopen(server_end), Ok(()));
202        assert_matches!(PackageDirectory::from_proxy(proxy).close().await, Ok(()));
203
204        pkg.into_proxy().into_channel().expect("no other users of the wrapped channel");
205    }
206
207    #[fuchsia::test]
208    async fn merkle_root_is_pkg_meta() {
209        let pkg = PackageDirectory::open_from_namespace().unwrap();
210
211        let merkle: Hash = std::fs::read_to_string("/pkg/meta").unwrap().parse().unwrap();
212
213        assert_eq!(pkg.merkle_root().await.unwrap(), merkle);
214    }
215
216    #[fuchsia::test]
217    async fn list_blobs() {
218        let pkg = PackageDirectory::open_from_namespace().unwrap();
219
220        // listing blobs succeeds, and this package has some blobs.
221        let blobs = pkg.blobs().await.unwrap().collect::<Vec<_>>();
222        assert!(!blobs.is_empty());
223
224        // the test duplicate blob appears twice
225        let duplicate_blob_merkle = fuchsia_merkle::root_from_slice("Hello World!");
226        assert_eq!(blobs.iter().filter(|hash| *hash == &duplicate_blob_merkle).count(), 2);
227    }
228
229    #[fuchsia::test]
230    async fn package_name_is_test_package_name() {
231        let pkg = PackageDirectory::open_from_namespace().unwrap();
232
233        assert_eq!(
234            pkg.meta_package().await.unwrap().into_path().to_string(),
235            "fuchsia-pkg-tests/0"
236        );
237    }
238
239    #[fuchsia::test]
240    async fn missing_subpackages_file_is_empty_subpackages() {
241        let pkg = PackageDirectory::open_from_namespace().unwrap();
242
243        assert_eq!(pkg.meta_subpackages().await.unwrap(), MetaSubpackages::default(),);
244    }
245
246    #[fuchsia::test]
247    async fn abi_revision_succeeds() {
248        let pkg = PackageDirectory::open_from_namespace().unwrap();
249
250        let _: AbiRevision = pkg.abi_revision().await.unwrap();
251    }
252}