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