| // Copyright 2022 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 { |
| fidl_fuchsia_component_resolution as fresolution, fidl_fuchsia_io as fio, |
| fuchsia_fs::{file::ReadError, node::OpenError}, |
| fuchsia_zircon_status::Status, |
| thiserror::Error, |
| version_history::AbiRevision, |
| }; |
| |
| #[derive(Error, Debug)] |
| pub enum AbiRevisionFileError { |
| #[error("Failed to decode ABI revision value")] |
| Decode, |
| #[error("Failed to open ABI revision file: {0}")] |
| Open(#[from] OpenError), |
| #[error("Failed to read ABI revision file: {0}")] |
| Read(#[from] ReadError), |
| } |
| |
| impl From<AbiRevisionFileError> for fresolution::ResolverError { |
| fn from(err: AbiRevisionFileError) -> fresolution::ResolverError { |
| match err { |
| AbiRevisionFileError::Open(_) => fresolution::ResolverError::AbiRevisionNotFound, |
| AbiRevisionFileError::Read(_) | AbiRevisionFileError::Decode => { |
| fresolution::ResolverError::InvalidAbiRevision |
| } |
| } |
| } |
| } |
| |
| /// Attempt to read an ABI revision value from the given file path, but do not fail if the file is absent. |
| pub async fn read_abi_revision_optional( |
| dir: &fio::DirectoryProxy, |
| path: &str, |
| ) -> Result<Option<AbiRevision>, AbiRevisionFileError> { |
| match read_abi_revision(dir, path).await { |
| Ok(abi) => Ok(Some(abi)), |
| Err(AbiRevisionFileError::Open(OpenError::OpenError(Status::NOT_FOUND))) => Ok(None), |
| Err(e) => Err(e), |
| } |
| } |
| |
| // TODO(https://fxbug.dev/42063073): return fuchsia.version.AbiRevision & use decode_persistent(). |
| /// Read an ABI revision value from the given file path. |
| async fn read_abi_revision( |
| dir: &fio::DirectoryProxy, |
| path: &str, |
| ) -> Result<AbiRevision, AbiRevisionFileError> { |
| let file = fuchsia_fs::directory::open_file(&dir, path, fio::OpenFlags::RIGHT_READABLE).await?; |
| let bytes: [u8; 8] = fuchsia_fs::file::read(&file) |
| .await? |
| .try_into() |
| .map_err(|_| AbiRevisionFileError::Decode)?; |
| Ok(AbiRevision::from_bytes(bytes)) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| fuchsia_fs::directory::open_in_namespace, |
| std::sync::Arc, |
| vfs::{ |
| directory::entry_container::Directory, execution_scope, file::vmo::read_only, |
| pseudo_directory, |
| }, |
| }; |
| |
| fn serve_dir(root: Arc<impl Directory>) -> fio::DirectoryProxy { |
| let (dir_proxy, dir_server) = |
| fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| root.open( |
| execution_scope::ExecutionScope::new(), |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY, |
| vfs::path::Path::dot().into(), |
| fidl::endpoints::ServerEnd::new(dir_server.into_channel()), |
| ); |
| dir_proxy |
| } |
| |
| fn init_fuchsia_abi_dir(filename: &'static str, content: &'static [u8]) -> fio::DirectoryProxy { |
| let dir = pseudo_directory! { |
| "meta" => pseudo_directory! { |
| "fuchsia.abi" => pseudo_directory! { |
| filename => read_only(content), |
| } |
| } |
| }; |
| serve_dir(dir) |
| } |
| |
| const ABI_REV_MAX: &'static [u8] = &u64::MAX.to_le_bytes(); |
| const ABI_REV_ZERO: &'static [u8] = &0u64.to_le_bytes(); |
| |
| #[fuchsia::test] |
| async fn test_read_abi_revision_impl() -> Result<(), AbiRevisionFileError> { |
| // Test input that cannot be decoded into a u64 fails |
| let dir = init_fuchsia_abi_dir("abi-revision", b"Invalid ABI revision string"); |
| let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await; |
| assert!(matches!(res.unwrap_err(), AbiRevisionFileError::Decode)); |
| |
| let dir = init_fuchsia_abi_dir("abi-revision", b""); |
| let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await; |
| assert!(matches!(res.unwrap_err(), AbiRevisionFileError::Decode)); |
| |
| // Test u64 inputs can be read |
| let dir = init_fuchsia_abi_dir("abi-revision", ABI_REV_MAX); |
| let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap(); |
| assert_eq!(res, Some(u64::MAX.into())); |
| |
| let dir = init_fuchsia_abi_dir("abi-revision", ABI_REV_ZERO); |
| let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap(); |
| assert_eq!(res, Some(0u64.into())); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn test_read_abi_revision_optional_allows_absent_file() -> Result<(), AbiRevisionFileError> |
| { |
| // Test abi-revision file not found produces Ok(None) |
| let dir = init_fuchsia_abi_dir("abi-revision-staging", ABI_REV_MAX); |
| let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap(); |
| assert_eq!(res, None); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn test_read_abi_revision_fails_absent_file() -> Result<(), AbiRevisionFileError> { |
| let dir = init_fuchsia_abi_dir("a-different-file", ABI_REV_MAX); |
| let err = read_abi_revision(&dir, AbiRevision::PATH).await.unwrap_err(); |
| assert!(matches!(err, AbiRevisionFileError::Open(OpenError::OpenError(Status::NOT_FOUND)))); |
| Ok(()) |
| } |
| |
| // Read this test package's ABI revision. |
| #[fuchsia::test] |
| async fn read_test_pkg_abi_revision() -> Result<(), AbiRevisionFileError> { |
| let dir_proxy = open_in_namespace( |
| "/pkg", |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE, |
| ) |
| .unwrap(); |
| let abi_revision = read_abi_revision(&dir_proxy, AbiRevision::PATH) |
| .await |
| .expect("test package doesn't contain an ABI revision"); |
| version_history::HISTORY |
| .check_abi_revision_for_runtime(abi_revision) |
| .expect("test package ABI revision should be valid"); |
| Ok(()) |
| } |
| } |