blob: 018cc4905ce66273f81f29940f28e29039a2d261 [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 {
crate::{PkgFs, TestEnv},
anyhow::Error,
fidl::endpoints::ClientEnd,
fidl_fuchsia_io::{DirectoryMarker, DirectoryProxy},
fidl_fuchsia_paver as paver,
fidl_fuchsia_space::{ErrorCode, ManagerMarker},
fuchsia_async::{self as fasync, OnSignals},
fuchsia_merkle::Hash,
fuchsia_pkg::{MetaContents, PackagePath},
fuchsia_zircon as zx,
maplit::{btreemap, hashmap},
matches::assert_matches,
mock_paver::{hooks as mphooks, MockPaverServiceBuilder, PaverEvent},
std::{
fs::{create_dir, create_dir_all, File},
io::Write as _,
path::PathBuf,
},
system_image::StaticPackages,
tempfile::TempDir,
};
struct TempDirPkgFs {
root: TempDir,
}
impl TempDirPkgFs {
fn new() -> Self {
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 root = tempfile::tempdir().unwrap();
create_dir(root.path().join("ctl")).unwrap();
create_dir(root.path().join("system")).unwrap();
File::create(root.path().join("system/meta"))
.unwrap()
.write_all(system_image_hash.to_string().as_bytes())
.unwrap();
create_dir(root.path().join("system/data")).unwrap();
static_packages
.serialize(File::create(root.path().join("system/data/static_packages")).unwrap())
.unwrap();
create_dir(root.path().join("versions")).unwrap();
for (hash, contents) in versions_contents.iter() {
let meta_path = root.path().join(format!("versions/{}/meta", hash));
create_dir_all(&meta_path).unwrap();
contents.serialize(&mut File::create(meta_path.join("contents")).unwrap()).unwrap();
}
create_dir(root.path().join("blobfs")).unwrap();
Self { root }
}
fn garbage_path(&self) -> PathBuf {
self.root.path().join("ctl/do-not-use-this-garbage")
}
fn create_garbage(&self) {
File::create(self.garbage_path()).unwrap();
}
fn garbage_exists(&self) -> bool {
self.garbage_path().exists()
}
}
impl PkgFs for TempDirPkgFs {
fn root_dir_handle(&self) -> Result<ClientEnd<DirectoryMarker>, Error> {
Ok(fdio::transfer_fd(File::open(self.root.path()).unwrap()).unwrap().into())
}
fn blobfs_root_proxy(&self) -> Result<DirectoryProxy, Error> {
let dir_handle: ClientEnd<DirectoryMarker> =
fdio::transfer_fd(File::open(self.root.path().join("blobfs")).unwrap()).unwrap().into();
Ok(dir_handle.into_proxy().unwrap())
}
}
#[fasync::run_singlethreaded(test)]
async fn gc_garbage_file_deleted() {
let env = TestEnv::builder().pkgfs(TempDirPkgFs::new()).build().await;
env.pkgfs.create_garbage();
let res = env.proxies.space_manager.gc().await;
assert_matches!(res, Ok(Ok(())));
assert!(!env.pkgfs.garbage_exists());
}
#[fasync::run_singlethreaded(test)]
async fn gc_twice_same_client() {
let env = TestEnv::builder().pkgfs(TempDirPkgFs::new()).build().await;
env.pkgfs.create_garbage();
let res = env.proxies.space_manager.gc().await;
assert_matches!(res, Ok(Ok(())));
assert!(!env.pkgfs.garbage_exists());
env.pkgfs.create_garbage();
let res = env.proxies.space_manager.gc().await;
assert_matches!(res, Ok(Ok(())));
assert!(!env.pkgfs.garbage_exists());
}
#[fasync::run_singlethreaded(test)]
async fn gc_twice_different_clients() {
let env = TestEnv::builder().pkgfs(TempDirPkgFs::new()).build().await;
env.pkgfs.create_garbage();
let res = env.proxies.space_manager.gc().await;
assert_matches!(res, Ok(Ok(())));
assert!(!env.pkgfs.garbage_exists());
env.pkgfs.create_garbage();
let second_connection = env
.apps
.realm_instance
.root
.connect_to_protocol_at_exposed_dir::<ManagerMarker>()
.expect("connect to space manager");
let res = second_connection.gc().await;
assert_matches!(res, Ok(Ok(())));
assert!(!env.pkgfs.garbage_exists());
}
#[fasync::run_singlethreaded(test)]
async fn gc_error_missing_garbage_file() {
let env = TestEnv::builder().pkgfs(TempDirPkgFs::new()).build().await;
let res = env.proxies.space_manager.gc().await;
assert_matches!(res, Ok(Err(ErrorCode::Internal)));
}
#[fasync::run_singlethreaded(test)]
async fn gc_error_pending_commit() {
let (throttle_hook, throttler) = mphooks::throttle();
let env = TestEnv::builder()
.pkgfs(TempDirPkgFs::new())
.paver_service_builder(
MockPaverServiceBuilder::new()
.insert_hook(throttle_hook)
.insert_hook(mphooks::config_status(|_| Ok(paver::ConfigurationStatus::Pending))),
)
.build()
.await;
env.pkgfs.create_garbage();
// Allow the paver to emit enough events to unblock the CommitStatusProvider FIDL server, but
// few enough to guarantee the commit is still pending.
let () = throttler.emit_next_paver_events(&[
PaverEvent::QueryCurrentConfiguration,
PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A },
]);
assert_matches!(env.proxies.space_manager.gc().await, Ok(Err(ErrorCode::PendingCommit)));
// When the commit completes, GC should unblock as well.
let () = throttler.emit_next_paver_events(&[
PaverEvent::SetConfigurationHealthy { configuration: paver::Configuration::A },
PaverEvent::SetConfigurationUnbootable { configuration: paver::Configuration::B },
PaverEvent::BootManagerFlush,
]);
let event_pair =
env.proxies.commit_status_provider.is_current_system_committed().await.unwrap();
assert_eq!(OnSignals::new(&event_pair, zx::Signals::USER_0).await, Ok(zx::Signals::USER_0));
assert_matches!(env.proxies.space_manager.gc().await, Ok(Ok(())));
}