blob: e8634f66c8759aab69b24d0288705f6a19ec6845 [file] [log] [blame]
// Copyright 2023 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 super::api;
use super::api::BlobError;
use super::api::Package as _;
use super::api::PackageResolverUrl;
use super::blob::BlobDirectoryError;
use super::package::Error as PackageError;
use super::package::Package;
use super::product_bundle::ProductBundle;
use super::update_package::Error as UpdatePackageError;
use super::update_package::UpdatePackage;
use super::zbi::Error as ZbiError;
use super::zbi::Zbi;
use fuchsia_url::AbsolutePackageUrl;
use std::rc::Rc;
use thiserror::Error;
use update_package::parse_image_packages_json;
const IMAGES_JSON_PATH: &str = "images.json";
const IMAGES_JSON_ORIG_PATH: &str = "images.json.orig";
/// Errors that may occur when constructing a [`System`].
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to extract blob directory from product bundle: {0}")]
BlobDirectory(#[from] BlobDirectoryError),
#[error("failed to read images json blob from update package")]
ReadImagesJsonBlob(#[from] BlobError),
#[error("failed to read images json from update package")]
ReadImagesJson,
#[error("failed to parse images json from update package")]
ParseImagesJson,
#[error("failed to find a main slot in the images json")]
MissingMainSlot,
#[error("failed to find a recovery slot in the images json")]
MissingRecoverySlot,
#[error("failed to construct update package: {0}")]
UpdatePackage(#[from] UpdatePackageError),
#[error("failed to find images json as content blob in update package at path \"{images_json_path}\" or \"{images_json_orig_path}\"")]
MissingImagesJsonBlob {
images_json_path: Box<dyn api::Path>,
images_json_orig_path: Box<dyn api::Path>,
},
#[error("failed to find zbi as content blob in update package")]
MissingZbiBlob,
#[error("failed to load zbi because package is unpinned: {zbi_package_url}")]
ZbiPackageUrlUnpinned { zbi_package_url: AbsolutePackageUrl },
#[error("failed to load images package from blob set: {0}")]
ImagesFromBlob(#[from] PackageError),
#[error("failed to load zbi from from blob set: {0}")]
ZbiFromBlob(#[from] ZbiError),
}
#[derive(Clone)]
pub(crate) struct System(Rc<SystemData>);
impl System {
/// Constructs a [`System`] backed by `product_bundle`.
pub fn new(product_bundle: ProductBundle, variant: api::SystemVariant) -> Result<Self, Error> {
let build_dir = product_bundle.directory().clone();
let blob_set = product_bundle.blob_set()?;
let update_package = UpdatePackage::new(
Some(product_bundle.data_source().clone()),
product_bundle.update_package_hash().clone(),
blob_set.clone(),
)?;
// Read and parse the images json from the update package.
let images_json_path: Box<dyn api::Path> = Box::new(IMAGES_JSON_PATH);
let images_json_orig_path: Box<dyn api::Path> = Box::new(IMAGES_JSON_ORIG_PATH);
let (_, images_json_blob) = update_package
.content_blobs()
.find(|(path, _blob)| path == &images_json_path)
.or_else(|| {
update_package.content_blobs().find(|(path, _blob)| path == &images_json_orig_path)
})
.ok_or_else(|| Error::MissingImagesJsonBlob {
images_json_path,
images_json_orig_path,
})?;
let mut images_json_contents = vec![];
images_json_blob
.reader_seeker()?
.read_to_end(&mut images_json_contents)
.map_err(|_error| Error::ReadImagesJson)?;
let image_packages_manifest = parse_image_packages_json(&images_json_contents)
.map_err(|_error| Error::ParseImagesJson)?;
// Get the metadata for either the main or recovery slot.
let metadata = match variant {
api::SystemVariant::Recovery => {
image_packages_manifest.recovery().ok_or(Error::MissingMainSlot)?
}
api::SystemVariant::Main => {
image_packages_manifest.fuchsia().ok_or(Error::MissingRecoverySlot)?
}
};
// Read the appropriate images package.
let zbi_package_url = metadata.zbi().url().package_url().clone();
let zbi_hash = zbi_package_url.hash().clone().ok_or_else(|| {
Error::ZbiPackageUrlUnpinned { zbi_package_url: zbi_package_url.clone() }
})?;
let images_json_blob =
blob_set.blob(Box::new(zbi_hash)).map_err(|_error| Error::MissingZbiBlob)?;
let images_json_url = PackageResolverUrl::Package(zbi_package_url.into());
let images_json_package = Package::new(
Some(update_package.data_source()),
images_json_url,
images_json_blob,
blob_set,
)?;
// Read the zbi from the images package.
let zbi_resource = metadata.zbi().url().resource().to_string();
let zbi_path: Box<dyn api::Path> = Box::new(zbi_resource);
let (_, zbi_blob) =
images_json_package.content_blobs().find(|(path, _blob)| path == &zbi_path).unwrap();
let zbi = Zbi::new(Some(update_package.data_source()), zbi_path, zbi_blob)?;
Ok(Self(Rc::new(SystemData {
variant,
build_dir,
update_package: Box::new(update_package),
zbi,
})))
}
/// Returns borrow of [`super::zbi::Zbi`] *implementation* to client that has access to a
/// [`System`] *implementation*.
pub fn zbi(&self) -> &Zbi {
&self.0.zbi
}
}
impl api::System for System {
fn variant(&self) -> api::SystemVariant {
self.0.variant.clone()
}
fn build_dir(&self) -> Box<dyn api::Path> {
self.0.build_dir.clone()
}
fn zbi(&self) -> Box<dyn api::Zbi> {
Box::new(self.0.zbi.clone())
}
fn update_package(&self) -> Box<dyn api::UpdatePackage> {
self.0.update_package.clone()
}
fn kernel_flags(&self) -> Box<dyn api::KernelFlags> {
todo!()
}
fn vb_meta(&self) -> Box<dyn api::VbMeta> {
todo!()
}
}
struct SystemData {
variant: api::SystemVariant,
build_dir: Box<dyn api::Path>,
update_package: Box<dyn api::UpdatePackage>,
zbi: Zbi,
}
#[cfg(test)]
mod tests {
use super::super::product_bundle::test::*;
use super::super::update_package::test::FAKE_UPDATE_PACKAGE;
use super::api::System as _;
use super::*;
use dyn_clone::DynClone;
use fuchsia_merkle::Hash;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn path<P: AsRef<Path> + DynClone + 'static>(p: P) -> Box<dyn api::Path> {
Box::new(p)
}
fn write_blob<Dir: AsRef<Path>>(directory: Dir, contents: &[u8]) -> Hash {
let hash = fuchsia_merkle::from_slice(contents).root();
let filename = format!("{}", hash);
fs::write(directory.as_ref().join(filename), contents).expect("write blob to file");
hash
}
#[fuchsia::test]
fn system_from_product_bundle() {
let pb_dir = TempDir::new().expect("create tempdir for product bundle");
let pb_path = path(pb_dir.path().to_path_buf());
let blobs_path = pb_dir.path().join(V2_SDK_A_PRODUCT_BUNDLE_REPOSITORY_BLOBS_PATH);
fs::create_dir_all(&blobs_path).expect("create blobs directory");
let mut update_blob_hashes: Vec<Hash> = vec![];
let mut blobs_map: HashMap<Hash, Vec<u8>> = HashMap::new();
FAKE_UPDATE_PACKAGE.blobs.iter().for_each(|blob| {
let hash = write_blob(&blobs_path, blob.as_slice());
blobs_map.insert(hash.clone(), blob.clone());
update_blob_hashes.push(hash);
});
// Write product bundle manifest.
let update_package_hash = FAKE_UPDATE_PACKAGE.hash.clone();
v2_sdk_a_product_bundle(pb_dir.path(), Some(update_package_hash.into()))
.write(utf8_path(pb_dir.path()))
.expect("write product bundle manifest");
// Instantiate product bundle.
let product_bundle = ProductBundle::new(pb_path.clone()).expect("create product bundle");
// Instantiate system.
let system =
System::new(product_bundle, api::SystemVariant::Main).expect("create system instance");
assert_eq!(system.build_dir().to_string(), pb_path.to_string());
let update_package = system.update_package();
assert_eq!(update_package.hash().to_string(), update_package_hash.to_string());
}
}