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