Skip to main content

fidl_fuchsia_component_abi_ext/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use fidl_fuchsia_component_resolution as fresolution;
5use fidl_fuchsia_io as fio;
6use fuchsia_fs::file::ReadError;
7use fuchsia_fs::node::OpenError;
8use thiserror::Error;
9use version_history::AbiRevision;
10use zx_status::Status;
11
12#[derive(Error, Debug)]
13pub enum AbiRevisionFileError {
14    #[error("Failed to decode ABI revision value")]
15    Decode,
16    #[error("Failed to open ABI revision file: {0}")]
17    Open(#[from] OpenError),
18    #[error("Failed to read ABI revision file: {0}")]
19    Read(#[from] ReadError),
20}
21
22impl From<AbiRevisionFileError> for fresolution::ResolverError {
23    fn from(err: AbiRevisionFileError) -> fresolution::ResolverError {
24        match err {
25            AbiRevisionFileError::Open(_) => fresolution::ResolverError::AbiRevisionNotFound,
26            AbiRevisionFileError::Read(_) | AbiRevisionFileError::Decode => {
27                fresolution::ResolverError::InvalidAbiRevision
28            }
29        }
30    }
31}
32
33/// Attempt to read an ABI revision value from the given file path, but do not fail if the file is absent.
34pub async fn read_abi_revision_optional(
35    dir: &fio::DirectoryProxy,
36    path: &str,
37) -> Result<Option<AbiRevision>, AbiRevisionFileError> {
38    match read_abi_revision(dir, path).await {
39        Ok(abi) => Ok(Some(abi)),
40        Err(AbiRevisionFileError::Open(OpenError::OpenError(Status::NOT_FOUND))) => Ok(None),
41        Err(e) => Err(e),
42    }
43}
44
45// TODO(https://fxbug.dev/42063073): return fuchsia.version.AbiRevision & use decode_persistent().
46/// Read an ABI revision value from the given file path.
47async fn read_abi_revision(
48    dir: &fio::DirectoryProxy,
49    path: &str,
50) -> Result<AbiRevision, AbiRevisionFileError> {
51    let file = fuchsia_fs::directory::open_file(&dir, path, fio::PERM_READABLE).await?;
52    let bytes: [u8; 8] = fuchsia_fs::file::read(&file)
53        .await?
54        .try_into()
55        .map_err(|_| AbiRevisionFileError::Decode)?;
56    Ok(AbiRevision::from_bytes(bytes))
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use fuchsia_fs::directory::open_in_namespace;
63    use vfs::file::vmo::read_only;
64    use vfs::pseudo_directory;
65
66    fn init_fuchsia_abi_dir(filename: &'static str, content: &'static [u8]) -> fio::DirectoryProxy {
67        let dir = pseudo_directory! {
68        "meta" => pseudo_directory! {
69              "fuchsia.abi" => pseudo_directory! {
70                filename => read_only(content),
71              }
72          }
73        };
74        vfs::directory::serve_read_only(dir, vfs::execution_scope::ExecutionScope::new())
75    }
76
77    const ABI_REV_MAX: &'static [u8] = &u64::MAX.to_le_bytes();
78    const ABI_REV_ZERO: &'static [u8] = &0u64.to_le_bytes();
79
80    #[fuchsia::test]
81    async fn test_read_abi_revision_impl() -> Result<(), AbiRevisionFileError> {
82        // Test input that cannot be decoded into a u64 fails
83        let dir = init_fuchsia_abi_dir("abi-revision", b"Invalid ABI revision string");
84        let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await;
85        assert!(matches!(res.unwrap_err(), AbiRevisionFileError::Decode));
86
87        let dir = init_fuchsia_abi_dir("abi-revision", b"");
88        let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await;
89        assert!(matches!(res.unwrap_err(), AbiRevisionFileError::Decode));
90
91        // Test u64 inputs can be read
92        let dir = init_fuchsia_abi_dir("abi-revision", ABI_REV_MAX);
93        let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap();
94        assert_eq!(res, Some(u64::MAX.into()));
95
96        let dir = init_fuchsia_abi_dir("abi-revision", ABI_REV_ZERO);
97        let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap();
98        assert_eq!(res, Some(0u64.into()));
99
100        Ok(())
101    }
102
103    #[fuchsia::test]
104    async fn test_read_abi_revision_optional_allows_absent_file() -> Result<(), AbiRevisionFileError>
105    {
106        // Test abi-revision file not found produces Ok(None)
107        let dir = init_fuchsia_abi_dir("abi-revision-staging", ABI_REV_MAX);
108        let res = read_abi_revision_optional(&dir, AbiRevision::PATH).await.unwrap();
109        assert_eq!(res, None);
110
111        Ok(())
112    }
113
114    #[fuchsia::test]
115    async fn test_read_abi_revision_fails_absent_file() -> Result<(), AbiRevisionFileError> {
116        let dir = init_fuchsia_abi_dir("a-different-file", ABI_REV_MAX);
117        let err = read_abi_revision(&dir, AbiRevision::PATH).await.unwrap_err();
118        assert!(matches!(err, AbiRevisionFileError::Open(OpenError::OpenError(Status::NOT_FOUND))));
119        Ok(())
120    }
121
122    // Read this test package's ABI revision.
123    #[fuchsia::test]
124    async fn read_test_pkg_abi_revision() -> Result<(), AbiRevisionFileError> {
125        let dir_proxy = open_in_namespace("/pkg", fio::PERM_READABLE).unwrap();
126        let abi_revision = read_abi_revision(&dir_proxy, AbiRevision::PATH)
127            .await
128            .expect("test package doesn't contain an ABI revision");
129        version_history_data::HISTORY
130            .check_abi_revision_for_runtime(abi_revision)
131            .expect("test package ABI revision should be valid");
132        Ok(())
133    }
134}