mem_util/
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.
4
5//! Utilities for working with the `fuchsia.mem` FIDL library.
6
7use std::borrow::Cow;
8use {fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem, zx_status as zxs};
9
10/// Open `path` from given `parent` directory, returning an [`fmem::Data`] of the contents.
11///
12/// Prioritizes returning an [`fmem::Data::Buffer`] if it can be done by reusing a VMO handle
13/// from the directory's server.
14pub async fn open_file_data(
15    parent: &fio::DirectoryProxy,
16    path: &str,
17) -> Result<fmem::Data, FileError> {
18    let file = fuchsia_fs::directory::open_file_async(parent, path, fio::PERM_READABLE)?;
19    match file
20        .get_backing_memory(fio::VmoFlags::READ)
21        .await
22        .map_err(|e| {
23            // Don't swallow the root cause of the error without a trace. It may
24            // be impossible to correlate resulting error to its root cause
25            // otherwise.
26            log::debug!("error for path={}: {}:", path, e);
27            FileError::GetBufferError(e)
28        })?
29        .map_err(zxs::Status::from_raw)
30    {
31        Ok(vmo) => {
32            let size = vmo.get_content_size().expect("failed to get VMO size");
33            Ok(fmem::Data::Buffer(fmem::Buffer { vmo, size }))
34        }
35        Err(e) => {
36            let _: zxs::Status = e;
37            // we still didn't get a VMO handle, fallback to reads over the channel
38            let bytes = fuchsia_fs::file::read(&file).await?;
39            Ok(fmem::Data::Bytes(bytes))
40        }
41    }
42}
43
44/// Errors that can occur when operating on `DirectoryProxy`s and `FileProxy`s.
45#[derive(Debug, thiserror::Error)]
46pub enum FileError {
47    #[error("Failed to open a File.")]
48    OpenError(#[from] fuchsia_fs::node::OpenError),
49
50    #[error("Couldn't read a file")]
51    ReadError(#[from] fuchsia_fs::file::ReadError),
52
53    #[error("FIDL call to retrieve a file's buffer failed")]
54    GetBufferError(#[source] fidl::Error),
55}
56
57/// Retrieve the bytes in `data`, returning a reference if it's a `Data::Bytes` and a copy of
58/// the bytes read from the VMO if it's a `Data::Buffer`.
59pub fn bytes_from_data(data: &fmem::Data) -> Result<Cow<'_, [u8]>, DataError> {
60    Ok(match data {
61        fmem::Data::Buffer(buf) => {
62            let size = buf.size as usize;
63            let mut raw_bytes = vec![0; size];
64            buf.vmo.read(&mut raw_bytes, 0).map_err(DataError::VmoReadError)?;
65            Cow::Owned(raw_bytes)
66        }
67        fmem::Data::Bytes(b) => Cow::Borrowed(b),
68        fmem::DataUnknown!() => return Err(DataError::UnrecognizedDataVariant),
69    })
70}
71
72/// Errors that can occur when operating on `fuchsia.mem.Data` values.
73#[derive(Debug, thiserror::Error, PartialEq, Eq)]
74pub enum DataError {
75    #[error("Couldn't read from VMO")]
76    VmoReadError(#[source] zxs::Status),
77
78    #[error("Encountered an unrecognized variant of fuchsia.mem.Data")]
79    UnrecognizedDataVariant,
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use fidl::endpoints::{create_proxy, ServerEnd};
86    use futures::StreamExt;
87    use std::sync::Arc;
88    use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
89    use vfs::directory::entry_container::Directory;
90    use vfs::execution_scope::ExecutionScope;
91    use vfs::file::vmo::read_only;
92    use vfs::file::{FileLike, FileOptions};
93    use vfs::object_request::Representation;
94    use vfs::{pseudo_directory, ObjectRequestRef};
95    use zx_status::Status;
96
97    #[fuchsia::test]
98    async fn bytes_from_read_only() {
99        let fs = pseudo_directory! {
100            // `read_only` is a vmo file, returns the buffer in OnOpen
101            "foo" => read_only("hello, world!"),
102        };
103        let directory = serve_vfs_dir(fs);
104
105        let data = open_file_data(&directory, "foo").await.unwrap();
106        match bytes_from_data(&data).unwrap() {
107            Cow::Owned(b) => assert_eq!(b, b"hello, world!"),
108            _ => panic!("must produce an owned value from reading contents of fmem::Data::Buffer"),
109        }
110    }
111
112    /// Test that we get a VMO when the server supports `File/GetBackingMemory`.
113    #[fuchsia::test]
114    async fn bytes_from_vmo_from_get_buffer() {
115        let vmo_data = b"hello, world!";
116        let fs = pseudo_directory! {
117            "foo" => read_only(vmo_data),
118        };
119        let directory = serve_vfs_dir(fs);
120
121        let data = open_file_data(&directory, "foo").await.unwrap();
122        match bytes_from_data(&data).unwrap() {
123            Cow::Owned(b) => assert_eq!(b, vmo_data),
124            _ => panic!("must produce an owned value from reading contents of fmem::Data::Buffer"),
125        }
126    }
127
128    /// Test that we correctly fall back to reading through FIDL calls in a channel if the server
129    /// doesn't support returning a VMO.
130    #[fuchsia::test]
131    async fn bytes_from_channel_fallback() {
132        // This test File does not handle `File/GetBackingMemory` request, but will return
133        // b"hello, world!" on File/Read`.
134        struct NonVMOTestFile;
135
136        impl DirectoryEntry for NonVMOTestFile {
137            fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
138                request.open_file(self)
139            }
140        }
141
142        impl GetEntryInfo for NonVMOTestFile {
143            fn entry_info(&self) -> EntryInfo {
144                EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::File)
145            }
146        }
147
148        impl vfs::node::Node for NonVMOTestFile {
149            async fn get_attributes(
150                &self,
151                _requested_attributes: fio::NodeAttributesQuery,
152            ) -> Result<fio::NodeAttributes2, Status> {
153                Err(Status::NOT_SUPPORTED)
154            }
155        }
156
157        impl FileLike for NonVMOTestFile {
158            fn open(
159                self: Arc<Self>,
160                scope: ExecutionScope,
161                _options: FileOptions,
162                object_request: ObjectRequestRef<'_>,
163            ) -> Result<(), Status> {
164                struct Connection;
165                impl Representation for Connection {
166                    type Protocol = fio::FileMarker;
167
168                    async fn get_representation(
169                        &self,
170                        _requested_attributes: fio::NodeAttributesQuery,
171                    ) -> Result<fio::Representation, Status> {
172                        unreachable!()
173                    }
174
175                    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
176                        unreachable!()
177                    }
178                }
179                let connection = Connection;
180                let object_request = object_request.take();
181                scope.spawn(async move {
182                    if let Ok(mut file_requests) =
183                        object_request.into_request_stream(&connection).await
184                    {
185                        let mut have_sent_bytes = false;
186                        while let Some(Ok(request)) = file_requests.next().await {
187                            match request {
188                                fio::FileRequest::GetBackingMemory { flags: _, responder } => {
189                                    responder.send(Err(Status::NOT_SUPPORTED.into_raw())).unwrap()
190                                }
191                                fio::FileRequest::Read { count: _, responder } => {
192                                    let to_send: &[u8] = if !have_sent_bytes {
193                                        have_sent_bytes = true;
194                                        b"hello, world!"
195                                    } else {
196                                        &[]
197                                    };
198                                    responder.send(Ok(to_send)).unwrap();
199                                }
200                                unexpected => unimplemented!("{:#?}", unexpected),
201                            }
202                        }
203                    }
204                });
205                Ok(())
206            }
207        }
208
209        let fs = pseudo_directory! {
210            "foo" => Arc::new(NonVMOTestFile),
211        };
212        let directory = serve_vfs_dir(fs);
213
214        let data = open_file_data(&directory, "foo").await.unwrap();
215        let data = bytes_from_data(&data).unwrap();
216        assert_eq!(
217            data,
218            Cow::Borrowed(b"hello, world!"),
219            "must produce a borrowed value from fmem::Data::Bytes"
220        );
221    }
222
223    fn serve_vfs_dir(root: Arc<impl Directory>) -> fio::DirectoryProxy {
224        let fs_scope = ExecutionScope::new();
225        let (client, server) = create_proxy::<fio::DirectoryMarker>();
226        root.open(
227            fs_scope.clone(),
228            fio::OpenFlags::RIGHT_READABLE,
229            vfs::path::Path::dot(),
230            ServerEnd::new(server.into_channel()),
231        );
232        client
233    }
234}