Skip to main content

io_conformance_util/
lib.rs

1// Copyright 2020 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.
4
5#![warn(missing_docs)]
6
7//! Crate to provide fidl logging and test setup helpers for conformance tests
8//! for fuchsia.io.
9
10use async_trait::async_trait;
11use fidl::endpoints::{ClientEnd, ProtocolMarker, Proxy, create_proxy};
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_io_test as io_test;
14use futures::TryStreamExt as _;
15
16/// Test harness helper struct.
17pub mod test_harness;
18
19/// Utility functions for getting combinations of flags.
20pub mod flags;
21
22/// A common name for a file to create in a conformance test.
23pub const TEST_FILE: &str = "testing.txt";
24
25/// A common set of file contents to write into a test file in a conformance test.
26pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
27
28/// A common name for a symlink to create in a conformance test.
29pub const TEST_SYMLINK: &str = "testing_symlink";
30
31/// A common symlink target path to use in a conformance test.
32pub const TEST_SYMLINK_TARGET: &[u8] = b"symlink_target_path";
33
34/// A default value for NodeAttributes, with zeros set for all fields.
35pub const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes {
36    mode: 0,
37    id: 0,
38    content_size: 0,
39    storage_size: 0,
40    link_count: 0,
41    creation_time: 0,
42    modification_time: 0,
43};
44
45/// Converts a generic [`fio::NodeProxy`] to either [`fio::FileProxy`] or [`fio::DirectoryProxy`].
46/// **WARNING**: This function does _not_ verify that the conversion is valid.
47pub fn convert_node_proxy<T: Proxy>(proxy: fio::NodeProxy) -> T {
48    T::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel"))
49}
50
51/// Helper function to call `get_token` on a directory. Only use this if testing something
52/// other than the `get_token` call directly.
53pub async fn get_token(dir: &fio::DirectoryProxy) -> fidl::NullableHandle {
54    let (status, token) = dir.get_token().await.expect("get_token failed");
55    assert_eq!(status, zx::sys::ZX_OK);
56    token.expect("handle missing")
57}
58
59/// Helper function to read a file and return its contents. Only use this if testing something other
60/// than the read call directly.
61pub async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> {
62    let file =
63        dir.open_node::<fio::FileMarker>(path, fio::Flags::PERM_READ_BYTES, None).await.unwrap();
64    file.read(100)
65        .await
66        .expect("read failed")
67        .map_err(zx::Status::err_from_raw)
68        .expect("read error")
69}
70
71/// Returns the .name field from a given DirectoryEntry, otherwise panics.
72pub fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String {
73    use io_test::DirectoryEntry;
74    match dir_entry {
75        DirectoryEntry::Directory(entry) => &entry.name,
76        DirectoryEntry::RemoteDirectory(entry) => &entry.name,
77        DirectoryEntry::File(entry) => &entry.name,
78        DirectoryEntry::Symlink(entry) => &entry.name,
79        DirectoryEntry::ExecutableFile(entry) => &entry.name,
80    }
81    .clone()
82}
83
84/// Asserts that the given `vmo_rights` align with the `expected_vmo_rights` passed to a
85/// get_backing_memory call. We check that the returned rights align with and do not exceed those
86/// in the given flags, that we have at least basic VMO rights, and that the flags align with the
87/// expected sharing mode.
88pub fn validate_vmo_rights(vmo: &zx::Vmo, expected_vmo_rights: fio::VmoFlags) {
89    let vmo_rights: zx::Rights = vmo.basic_info().expect("failed to get VMO info").rights;
90
91    // Ensure that we have at least some basic rights.
92    assert!(vmo_rights.contains(zx::Rights::BASIC));
93    assert!(vmo_rights.contains(zx::Rights::MAP));
94    assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY));
95
96    // Ensure the returned rights match and do not exceed those we requested in `expected_vmo_rights`.
97    assert!(
98        vmo_rights.contains(zx::Rights::READ) == expected_vmo_rights.contains(fio::VmoFlags::READ)
99    );
100    assert!(
101        vmo_rights.contains(zx::Rights::WRITE)
102            == expected_vmo_rights.contains(fio::VmoFlags::WRITE)
103    );
104    assert!(
105        vmo_rights.contains(zx::Rights::EXECUTE)
106            == expected_vmo_rights.contains(fio::VmoFlags::EXECUTE)
107    );
108
109    // Make sure we get SET_PROPERTY if we specified a private copy.
110    if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) {
111        assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
112    }
113}
114
115/// Creates a directory with the given DirectoryEntry, opening the file with the given
116/// file flags, and returning a Buffer object initialized with the given vmo_flags.
117pub async fn create_file_and_get_backing_memory(
118    dir_entry: io_test::DirectoryEntry,
119    test_harness: &test_harness::TestHarness,
120    file_flags: fio::Flags,
121    vmo_flags: fio::VmoFlags,
122) -> Result<(zx::Vmo, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> {
123    let file_path = get_directory_entry_name(&dir_entry);
124    let dir_proxy =
125        test_harness.get_directory(vec![dir_entry], test_harness.dir_rights.all_flags());
126    let file_proxy = dir_proxy.open_node::<fio::FileMarker>(&file_path, file_flags, None).await?;
127    let vmo = file_proxy
128        .get_backing_memory(vmo_flags)
129        .await
130        .expect("get_backing_memory failed")
131        .map_err(zx::Status::err_from_raw)?;
132    Ok((vmo, (dir_proxy, file_proxy)))
133}
134
135/// Makes a directory with a name and set of entries.
136pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
137    let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
138        entries.into_iter().map(|e| Some(Box::new(e))).collect();
139    io_test::DirectoryEntry::Directory(io_test::Directory { name: name.to_string(), entries })
140}
141
142/// Makes a remote directory with a name, which forwards the requests to the given directory proxy.
143pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
144    let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
145        remote_dir.into_channel().unwrap().into_zx_channel(),
146    );
147
148    io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
149        name: name.to_string(),
150        remote_client,
151    })
152}
153
154/// Makes a file to be placed in the test directory.
155pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
156    io_test::DirectoryEntry::File(io_test::File { name: name.to_string(), contents })
157}
158
159/// Makes an executable file to be placed in the test directory.
160pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
161    io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
162}
163
164/// Makes a symlink to be placed in the test directory.
165pub fn symlink(name: &str, target: &[u8]) -> io_test::DirectoryEntry {
166    io_test::DirectoryEntry::Symlink(io_test::Symlink {
167        name: name.to_string(),
168        target: target.to_vec(),
169    })
170}
171
172/// Extension trait for [`fio::DirectoryProxy`] to make interactions with the fuchsia.io protocol
173/// less verbose.
174#[async_trait]
175pub trait DirectoryProxyExt {
176    /// Open `path` specified using `flags` and `options`, returning a proxy to the remote resource.
177    ///
178    /// Waits for [`fio::NodeEvent::OnRepresentation`] if [`fio::Flags::FLAG_SEND_REPRESENTATION`]
179    /// is specified, otherwise calls `fuchsia.io/Node.GetAttributes` to verify the result.
180    async fn open_node<T: ProtocolMarker>(
181        &self,
182        path: &str,
183        flags: fio::Flags,
184        options: Option<fio::Options>,
185    ) -> Result<T::Proxy, zx::Status>;
186
187    /// Similar to [`DirectoryProxyExt::open_node`], but waits for and returns the
188    /// [`fio::NodeEvent::OnRepresentation`] event sent when opening a resource.
189    ///
190    /// Requires [`fio::Flags::FLAG_SEND_REPRESENTATION`] to be specified in `flags`.
191    async fn open_node_repr<T: ProtocolMarker>(
192        &self,
193        path: &str,
194        flags: fio::Flags,
195        options: Option<fio::Options>,
196    ) -> Result<(T::Proxy, fio::Representation), zx::Status>;
197}
198
199#[async_trait]
200impl DirectoryProxyExt for fio::DirectoryProxy {
201    async fn open_node<T: ProtocolMarker>(
202        &self,
203        path: &str,
204        flags: fio::Flags,
205        options: Option<fio::Options>,
206    ) -> Result<T::Proxy, zx::Status> {
207        open_node_impl::<T>(self, path, flags, options).await.map(|(proxy, _representation)| proxy)
208    }
209
210    async fn open_node_repr<T: ProtocolMarker>(
211        &self,
212        path: &str,
213        flags: fio::Flags,
214        options: Option<fio::Options>,
215    ) -> Result<(T::Proxy, fio::Representation), zx::Status> {
216        assert!(
217            flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION),
218            "flags must specify the FLAG_SEND_REPRESENTATION flag to use this function!"
219        );
220        let (proxy, representation) = open_node_impl::<T>(self, path, flags, options).await?;
221        Ok((proxy, representation.unwrap()))
222    }
223}
224
225async fn open_node_impl<T: ProtocolMarker>(
226    dir: &fio::DirectoryProxy,
227    path: &str,
228    flags: fio::Flags,
229    options: Option<fio::Options>,
230) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
231    let (proxy, server) = create_proxy::<fio::NodeMarker>();
232    dir.open(path, flags, &options.unwrap_or_default(), server.into_channel())
233        .expect("Failed to call open3");
234    let representation = if flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION) {
235        Some(get_on_representation_event(&proxy).await?)
236    } else {
237        // We use GetAttributes to test that opening the resource succeeded.
238        let _ = proxy.get_attributes(Default::default()).await.map_err(|e| {
239            if let fidl::Error::ClientChannelClosed { epitaph, .. } = e {
240                match epitaph.into() {
241                    Err(s) => s,
242                    Ok(()) => zx::Status::PEER_CLOSED,
243                }
244            } else {
245                panic!("Unhandled FIDL error: {:?}", e);
246            }
247        })?;
248        None
249    };
250    Ok((convert_node_proxy(proxy), representation))
251}
252
253/// Wait for and return a [`fio::NodeEvent::OnRepresentation`] event sent via `node_proxy`.
254async fn get_on_representation_event(
255    node_proxy: &fio::NodeProxy,
256) -> Result<fio::Representation, zx::Status> {
257    // Try to extract the expected NodeEvent, but map channel epitaphs to zx::Status.
258    let event = Clone::clone(node_proxy)
259        .take_event_stream()
260        .try_next()
261        .await
262        .map_err(|e| {
263            if let fidl::Error::ClientChannelClosed { epitaph, .. } = e {
264                match epitaph.into() {
265                    Err(s) => s,
266                    Ok(()) => zx::Status::PEER_CLOSED,
267                }
268            } else {
269                panic!("Unhandled FIDL error: {:?}", e);
270            }
271        })?
272        .expect("Missing NodeEvent in stream!");
273    let representation = match event {
274        fio::NodeEvent::OnRepresentation { payload } => payload,
275        _ => panic!("Found unexpected NodeEvent type in stream!"),
276    };
277    Ok(representation)
278}