| // Copyright 2019 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::{ |
| errors::{self, Error}, |
| update_manager::TargetChannelUpdater, |
| }, |
| anyhow::{anyhow, Context as _}, |
| fidl_fuchsia_paver::{ |
| Asset, BootManagerMarker, Configuration, DataSinkMarker, PaverMarker, PaverProxy, |
| }, |
| fidl_fuchsia_pkg::{self as fpkg, PackageResolverMarker, PackageResolverProxyInterface}, |
| fidl_fuchsia_space as fidl_space, |
| fuchsia_component::client::connect_to_protocol, |
| fuchsia_hash::Hash, |
| fuchsia_syslog::{fx_log_err, fx_log_info, fx_log_warn}, |
| fuchsia_zircon as zx, |
| std::{cmp::min, convert::TryInto as _, io}, |
| update_package::{ImageType, UpdatePackage}, |
| }; |
| |
| const UPDATE_PACKAGE_URL: &str = "fuchsia-pkg://fuchsia.com/update"; |
| |
| #[derive(PartialEq, Eq, Debug, Clone)] |
| pub enum SystemUpdateStatus { |
| UpToDate { system_image: Hash, update_package: Hash }, |
| UpdateAvailable { current_system_image: Hash, latest_system_image: Hash }, |
| } |
| |
| pub async fn check_for_system_update( |
| last_known_update_package: Option<&Hash>, |
| target_channel_manager: &dyn TargetChannelUpdater, |
| ) -> Result<SystemUpdateStatus, Error> { |
| let mut file_system = RealFileSystem; |
| let package_resolver = |
| connect_to_protocol::<PackageResolverMarker>().map_err(Error::ConnectPackageResolver)?; |
| let paver = connect_to_protocol::<PaverMarker>().map_err(Error::ConnectPaver)?; |
| let space_manager = |
| connect_to_protocol::<fidl_space::ManagerMarker>().map_err(Error::ConnectSpaceManager)?; |
| |
| check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| target_channel_manager, |
| last_known_update_package, |
| &space_manager, |
| ) |
| .await |
| } |
| |
| // For mocking |
| trait FileSystem { |
| fn read_to_string(&self, path: &str) -> io::Result<String>; |
| fn remove_file(&mut self, path: &str) -> io::Result<()>; |
| } |
| |
| struct RealFileSystem; |
| |
| impl FileSystem for RealFileSystem { |
| fn read_to_string(&self, path: &str) -> io::Result<String> { |
| std::fs::read_to_string(path) |
| } |
| fn remove_file(&mut self, path: &str) -> io::Result<()> { |
| std::fs::remove_file(path) |
| } |
| } |
| |
| async fn check_for_system_update_impl( |
| file_system: &mut impl FileSystem, |
| package_resolver: &impl PackageResolverProxyInterface, |
| paver: &PaverProxy, |
| target_channel_manager: &dyn TargetChannelUpdater, |
| last_known_update_package: Option<&Hash>, |
| space_manager: &fidl_space::ManagerProxy, |
| ) -> Result<SystemUpdateStatus, Error> { |
| let update_pkg = |
| latest_update_package(package_resolver, target_channel_manager, space_manager).await?; |
| let latest_update_merkle = update_pkg.hash().await.map_err(errors::UpdatePackage::Hash)?; |
| let current_system_image = current_system_image_merkle(file_system)?; |
| let latest_system_image = latest_system_image_merkle(&update_pkg).await?; |
| |
| let up_to_date = Ok(SystemUpdateStatus::UpToDate { |
| system_image: current_system_image, |
| update_package: latest_update_merkle, |
| }); |
| |
| if let Some(last_known_update_package) = last_known_update_package { |
| if *last_known_update_package == latest_update_merkle { |
| fx_log_info!("Last known update package is latest, system is up to date"); |
| return up_to_date; |
| } |
| } |
| |
| let update_available = |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }); |
| |
| if current_system_image != latest_system_image { |
| fx_log_info!("Current system image is not latest, system is not up to date"); |
| return update_available; |
| } |
| |
| // When present, checking vbmeta is sufficient, but vbmeta isn't supported on all devices. |
| for (image, asset) in [ |
| (ImageType::FuchsiaVbmeta, Asset::VerifiedBootMetadata), |
| (ImageType::Zbi, Asset::Kernel), |
| (ImageType::ZbiSigned, Asset::Kernel), |
| ] |
| .iter() |
| { |
| match is_image_up_to_date(&update_pkg, paver, *image, *asset).await { |
| Ok(true) => { |
| fx_log_info!("Image {:?} is up to date, system is up to date", image); |
| return up_to_date; |
| } |
| Ok(false) => { |
| fx_log_info!("Image {:?} is not latest, system is not up to date", image); |
| return update_available; |
| } |
| Err(err) => { |
| fx_log_warn!( |
| "Failed to check if {} is up to date: {:#}", |
| image.name(), |
| anyhow!(err) |
| ); |
| } |
| } |
| } |
| |
| fx_log_info!("Could not find any system differences, assuming system is up to date"); |
| up_to_date |
| } |
| |
| fn current_system_image_merkle(file_system: &impl FileSystem) -> Result<Hash, Error> { |
| Ok(file_system |
| .read_to_string("/pkgfs/system/meta") |
| .map_err(Error::ReadSystemMeta)? |
| .parse::<Hash>() |
| .map_err(Error::ParseSystemMeta)?) |
| } |
| |
| async fn gc(space_manager: &fidl_space::ManagerProxy) -> Result<(), anyhow::Error> { |
| let () = space_manager |
| .gc() |
| .await |
| .context("while performing gc call")? |
| .map_err(|e| anyhow!("garbage collection responded with {:?}", e))?; |
| Ok(()) |
| } |
| |
| async fn latest_update_package( |
| package_resolver: &impl PackageResolverProxyInterface, |
| channel_manager: &dyn TargetChannelUpdater, |
| space_manager: &fidl_space::ManagerProxy, |
| ) -> Result<UpdatePackage, errors::UpdatePackage> { |
| match latest_update_package_attempt(package_resolver, channel_manager).await { |
| Ok(update_package) => return Ok(update_package), |
| Err(errors::UpdatePackage::Resolve(fidl_fuchsia_pkg_ext::ResolveError::NoSpace)) => {} |
| Err(raw) => return Err(raw), |
| } |
| |
| fx_log_info!("No space left for update package. Attempting to clean up older packages."); |
| |
| // If the first attempt fails with NoSpace, perform a GC and retry. |
| if let Err(e) = gc(space_manager).await { |
| fx_log_err!("unable to gc packages: {:#}", anyhow!(e)); |
| } |
| |
| match latest_update_package_attempt(package_resolver, channel_manager).await { |
| Ok(update_package) => Ok(update_package), |
| Err(raw) => { |
| fx_log_err!( |
| "`latest_update_package_attempt` failed twice to fetch the update package." |
| ); |
| Err(raw) |
| } |
| } |
| } |
| |
| async fn latest_update_package_attempt( |
| package_resolver: &impl PackageResolverProxyInterface, |
| channel_manager: &dyn TargetChannelUpdater, |
| ) -> Result<UpdatePackage, errors::UpdatePackage> { |
| let (dir_proxy, dir_server_end) = |
| fidl::endpoints::create_proxy().map_err(errors::UpdatePackage::CreateDirectoryProxy)?; |
| let update_package = |
| channel_manager.get_target_channel_update_url().unwrap_or(UPDATE_PACKAGE_URL.to_owned()); |
| let fut = package_resolver.resolve(&update_package, dir_server_end); |
| let _: fpkg::ResolutionContext = fut |
| .await |
| .map_err(errors::UpdatePackage::ResolveFidl)? |
| .map_err(|raw| errors::UpdatePackage::Resolve(raw.into()))?; |
| Ok(UpdatePackage::new(dir_proxy)) |
| } |
| |
| async fn latest_system_image_merkle( |
| update_package: &UpdatePackage, |
| ) -> Result<Hash, errors::UpdatePackage> { |
| let packages = |
| update_package.packages().await.map_err(errors::UpdatePackage::ExtractPackagesManifest)?; |
| let system_image = packages |
| .into_iter() |
| .find(|url| url.path() == "/system_image/0") |
| .ok_or(errors::UpdatePackage::MissingSystemImage)?; |
| let hash = system_image |
| .hash() |
| .ok_or_else(|| errors::UpdatePackage::UnPinnedSystemImage(system_image.clone()))?; |
| Ok(hash.to_owned()) |
| } |
| |
| async fn is_image_up_to_date( |
| update_package: &UpdatePackage, |
| paver: &PaverProxy, |
| image_type: ImageType, |
| asset: Asset, |
| ) -> Result<bool, anyhow::Error> { |
| let latest_image = |
| update_package.open_image(&update_package::Image::new(image_type, None)).await?; |
| |
| let (boot_manager, server_end) = fidl::endpoints::create_proxy::<BootManagerMarker>()?; |
| let () = paver.find_boot_manager(server_end).context("connect to fuchsia.paver.BootManager")?; |
| let configuration = match boot_manager.query_current_configuration().await { |
| Ok(response) => response |
| .map_err(|status| zx::Status::from_raw(status)) |
| .context("querying current configuration")?, |
| Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. }) => { |
| fx_log_warn!("device does not support ABR. Checking image in slot A"); |
| Configuration::A |
| } |
| Err(err) => return Err(err).context("querying current configuration"), |
| }; |
| |
| let (data_sink, server_end) = fidl::endpoints::create_proxy::<DataSinkMarker>()?; |
| let () = paver.find_data_sink(server_end).context("connect to fuchsia.paver.DataSink")?; |
| |
| let current_image = data_sink |
| .read_asset(configuration, asset) |
| .await? |
| .map_err(|status| anyhow!("read_asset responded with {}", zx::Status::from_raw(status)))?; |
| |
| compare_buffer(latest_image, current_image) |
| } |
| |
| // Compare two buffers and return true if they are equal. |
| // If they have different sizes, only compare up to the smaller size and ignoring the rest. |
| // This is because when reading asset from paver, we might get extra data after the image. |
| fn compare_buffer( |
| a: fidl_fuchsia_mem::Buffer, |
| b: fidl_fuchsia_mem::Buffer, |
| ) -> Result<bool, anyhow::Error> { |
| let a_size = a.size.try_into()?; |
| let b_size = b.size.try_into()?; |
| let size = min(a_size, b_size); |
| let max_buffer_size = 32 * 1024; |
| let step_size = min(size, max_buffer_size); |
| let mut a_buf = vec![0u8; step_size]; |
| let mut b_buf = vec![0u8; step_size]; |
| for offset in (0..size).step_by(step_size) { |
| let current_step_size = min(step_size, size - offset); |
| a.vmo.read(&mut a_buf[..current_step_size], offset as u64)?; |
| b.vmo.read(&mut b_buf[..current_step_size], offset as u64)?; |
| if a_buf[..current_step_size] != b_buf[..current_step_size] { |
| return Ok(false); |
| } |
| } |
| Ok(true) |
| } |
| |
| #[cfg(test)] |
| pub mod test_check_for_system_update_impl { |
| use { |
| super::*, |
| crate::update_manager::tests::FakeTargetChannelUpdater, |
| assert_matches::assert_matches, |
| fidl_fuchsia_io as fio, |
| fidl_fuchsia_paver::Configuration, |
| fidl_fuchsia_pkg::{ |
| PackageResolverGetHashResult, PackageResolverResolveResult, |
| PackageResolverResolveWithContextResult, PackageUrl, |
| }, |
| fuchsia_async as fasync, |
| futures::{future, TryFutureExt, TryStreamExt}, |
| lazy_static::lazy_static, |
| maplit::hashmap, |
| mock_paver::MockPaverServiceBuilder, |
| parking_lot::Mutex, |
| std::{collections::hash_map::HashMap, fs, sync::Arc}, |
| }; |
| |
| const ACTIVE_SYSTEM_IMAGE_MERKLE: &str = |
| "0000000000000000000000000000000000000000000000000000000000000000"; |
| const NEW_SYSTEM_IMAGE_MERKLE: &str = |
| "1111111111111111111111111111111111111111111111111111111111111111"; |
| |
| lazy_static! { |
| static ref UPDATE_PACKAGE_MERKLE: Hash = [0x22; 32].into(); |
| } |
| |
| struct FakeFileSystem { |
| contents: HashMap<String, String>, |
| } |
| impl FakeFileSystem { |
| fn new_with_valid_system_meta() -> FakeFileSystem { |
| FakeFileSystem { |
| contents: hashmap![ |
| "/pkgfs/system/meta".to_string() => ACTIVE_SYSTEM_IMAGE_MERKLE.to_string() |
| ], |
| } |
| } |
| } |
| impl FileSystem for FakeFileSystem { |
| fn read_to_string(&self, path: &str) -> io::Result<String> { |
| self.contents |
| .get(path) |
| .ok_or(io::Error::new( |
| io::ErrorKind::NotFound, |
| format!("not present in fake file system: {}", path), |
| )) |
| .map(|s| s.to_string()) |
| } |
| fn remove_file(&mut self, path: &str) -> io::Result<()> { |
| self.contents.remove(path).and(Some(())).ok_or(io::Error::new( |
| io::ErrorKind::NotFound, |
| format!("fake file system cannot remove non-existent file: {}", path), |
| )) |
| } |
| } |
| |
| pub struct PackageResolverProxyTempDirBuilder { |
| temp_dir: tempfile::TempDir, |
| expected_package_url: String, |
| write_packages_json: bool, |
| packages: Vec<String>, |
| images: HashMap<String, Vec<u8>>, |
| } |
| |
| impl PackageResolverProxyTempDirBuilder { |
| const VBMETA_NAME: &'static str = "fuchsia.vbmeta"; |
| const ZBI_NAME: &'static str = "zbi"; |
| const ZBI_SIGNED_NAME: &'static str = "zbi.signed"; |
| fn new() -> PackageResolverProxyTempDirBuilder { |
| Self { |
| temp_dir: tempfile::tempdir().expect("create temp dir"), |
| expected_package_url: UPDATE_PACKAGE_URL.to_owned(), |
| write_packages_json: false, |
| packages: Vec::new(), |
| images: HashMap::new(), |
| } |
| } |
| |
| fn with_packages_json(mut self) -> Self { |
| self.write_packages_json = true; |
| self |
| } |
| |
| fn with_system_image_merkle(mut self, merkle: &str) -> Self { |
| assert!(self.write_packages_json); |
| self.packages.push(format!("fuchsia-pkg://fuchsia.com/system_image/0?hash={}", merkle)); |
| self |
| } |
| |
| fn with_image(mut self, name: &str, value: impl AsRef<[u8]>) -> Self { |
| self.images.insert(name.to_owned(), value.as_ref().to_vec()); |
| self |
| } |
| |
| fn with_update_url(mut self, url: &str) -> Self { |
| self.expected_package_url = url.to_owned(); |
| self |
| } |
| |
| fn build(self) -> PackageResolverProxyTempDir { |
| fs::write(self.temp_dir.path().join("meta"), UPDATE_PACKAGE_MERKLE.to_string()) |
| .expect("write meta"); |
| if self.write_packages_json { |
| let packages_json = serde_json::json!({ |
| "version": "1", |
| "content": self.packages |
| }) |
| .to_string(); |
| eprintln!("{}", packages_json); |
| fs::write(self.temp_dir.path().join("packages.json"), packages_json) |
| .expect("write packages.json"); |
| } |
| |
| for (name, image) in self.images.into_iter() { |
| fs::write(self.temp_dir.path().join(name.clone()), image) |
| .expect(&format!("write {}", name)); |
| } |
| PackageResolverProxyTempDir { |
| temp_dir: self.temp_dir, |
| expected_package_url: self.expected_package_url, |
| } |
| } |
| } |
| |
| pub struct PackageResolverProxyTempDir { |
| temp_dir: tempfile::TempDir, |
| expected_package_url: String, |
| } |
| impl PackageResolverProxyTempDir { |
| fn new_with_default_meta() -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new().build() |
| } |
| |
| fn new_with_empty_packages_json() -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new().with_packages_json().build() |
| } |
| |
| fn new_with_latest_system_image(merkle: &str) -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(merkle) |
| .build() |
| } |
| |
| fn new_with_vbmeta(vbmeta: impl AsRef<[u8]>) -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(ACTIVE_SYSTEM_IMAGE_MERKLE) |
| .with_image(PackageResolverProxyTempDirBuilder::VBMETA_NAME, vbmeta) |
| .build() |
| } |
| |
| fn new_with_zbi(zbi: impl AsRef<[u8]>) -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(ACTIVE_SYSTEM_IMAGE_MERKLE) |
| .with_image(PackageResolverProxyTempDirBuilder::ZBI_NAME, zbi) |
| .build() |
| } |
| |
| fn new_with_zbi_signed(zbi: impl AsRef<[u8]>) -> PackageResolverProxyTempDir { |
| PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(ACTIVE_SYSTEM_IMAGE_MERKLE) |
| .with_image(PackageResolverProxyTempDirBuilder::ZBI_SIGNED_NAME, zbi) |
| .build() |
| } |
| } |
| impl PackageResolverProxyInterface for PackageResolverProxyTempDir { |
| type ResolveResponseFut = future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| package_url: &str, |
| dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| assert_eq!(package_url, self.expected_package_url); |
| fdio::service_connect( |
| self.temp_dir.path().to_str().expect("path is utf8"), |
| dir.into_channel(), |
| ) |
| .unwrap(); |
| future::ok(Ok(fpkg::ResolutionContext { bytes: vec![] })) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| panic!("resolve_with_context not implemented"); |
| } |
| |
| type GetHashResponseFut = future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| struct MockSpaceManagerService { |
| call_count: Mutex<u32>, |
| } |
| |
| impl MockSpaceManagerService { |
| fn new() -> Self { |
| Self { call_count: Mutex::new(0) } |
| } |
| |
| /// Spawns a new task to serve the space manager protocol. |
| pub fn spawn_gc_service(self: &Arc<Self>) -> fidl_space::ManagerProxy { |
| let (proxy, server_end) = |
| fidl::endpoints::create_proxy_and_stream::<fidl_space::ManagerMarker>().unwrap(); |
| |
| fasync::Task::spawn(Arc::clone(self).run_gc_service(server_end).unwrap_or_else(|e| { |
| panic!("error running space manager service: {:#}", anyhow!(e)) |
| })) |
| .detach(); |
| |
| proxy |
| } |
| |
| async fn run_gc_service( |
| self: Arc<Self>, |
| mut stream: fidl_space::ManagerRequestStream, |
| ) -> Result<(), anyhow::Error> { |
| while let Some(req) = stream.try_next().await? { |
| match req { |
| fidl_space::ManagerRequest::Gc { responder } => { |
| *self.call_count.lock() += 1; |
| responder.send(&mut Ok(())).unwrap() |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_missing_system_meta_file() { |
| let mut file_system = FakeFileSystem { contents: hashmap![] }; |
| let package_resolver = PackageResolverProxyTempDir::new_with_default_meta(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Err(Error::ReadSystemMeta(_))); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_malformatted_system_meta_file() { |
| let mut file_system = FakeFileSystem { |
| contents: hashmap![ |
| "/pkgfs/system/meta".to_string() => "not-a-merkle".to_string() |
| ], |
| }; |
| let package_resolver = PackageResolverProxyTempDir::new_with_default_meta(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Err(Error::ParseSystemMeta(_))); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_resolve_update_package_fidl_error() { |
| struct PackageResolverProxyFidlError; |
| impl PackageResolverProxyInterface for PackageResolverProxyFidlError { |
| type ResolveResponseFut = |
| future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| _package_url: &str, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| future::err(fidl::Error::Invalid) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| future::err(fidl::Error::Invalid) |
| } |
| |
| type GetHashResponseFut = |
| future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyFidlError; |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::ResolveFidl(_)))); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_package_garbage_collection_called_no_space() { |
| struct PackageResolverProxyNoSpaceOnce { |
| temp_dir: tempfile::TempDir, |
| expected_package_url: String, |
| call_count: Mutex<u32>, |
| } |
| |
| impl PackageResolverProxyNoSpaceOnce { |
| fn new() -> PackageResolverProxyNoSpaceOnce { |
| let package_resolver = PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(ACTIVE_SYSTEM_IMAGE_MERKLE) |
| .build(); |
| PackageResolverProxyNoSpaceOnce { |
| temp_dir: package_resolver.temp_dir, |
| expected_package_url: package_resolver.expected_package_url, |
| call_count: Mutex::new(0), |
| } |
| } |
| } |
| |
| impl PackageResolverProxyInterface for PackageResolverProxyNoSpaceOnce { |
| type ResolveResponseFut = |
| future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| package_url: &str, |
| dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| *self.call_count.lock() += 1; |
| |
| if *self.call_count.lock() == 1 { |
| return future::ok(Err(fidl_fuchsia_pkg::ResolveError::NoSpace)); |
| } |
| |
| assert_eq!(package_url, self.expected_package_url); |
| fdio::service_connect( |
| self.temp_dir.path().to_str().expect("path is utf8"), |
| dir.into_channel(), |
| ) |
| .unwrap(); |
| future::ok(Ok(fpkg::ResolutionContext { bytes: vec![] })) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| panic!("resolve_with_context not implemented"); |
| } |
| |
| type GetHashResponseFut = |
| future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyNoSpaceOnce::new(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| Some(&UPDATE_PACKAGE_MERKLE), |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal")); |
| |
| assert_matches!(*mock_space_manager.call_count.lock(), 1); |
| assert_matches!(*package_resolver.call_count.lock(), 2); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_package_garbage_collection_succeeds() { |
| struct PackageResolverProxyNoSpaceError; |
| impl PackageResolverProxyInterface for PackageResolverProxyNoSpaceError { |
| type ResolveResponseFut = |
| future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| _package_url: &str, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| future::ok(Err(fidl_fuchsia_pkg::ResolveError::NoSpace)) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| panic!("resolve_with_context not implemented"); |
| } |
| |
| type GetHashResponseFut = |
| future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyNoSpaceError; |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Err(Error::UpdatePackage(errors::UpdatePackage::Resolve( |
| fidl_fuchsia_pkg_ext::ResolveError::NoSpace |
| ))) |
| ); |
| assert_matches!(*mock_space_manager.call_count.lock(), 1); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_resolve_update_package_zx_error() { |
| struct PackageResolverProxyZxError; |
| impl PackageResolverProxyInterface for PackageResolverProxyZxError { |
| type ResolveResponseFut = |
| future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| _package_url: &str, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| future::ok(Err(fidl_fuchsia_pkg::ResolveError::Internal)) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| panic!("resolve_with_context not implemented"); |
| } |
| |
| type GetHashResponseFut = |
| future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyZxError; |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::Resolve(_)))); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_resolve_update_package_directory_closed() { |
| struct PackageResolverProxyDirectoryCloser; |
| impl PackageResolverProxyInterface for PackageResolverProxyDirectoryCloser { |
| type ResolveResponseFut = |
| future::Ready<Result<PackageResolverResolveResult, fidl::Error>>; |
| fn resolve( |
| &self, |
| _package_url: &str, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveResponseFut { |
| future::ok(Ok(fpkg::ResolutionContext { bytes: vec![] })) |
| } |
| |
| type ResolveWithContextResponseFut = |
| future::Ready<Result<PackageResolverResolveWithContextResult, fidl::Error>>; |
| fn resolve_with_context( |
| &self, |
| _package_url: &str, |
| _context: &mut fpkg::ResolutionContext, |
| _dir: fidl::endpoints::ServerEnd<fio::DirectoryMarker>, |
| ) -> Self::ResolveWithContextResponseFut { |
| panic!("resolve_with_context not implemented"); |
| } |
| |
| type GetHashResponseFut = |
| future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>; |
| fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut { |
| panic!("get_hash not implemented"); |
| } |
| } |
| |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyDirectoryCloser; |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::Hash(_)))); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_package_missing_packages_json() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_default_meta(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Err(Error::UpdatePackage(errors::UpdatePackage::ExtractPackagesManifest(_))) |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_package_empty_packages_json() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_empty_packages_json(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Err(Error::UpdatePackage(errors::UpdatePackage::MissingSystemImage)) |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_package_bad_system_image() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = |
| PackageResolverProxyTempDir::new_with_latest_system_image("bad-merkle"); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| assert_matches!( |
| result, |
| Err(Error::UpdatePackage(errors::UpdatePackage::ExtractPackagesManifest(_))) |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_up_to_date() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => panic!("shouldn't read kernel if vbmeta is the same!"), |
| Asset::VerifiedBootMetadata => Ok(vec![1]), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| Some(&UPDATE_PACKAGE_MERKLE), |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_up_to_date_with_missing_vbmeta() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Err(zx::Status::NOT_FOUND), |
| Asset::VerifiedBootMetadata => Err(zx::Status::NOT_FOUND), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| Some(&UPDATE_PACKAGE_MERKLE), |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_available() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = |
| PackageResolverProxyTempDir::new_with_latest_system_image(NEW_SYSTEM_IMAGE_MERKLE); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == NEW_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| assert_matches!(*mock_space_manager.call_count.lock(), 0); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_update_available_changing_target_channel() { |
| let update_url: &str = "fuchsia-pkg://this.is.a.test.example.com/my-update-package"; |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDirBuilder::new() |
| .with_packages_json() |
| .with_system_image_merkle(NEW_SYSTEM_IMAGE_MERKLE) |
| .with_update_url(update_url) |
| .build(); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new_with_update_url(update_url), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == NEW_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_different_update_package_hash_does_not_trigger_update() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = |
| PackageResolverProxyTempDir::new_with_latest_system_image(ACTIVE_SYSTEM_IMAGE_MERKLE); |
| let mock_paver = Arc::new(MockPaverServiceBuilder::new().build()); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let previous_update_package = Hash::from([0x44; 32]); |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| Some(&previous_update_package), |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package}) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| update_package == *UPDATE_PACKAGE_MERKLE |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_vbmeta_only_update_available() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => panic!("not expecting to read kernel"), |
| Asset::VerifiedBootMetadata => Ok(vec![0]), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_only_update_available() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_did_not_require_update() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi([0]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_only_no_abr_update_available() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .boot_manager_close_with_epitaph(zx::Status::NOT_SUPPORTED) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_no_abr_did_not_require_update() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi([0]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .boot_manager_close_with_epitaph(zx::Status::NOT_SUPPORTED) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_signed_only_update_available() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi_signed([1]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image }) |
| if |
| current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") && |
| latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("new system image string literal") |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_zbi_signed_did_not_require_update() { |
| let mut file_system = FakeFileSystem::new_with_valid_system_meta(); |
| let package_resolver = PackageResolverProxyTempDir::new_with_zbi_signed([0]); |
| let mock_paver = Arc::new( |
| MockPaverServiceBuilder::new() |
| .active_config(Configuration::A) |
| .insert_hook(mock_paver::hooks::read_asset(|configuration, asset| { |
| assert_eq!(configuration, Configuration::A); |
| match asset { |
| Asset::Kernel => Ok(vec![0]), |
| Asset::VerifiedBootMetadata => panic!("no vbmeta available"), |
| } |
| })) |
| .build(), |
| ); |
| let paver = mock_paver.spawn_paver_service(); |
| let mock_space_manager = Arc::new(MockSpaceManagerService::new()); |
| let space_manager = mock_space_manager.spawn_gc_service(); |
| |
| let result = check_for_system_update_impl( |
| &mut file_system, |
| &package_resolver, |
| &paver, |
| &FakeTargetChannelUpdater::new(), |
| None, |
| &space_manager, |
| ) |
| .await; |
| |
| assert_matches!( |
| result, |
| Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ }) |
| if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE |
| .parse() |
| .expect("active system image string literal") |
| ); |
| } |
| } |
| |
| #[cfg(test)] |
| mod test_compare_buffer { |
| use {super::*, assert_matches::assert_matches, fidl_fuchsia_mem::Buffer, fuchsia_zircon::Vmo}; |
| |
| fn buffer(data: &[u8]) -> Buffer { |
| let size = data.len() as u64; |
| let vmo = Vmo::create(size).unwrap(); |
| vmo.write(data, 0).unwrap(); |
| Buffer { vmo, size } |
| } |
| |
| #[test] |
| fn equal() { |
| let a = [1; 100]; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&a)), Ok(true)); |
| } |
| |
| #[test] |
| fn equal_different_size() { |
| let a = [1; 100]; |
| let b = [1; 200]; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(true)); |
| } |
| |
| #[test] |
| fn equal_large_buffer() { |
| let a = [1; 100000]; |
| let b = [1; 100001]; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(true)); |
| } |
| |
| #[test] |
| fn not_equal() { |
| let a = [1; 100]; |
| let b = [0; 100]; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false)); |
| } |
| |
| #[test] |
| fn not_equal_different_size() { |
| let a = [1; 100]; |
| let b = [0; 200]; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false)); |
| } |
| |
| #[test] |
| fn not_equal_large_buffer() { |
| let a = [1; 100000]; |
| let mut b = [1; 100001]; |
| b[99999] = 0; |
| assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false)); |
| } |
| |
| #[test] |
| fn vmo_read_error() { |
| let a = Buffer { vmo: Vmo::create(100).unwrap(), size: 10000 }; |
| let b = Buffer { vmo: Vmo::create(10000).unwrap(), size: 10000 }; |
| assert_matches!(compare_buffer(a, b), Err(_)); |
| } |
| } |
| |
| #[cfg(test)] |
| mod test_real_file_system { |
| use super::*; |
| use assert_matches::assert_matches; |
| use proptest::prelude::*; |
| use std::fs; |
| use std::io::{self, Write}; |
| |
| #[test] |
| fn test_read_to_string_errors_on_missing_file() { |
| let dir = tempfile::tempdir().expect("create temp dir"); |
| let read_res = RealFileSystem.read_to_string( |
| dir.path().join("this-file-does-not-exist").to_str().expect("paths are utf8"), |
| ); |
| assert_matches!(read_res.map_err(|e| e.kind()), Err(io::ErrorKind::NotFound)); |
| } |
| |
| proptest! { |
| #[test] |
| fn test_read_to_string_preserves_contents( |
| contents in ".{0, 65}", |
| file_name in "[^\\.\0/]{1,10}", |
| ) { |
| let dir = tempfile::tempdir().expect("create temp dir"); |
| let file_path = dir.path().join(file_name); |
| let mut file = fs::File::create(&file_path).expect("create file"); |
| file.write_all(contents.as_bytes()).expect("write the contents"); |
| |
| let read_contents = RealFileSystem |
| .read_to_string(file_path.to_str().expect("paths are utf8")) |
| .expect("read the file"); |
| |
| prop_assert_eq!(read_contents, contents); |
| } |
| } |
| } |