system_image/
system_image.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
5use crate::errors::AnchoredPackagesError;
6use crate::{
7    AnchoredPackages, CachePackages, CachePackagesInitError, StaticPackages,
8    StaticPackagesInitError, get_system_image_hash,
9};
10use anyhow::Context as _;
11use fuchsia_hash::Hash;
12use package_directory::RootDir;
13use std::sync::Arc;
14
15static DISABLE_RESTRICTIONS_FILE_PATH: &str = "data/pkgfs_disable_executability_restrictions";
16
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18pub enum ExecutabilityRestrictions {
19    Enforce,
20    DoNotEnforce,
21}
22
23/// System image package.
24pub struct SystemImage {
25    root_dir: Arc<RootDir<blobfs::Client>>,
26}
27
28impl SystemImage {
29    pub async fn new(
30        blobfs: blobfs::Client,
31        boot_args: &fidl_fuchsia_boot::ArgumentsProxy,
32    ) -> Result<Self, anyhow::Error> {
33        let hash = get_system_image_hash(boot_args).await.context("getting system_image hash")?;
34        let root_dir = RootDir::new(blobfs, hash)
35            .await
36            .with_context(|| format!("creating RootDir for system_image: {hash}"))?;
37        Ok(SystemImage { root_dir })
38    }
39
40    /// Make a `SystemImage` from a `RootDir` for the `system_image` package.
41    pub fn from_root_dir(root_dir: Arc<RootDir<blobfs::Client>>) -> Self {
42        Self { root_dir }
43    }
44
45    pub fn load_executability_restrictions(&self) -> ExecutabilityRestrictions {
46        match self.root_dir.has_file(DISABLE_RESTRICTIONS_FILE_PATH) {
47            true => ExecutabilityRestrictions::DoNotEnforce,
48            false => ExecutabilityRestrictions::Enforce,
49        }
50    }
51
52    /// The hash of the `system_image` package.
53    pub fn hash(&self) -> &Hash {
54        self.root_dir.hash()
55    }
56
57    /// Load `data/cache_packages.json`.
58    pub async fn cache_packages(&self) -> Result<CachePackages, CachePackagesInitError> {
59        self.root_dir
60            .read_file("data/cache_packages.json")
61            .await
62            .map_err(CachePackagesInitError::ReadCachePackagesJson)
63            .and_then(|content| CachePackages::from_json(content.as_slice()))
64    }
65
66    /// Load `data/static_packages`.
67    pub async fn static_packages(&self) -> Result<StaticPackages, StaticPackagesInitError> {
68        StaticPackages::deserialize(
69            self.root_dir
70                .read_file("data/static_packages")
71                .await
72                .map_err(StaticPackagesInitError::ReadStaticPackages)?
73                .as_slice(),
74        )
75        .map_err(StaticPackagesInitError::ProcessingStaticPackages)
76    }
77
78    /// Load `data/anchored_packages.json`.
79    pub async fn anchored_packages(&self) -> Result<AnchoredPackages, AnchoredPackagesError> {
80        self.root_dir
81            .read_file("data/anchored_packages.json")
82            .await
83            .map_err(AnchoredPackagesError::ReadAnchoredPackagesJson)
84            .and_then(|content| AnchoredPackages::from_json(content.as_slice()))
85    }
86
87    /// Consume self and return the contained `package_directory::RootDir`.
88    pub fn into_root_dir(self) -> Arc<RootDir<blobfs::Client>> {
89        self.root_dir
90    }
91
92    /// The package path of the system image package.
93    pub fn package_path() -> fuchsia_pkg::PackagePath {
94        fuchsia_pkg::PackagePath::from_name_and_variant(
95            "system_image".parse().expect("valid package name"),
96            fuchsia_pkg::PackageVariant::zero(),
97        )
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use assert_matches::assert_matches;
105    use blobfs_ramdisk::BlobfsRamdisk;
106    use fuchsia_hash::GenericDigest;
107    use fuchsia_pkg::PackagePath;
108    use fuchsia_pkg::package_sets::AnchoredPackageSetType;
109    use fuchsia_pkg_testing::{Package, PackageBuilder, SystemImageBuilder};
110    use package_directory::NonMetaStorage;
111    use std::collections::HashSet;
112    use std::str::FromStr;
113
114    struct TestEnv {
115        _blobfs: blobfs_ramdisk::BlobfsRamdisk,
116    }
117
118    impl TestEnv {
119        async fn new(system_image: SystemImageBuilder) -> (Self, SystemImage) {
120            let blobfs = blobfs_ramdisk::BlobfsRamdisk::start().await.unwrap();
121            let system_image = system_image.build().await;
122            system_image.write_to_blobfs(&blobfs).await;
123            let root_dir = RootDir::new(blobfs.client(), *system_image.hash()).await.unwrap();
124            (Self { _blobfs: blobfs }, SystemImage { root_dir })
125        }
126    }
127
128    #[fuchsia_async::run_singlethreaded(test)]
129    async fn cache_packages_fails_without_config_files() {
130        let (_env, system_image) = TestEnv::new(SystemImageBuilder::new()).await;
131        assert_matches!(
132            system_image.cache_packages().await,
133            Err(CachePackagesInitError::ReadCachePackagesJson(
134                package_directory::ReadFileError::NoFileAtPath { .. }
135            ))
136        );
137    }
138
139    #[fuchsia_async::run_singlethreaded(test)]
140    async fn cache_packages_deserialize_valid_line_oriented() {
141        let (_env, system_image) = TestEnv::new(
142            SystemImageBuilder::new()
143                .cache_package("name/variant".parse().unwrap(), [0; 32].into()),
144        )
145        .await;
146
147        assert_eq!(
148            system_image.cache_packages().await.unwrap(),
149            CachePackages::from_entries(
150                vec!["fuchsia-pkg://fuchsia.com/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000"
151                    .parse()
152                    .unwrap()
153                ]
154            )
155        );
156    }
157
158    #[fuchsia_async::run_singlethreaded(test)]
159    async fn static_packages_deserialize_valid_line_oriented() {
160        let (_env, system_image) = TestEnv::new(
161            SystemImageBuilder::new()
162                .static_package("name/variant".parse().unwrap(), [0; 32].into()),
163        )
164        .await;
165
166        assert_eq!(
167            system_image.static_packages().await.unwrap(),
168            StaticPackages::from_entries(vec![("name/variant".parse().unwrap(), [0; 32].into())])
169        );
170    }
171
172    #[fuchsia_async::run_singlethreaded(test)]
173    async fn anchored_packages_fails_without_config_files() {
174        let (_env, system_image) = TestEnv::new(SystemImageBuilder::new()).await;
175        assert_matches!(
176            system_image.anchored_packages().await,
177            Err(AnchoredPackagesError::ReadAnchoredPackagesJson(
178                package_directory::ReadFileError::NoFileAtPath { .. }
179            ))
180        );
181    }
182
183    #[fuchsia_async::run_singlethreaded(test)]
184    async fn anchored_packages_deserialize_valid() {
185        let (_env, system_image) = TestEnv::new(SystemImageBuilder::new().anchored_package(
186            AnchoredPackageSetType::OnDemand,
187            "name/variant".parse().unwrap(),
188            [0; 32].into(),
189        ))
190        .await;
191        let json = r#" {
192            "anchored_on_demand": {
193                "fuchsia-pkg://fuchsia.com/name/variant": {
194                    "hash": "0000000000000000000000000000000000000000000000000000000000000000"
195                }
196            }
197        }"#;
198        assert_eq!(
199            system_image.anchored_packages().await.unwrap(),
200            AnchoredPackages::from_json(json.as_bytes()).unwrap()
201        );
202    }
203
204    /// Constructs a test package to be used in the system image builder tests below.
205    async fn make_test_package(name: &str) -> Package {
206        // This will generate deterministic package hashes and blobs which the test functions
207        // are using to verify that write/read roundtrip of the constructed system image works.
208        let builder = PackageBuilder::new(name).add_resource_at("pkgname", name.as_bytes());
209        builder.build().await.expect("build package")
210    }
211
212    #[fuchsia::test]
213    async fn static_package_in_system_image_read_write_roundtrip() {
214        let static_test_package = make_test_package("test-package").await;
215        let system_image_package =
216            SystemImageBuilder::new().static_packages(&[&static_test_package]).build().await;
217        let blobfs = BlobfsRamdisk::builder().start().await.expect("started blobfs");
218        static_test_package.write_to_blobfs(&blobfs).await;
219        system_image_package.write_to_blobfs(&blobfs).await;
220
221        let client = blobfs.client();
222
223        let blobs_got = client.list_known_blobs().await.unwrap();
224
225        let mut blobs_expected = HashSet::new();
226        for digest in [
227            "055dc718192ef18007ac4268e223224232f29bd4840eaac713da3e930bdc9c47",
228            "2462c914da92ac7c87de422cf072049fb58442434732dfd5f39ee6de6011d2c3",
229            "81c6073abd7938367b78cf50eff3cfefeb733902951efc89312cae03d8ac16a7",
230            "b6f3dc74baf53aa20a439925117811fbd54ba2641ffbd9e10838ac8bb5dba1d8",
231        ] {
232            blobs_expected.insert(GenericDigest::from_str(digest).unwrap());
233        }
234
235        assert_eq!(blobs_got, blobs_expected);
236        assert_eq!(
237            "test-package",
238            String::from_utf8(
239                client
240                    .read_blob(
241                        &GenericDigest::from_str(
242                            "b6f3dc74baf53aa20a439925117811fbd54ba2641ffbd9e10838ac8bb5dba1d8"
243                        )
244                        .unwrap()
245                    )
246                    .await
247                    .expect("reading blob")
248            )
249            .unwrap()
250        );
251    }
252
253    #[fuchsia::test]
254    async fn static_and_anchored_package_in_system_image_read_write_roundtrip() {
255        let static_test_package = make_test_package("test-package").await;
256        let anchored_automatic_test_package = make_test_package("test-package-anchored").await;
257        let system_image_package = SystemImageBuilder::new()
258            .static_packages(&[&static_test_package])
259            .anchored_package(
260                AnchoredPackageSetType::Automatic,
261                PackagePath::from_name_and_variant(
262                    anchored_automatic_test_package.name().clone(),
263                    "0".parse().unwrap(),
264                ),
265                *anchored_automatic_test_package.hash(),
266            )
267            .build()
268            .await;
269        let blobfs = BlobfsRamdisk::builder().start().await.expect("started blobfs");
270        static_test_package.write_to_blobfs(&blobfs).await;
271        anchored_automatic_test_package.write_to_blobfs(&blobfs).await;
272        system_image_package.write_to_blobfs(&blobfs).await;
273
274        let client = blobfs.client();
275
276        let blobs_got = client.list_known_blobs().await.unwrap();
277        let mut blobs_expected = HashSet::new();
278        for digest in [
279            "055dc718192ef18007ac4268e223224232f29bd4840eaac713da3e930bdc9c47",
280            "211ac169e3f0422e09d9b51a0fe2c89122478a4eabc1300f51383842e9ecc54d",
281            "797f25d8a6d0163a562d1e4a7850f128cea69bf0d68db1a3e472b3f79d5c125b",
282            "81c6073abd7938367b78cf50eff3cfefeb733902951efc89312cae03d8ac16a7",
283            "914cca7ed08e5675692fecc0d1a74a7f5536996b562fe3f09d41167425cbe50a",
284            "a1c9428b24ad12f7a4cd20b8cdda877fbc65b93339462a7c7cda2b18dea334d9",
285            "b6f3dc74baf53aa20a439925117811fbd54ba2641ffbd9e10838ac8bb5dba1d8",
286        ] {
287            blobs_expected.insert(GenericDigest::from_str(digest).unwrap());
288        }
289
290        assert_eq!(blobs_got, blobs_expected);
291        assert_eq!(
292            "test-package",
293            String::from_utf8(
294                client
295                    .read_blob(
296                        &GenericDigest::from_str(
297                            "b6f3dc74baf53aa20a439925117811fbd54ba2641ffbd9e10838ac8bb5dba1d8"
298                        )
299                        .unwrap()
300                    )
301                    .await
302                    .expect("reading blob")
303            )
304            .unwrap()
305        );
306        assert_eq!(
307            "test-package-anchored",
308            String::from_utf8(
309                client
310                    .read_blob(
311                        &GenericDigest::from_str(
312                            "a1c9428b24ad12f7a4cd20b8cdda877fbc65b93339462a7c7cda2b18dea334d9"
313                        )
314                        .unwrap()
315                    )
316                    .await
317                    .expect("reading blob")
318            )
319            .unwrap()
320        );
321    }
322}