system_image/
system_image.rs1use 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
23pub 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 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 pub fn hash(&self) -> &Hash {
54 self.root_dir.hash()
55 }
56
57 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 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 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 pub fn into_root_dir(self) -> Arc<RootDir<blobfs::Client>> {
89 self.root_dir
90 }
91
92 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 async fn make_test_package(name: &str) -> Package {
206 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}