blob: fd9b7289911421f5de35874303c4559be8397d4c [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
get_system_image_hash, CachePackages, CachePackagesInitError, NonStaticAllowList,
StaticPackages, StaticPackagesInitError,
},
anyhow::Context as _,
fuchsia_hash::Hash,
package_directory::RootDir,
};
static DISABLE_RESTRICTIONS_FILE_PATH: &str = "data/pkgfs_disable_executability_restrictions";
static NON_STATIC_ALLOW_LIST_FILE_PATH: &str =
"data/pkgfs_packages_non_static_packages_allowlist.txt";
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ExecutabilityRestrictions {
Enforce,
DoNotEnforce,
}
/// System image package.
pub struct SystemImage {
root_dir: RootDir<blobfs::Client>,
}
impl SystemImage {
pub async fn new(
blobfs: blobfs::Client,
boot_args: &fidl_fuchsia_boot::ArgumentsProxy,
) -> Result<Self, anyhow::Error> {
let hash = get_system_image_hash(boot_args).await.context("getting system_image hash")?;
let root_dir =
RootDir::new(blobfs, hash).await.context("creating RootDir for system_image")?;
Ok(SystemImage { root_dir })
}
/// Make a `SystemImage` from a `RootDir` for the `system_image` package.
pub fn from_root_dir(root_dir: RootDir<blobfs::Client>) -> Self {
Self { root_dir }
}
pub fn load_executability_restrictions(&self) -> ExecutabilityRestrictions {
match self.root_dir.has_file(DISABLE_RESTRICTIONS_FILE_PATH) {
true => ExecutabilityRestrictions::DoNotEnforce,
false => ExecutabilityRestrictions::Enforce,
}
}
/// The hash of the `system_image` package.
pub fn hash(&self) -> &Hash {
&self.root_dir.hash()
}
/// Load `data/cache_packages.json`.
pub async fn cache_packages(&self) -> Result<CachePackages, CachePackagesInitError> {
self.root_dir
.read_file("data/cache_packages.json")
.await
.map_err(CachePackagesInitError::ReadCachePackagesJson)
.and_then(|content| CachePackages::from_json(content.as_slice()))
}
/// Load `data/static_packages`.
pub async fn static_packages(&self) -> Result<StaticPackages, StaticPackagesInitError> {
StaticPackages::deserialize(
self.root_dir
.read_file("data/static_packages")
.await
.map_err(StaticPackagesInitError::ReadStaticPackages)?
.as_slice(),
)
.map_err(StaticPackagesInitError::ProcessingStaticPackages)
}
/// Load the non-static allow list from
/// "data/pkgfs_packages_non_static_packages_allowlist.txt". Errors during loading result in an
/// empty allow list.
pub async fn non_static_allow_list(&self) -> NonStaticAllowList {
async {
NonStaticAllowList::parse(
self.root_dir
.read_file(NON_STATIC_ALLOW_LIST_FILE_PATH)
.await
.context("reading allow list contents")?
.as_slice(),
)
.context("parsing allow list contents")
}
.await
.unwrap_or_else(|e| {
fuchsia_syslog::fx_log_warn!(
"Failed to load non static allow list from system_image, treating as empty: {e:#}"
);
NonStaticAllowList::empty()
})
}
/// Consume self and return the contained `package_directory::RootDir`.
pub fn into_root_dir(self) -> RootDir<blobfs::Client> {
self.root_dir
}
}
#[cfg(test)]
mod tests {
use {super::*, assert_matches::assert_matches, fuchsia_pkg_testing::SystemImageBuilder};
struct TestEnv {
_blobfs: blobfs_ramdisk::BlobfsRamdisk,
}
impl TestEnv {
async fn new(system_image: SystemImageBuilder<'_>) -> (Self, SystemImage) {
let blobfs = blobfs_ramdisk::BlobfsRamdisk::start().unwrap();
let system_image = system_image.build().await;
system_image.write_to_blobfs_dir(&blobfs.root_dir().unwrap());
let root_dir =
RootDir::new(blobfs.client(), *system_image.meta_far_merkle_root()).await.unwrap();
(Self { _blobfs: blobfs }, SystemImage { root_dir })
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cache_packages_fails_without_config_files() {
let (_env, system_image) = TestEnv::new(SystemImageBuilder::new()).await;
assert_matches!(
system_image.cache_packages().await,
Err(CachePackagesInitError::ReadCachePackagesJson(
package_directory::ReadFileError::NoFileAtPath { .. }
))
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cache_packages_deserialize_valid_line_oriented() {
let (_env, system_image) = TestEnv::new(
SystemImageBuilder::new()
.cache_package("name/variant".parse().unwrap(), [0; 32].into()),
)
.await;
assert_eq!(
system_image.cache_packages().await.unwrap(),
CachePackages::from_entries(
vec!["fuchsia-pkg://fuchsia.com/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000"
.parse()
.unwrap()
]
)
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn static_packages_deserialize_valid_line_oriented() {
let (_env, system_image) = TestEnv::new(
SystemImageBuilder::new()
.static_package("name/variant".parse().unwrap(), [0; 32].into()),
)
.await;
assert_eq!(
system_image.static_packages().await.unwrap(),
StaticPackages::from_entries(vec![("name/variant".parse().unwrap(), [0; 32].into())])
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn non_static_allow_list_succeeds() {
let (_env, system_image) = TestEnv::new(
SystemImageBuilder::new().pkgfs_non_static_packages_allowlist(&["allow-me"]),
)
.await;
assert_eq!(
system_image.non_static_allow_list().await,
NonStaticAllowList::parse(b"allow-me\n").unwrap()
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn non_static_allow_list_missing_file_causes_empty_list() {
let (_env, system_image) = TestEnv::new(SystemImageBuilder::new()).await;
assert_eq!(system_image.non_static_allow_list().await, NonStaticAllowList::empty());
}
}