blob: cbc5c68218a5c94df8c0fdade6b0d90ff39ec758 [file] [log] [blame]
// Copyright 2020 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 {
anyhow::{anyhow, Context as _, Error},
fidl::endpoints::ClientEnd,
fidl_fuchsia_io::DirectoryMarker,
fuchsia_inspect as finspect,
fuchsia_inspect_contrib::inspectable::InspectableLen,
fuchsia_merkle::Hash,
fuchsia_pkg::MetaContents,
fuchsia_syslog::fx_log_err,
futures::{stream::FuturesUnordered, TryStreamExt},
pkgfs::{system::Client as SystemImage, versions::Client as Versions},
std::{collections::HashSet, io::Read as _},
system_image::StaticPackages,
};
#[derive(Debug)]
pub(crate) struct BlobLocation {
inner: Inner,
node: finspect::Node,
}
#[derive(Debug)]
enum Inner {
AssumeAllInBase(finspect::Node),
BaseBlobs(BaseBlobs),
}
#[derive(Debug)]
struct BaseBlobs {
blobs: InspectableLen<HashSet<Hash>>,
node: finspect::Node,
}
impl BaseBlobs {
fn new(blobs: HashSet<Hash>, node: finspect::Node) -> Self {
Self { blobs: InspectableLen::new(blobs, &node, "count"), node }
}
}
impl BlobLocation {
pub async fn new(
system_image: impl FnOnce() -> Result<SystemImage, Error>,
versions: impl FnOnce() -> Result<Versions, Error>,
node: finspect::Node,
) -> Self {
match Self::load_base_blobs(system_image, versions).await {
Ok(base_blobs) => Self {
inner: Inner::BaseBlobs(BaseBlobs::new(
base_blobs,
node.create_child("base-blobs"),
)),
node,
},
Err(e) => {
fx_log_err!(
"Error determining base blobs, assuming all blobs are in base: {:#}",
anyhow!(e)
);
Self {
inner: Inner::AssumeAllInBase(node.create_child("assume-all-in-base")),
node,
}
}
}
}
async fn load_base_blobs(
system_image: impl FnOnce() -> Result<SystemImage, Error>,
versions: impl FnOnce() -> Result<Versions, Error>,
) -> Result<HashSet<Hash>, Error> {
let system_image = system_image()?;
let system_image_meta_file =
system_image.open_file("meta").await.context("error opening system_image meta")?;
let mut system_image_hash = String::new();
(&system_image_meta_file)
.read_to_string(&mut system_image_hash)
.context("error reading system_image hash")?;
let system_image_hash = system_image_hash.parse().context("system_image hash invalid")?;
let static_packages = StaticPackages::deserialize(
system_image
.open_file("data/static_packages")
.await
.context("open system_image data/static_packages")?,
)?;
let versions = versions()?;
let mut futures = std::iter::once(&system_image_hash)
.chain(static_packages.hashes())
.map(|p| Self::package_blobs(&versions, p))
.collect::<FuturesUnordered<_>>();
let mut ret = HashSet::new();
while let Some(p) = futures.try_next().await? {
ret.extend(p);
}
Ok(ret)
}
// Return all blobs that make up `package`, including the meta.far.
async fn package_blobs(
versions: &Versions,
package: &Hash,
) -> Result<impl Iterator<Item = Hash>, Error> {
let meta_contents = MetaContents::deserialize(
client_end_to_openat(versions.open_package(package).await?.into_client_end())?
.open_file("meta/contents")
.with_context(|| format!("error opening meta/contents of {}", package))?,
)
.context(format!("error deserializing meta/contents of {}", package))?;
Ok(std::iter::once(package.clone())
.chain(meta_contents.into_contents().into_iter().map(|(_, hash)| hash)))
}
// TODO(fxbug.dev/43635) use this function, remove allow
#[allow(dead_code)]
pub fn is_blob_in_base(&self, hash: &Hash) -> bool {
match &self.inner {
Inner::AssumeAllInBase(..) => true,
Inner::BaseBlobs(BaseBlobs { blobs, .. }) => blobs.contains(hash),
}
}
}
fn client_end_to_openat(client: ClientEnd<DirectoryMarker>) -> Result<openat::Dir, Error> {
fdio::create_fd(client.into()).context("failed to create fd")
}
#[cfg(test)]
mod tests {
use {
super::*,
fuchsia_inspect::assert_inspect_tree,
fuchsia_pkg::PackagePath,
maplit::{btreemap, hashmap},
std::{
collections::HashMap,
fs::{create_dir, create_dir_all, File},
io::Write as _,
},
tempfile::TempDir,
};
struct TestPkgfs {
pkgfs_root: TempDir,
}
impl TestPkgfs {
fn new(
system_image_hash: &Hash,
static_packages: &StaticPackages,
versions_contents: &HashMap<Hash, MetaContents>,
) -> Self {
let pkgfs_root = TempDir::new().unwrap();
create_dir(pkgfs_root.path().join("system")).unwrap();
File::create(pkgfs_root.path().join("system/meta"))
.unwrap()
.write_all(system_image_hash.to_string().as_bytes())
.unwrap();
create_dir(pkgfs_root.path().join("system/data")).unwrap();
static_packages
.serialize(
File::create(pkgfs_root.path().join("system/data/static_packages")).unwrap(),
)
.unwrap();
create_dir(pkgfs_root.path().join("versions")).unwrap();
for (hash, contents) in versions_contents.iter() {
let meta_path = pkgfs_root.path().join(format!("versions/{}/meta", hash));
create_dir_all(&meta_path).unwrap();
contents.serialize(&mut File::create(meta_path.join("contents")).unwrap()).unwrap();
}
Self { pkgfs_root }
}
fn system_image(&self) -> SystemImage {
SystemImage::open_from_pkgfs_root(&fidl_fuchsia_io::DirectoryProxy::new(
fuchsia_async::Channel::from_channel(
fdio::transfer_fd(File::open(self.pkgfs_root.path()).unwrap()).unwrap().into(),
)
.unwrap(),
))
.unwrap()
}
fn versions(&self) -> Versions {
Versions::open_from_pkgfs_root(&fidl_fuchsia_io::DirectoryProxy::new(
fuchsia_async::Channel::from_channel(
fdio::transfer_fd(File::open(self.pkgfs_root.path()).unwrap()).unwrap().into(),
)
.unwrap(),
))
.unwrap()
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn inspect_correct_blob_count() {
let system_image_hash: Hash =
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap();
let fake_package_hash: Hash =
"1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap();
let static_packages = StaticPackages::from_entries(vec![(
PackagePath::from_name_and_variant("fake-package", "0").unwrap(),
fake_package_hash,
)]);
let versions_contents = hashmap! {
system_image_hash.clone() => MetaContents::from_map(
btreemap! {
"some-blob".to_string() =>
"2222222222222222222222222222222222222222222222222222222222222222".parse().unwrap()
}
).unwrap(),
fake_package_hash.clone() => MetaContents::from_map(
btreemap! {
"other-blob".to_string() =>
"3333333333333333333333333333333333333333333333333333333333333333".parse().unwrap()
}
).unwrap()
};
let env = TestPkgfs::new(&system_image_hash, &static_packages, &versions_contents);
let inspector = finspect::Inspector::new();
let _blob_location = BlobLocation::new(
|| Ok(env.system_image()),
|| Ok(env.versions()),
inspector.root().create_child("blob-location"),
)
.await;
assert_inspect_tree!(inspector, root: {
"blob-location": {
"base-blobs": {
count: 4u64,
}
}
});
}
#[fuchsia_async::run_singlethreaded(test)]
async fn inspect_correct_blob_count_shared_blob() {
let system_image_hash: Hash =
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap();
let fake_package_hash: Hash =
"1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap();
let static_packages = StaticPackages::from_entries(vec![(
PackagePath::from_name_and_variant("fake-package", "0").unwrap(),
fake_package_hash,
)]);
let versions_contents = hashmap! {
system_image_hash.clone() => MetaContents::from_map(
btreemap! {
"shared-blob".to_string() =>
"2222222222222222222222222222222222222222222222222222222222222222".parse().unwrap()
}
).unwrap(),
fake_package_hash.clone() => MetaContents::from_map(
btreemap! {
"secretly-the-same-blob".to_string() =>
"2222222222222222222222222222222222222222222222222222222222222222".parse().unwrap()
}
).unwrap()
};
let env = TestPkgfs::new(&system_image_hash, &static_packages, &versions_contents);
let inspector = finspect::Inspector::new();
let _blob_location = BlobLocation::new(
|| Ok(env.system_image()),
|| Ok(env.versions()),
inspector.root().create_child("blob-location"),
)
.await;
assert_inspect_tree!(inspector, root: {
"blob-location": {
"base-blobs": {
count: 3u64,
}
}
});
}
#[fuchsia_async::run_singlethreaded(test)]
async fn inspect_failure_loading_base_blobs_assume_all_in_base() {
let system_image_hash: Hash =
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap();
let static_packages = StaticPackages::from_entries(vec![]);
// system_image not in versions, so loading will fail
let versions_contents = HashMap::new();
let env = TestPkgfs::new(&system_image_hash, &static_packages, &versions_contents);
let inspector = finspect::Inspector::new();
let _blob_location = BlobLocation::new(
|| Ok(env.system_image()),
|| Ok(env.versions()),
inspector.root().create_child("blob-location"),
)
.await;
assert_inspect_tree!(inspector, root: {
"blob-location": {
"assume-all-in-base": {}
}
});
}
#[fuchsia_async::run_singlethreaded(test)]
async fn is_blob_in_base_when_loading_succeeds() {
let system_image_hash: Hash =
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap();
let fake_package_hash: Hash =
"1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap();
let static_packages = StaticPackages::from_entries(vec![(
PackagePath::from_name_and_variant("fake-package", "0").unwrap(),
fake_package_hash,
)]);
let some_blob_hash =
"2222222222222222222222222222222222222222222222222222222222222222".parse().unwrap();
let other_blob_hash =
"3333333333333333333333333333333333333333333333333333333333333333".parse().unwrap();
let versions_contents = hashmap! {
system_image_hash.clone() => MetaContents::from_map(
btreemap! {
"some-blob".to_string() => some_blob_hash
}
).unwrap(),
fake_package_hash.clone() => MetaContents::from_map(
btreemap! {
"other-blob".to_string() => other_blob_hash
}
).unwrap()
};
let env = TestPkgfs::new(&system_image_hash, &static_packages, &versions_contents);
let blob_location = BlobLocation::new(
|| Ok(env.system_image()),
|| Ok(env.versions()),
finspect::Inspector::new().root().create_child("blob-location"),
)
.await;
assert_eq!(blob_location.is_blob_in_base(&system_image_hash), true);
assert_eq!(blob_location.is_blob_in_base(&fake_package_hash), true);
assert_eq!(blob_location.is_blob_in_base(&some_blob_hash), true);
assert_eq!(blob_location.is_blob_in_base(&other_blob_hash), true);
assert_eq!(
blob_location.is_blob_in_base(
&"4444444444444444444444444444444444444444444444444444444444444444"
.parse()
.unwrap(),
),
false
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn is_blob_in_base_when_loading_fails() {
let system_image_hash: Hash =
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap();
let static_packages = StaticPackages::from_entries(vec![]);
// system_image not in versions, so loading will fail
let versions_contents = HashMap::new();
let env = TestPkgfs::new(&system_image_hash, &static_packages, &versions_contents);
let blob_location = BlobLocation::new(
|| Ok(env.system_image()),
|| Ok(env.versions()),
finspect::Inspector::new().root().create_child("blob-location"),
)
.await;
assert_eq!(blob_location.is_blob_in_base(&system_image_hash), true);
let non_existent_hash =
"1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap();
assert_eq!(blob_location.is_blob_in_base(&non_existent_hash), true);
}
}