Skip to main content

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 fidl_fuchsia_io as fio;
8use fidl_fuchsia_mem as fmem;
9use std::borrow::Cow;
10use zx_status as zxs;
11
12/// Open `path` from given `parent` directory, returning an [`fmem::Data`] of the contents.
13///
14/// Prioritizes returning an [`fmem::Data::Buffer`] if it can be done by reusing a VMO handle
15/// from the directory's server.
16pub async fn open_file_data(
17    parent: &fio::DirectoryProxy,
18    path: &str,
19) -> Result<fmem::Data, FileError> {
20    let file = fuchsia_fs::directory::open_file_async(parent, path, fio::PERM_READABLE)?;
21    match file
22        .get_backing_memory(fio::VmoFlags::READ)
23        .await
24        .map_err(|e| {
25            // Don't swallow the root cause of the error without a trace. It may
26            // be impossible to correlate resulting error to its root cause
27            // otherwise.
28            log::debug!("error for path={}: {}:", path, e);
29            FileError::GetBufferError(e)
30        })?
31        .map_err(zxs::Status::from_raw)
32    {
33        Ok(vmo) => {
34            let size = vmo.get_content_size().expect("failed to get VMO size");
35            Ok(fmem::Data::Buffer(fmem::Buffer { vmo, size }))
36        }
37        Err(e) => {
38            let _: zxs::Status = e;
39            // we still didn't get a VMO handle, fallback to reads over the channel
40            let bytes = fuchsia_fs::file::read(&file).await?;
41            Ok(fmem::Data::Bytes(bytes))
42        }
43    }
44}
45
46/// Errors that can occur when operating on `DirectoryProxy`s and `FileProxy`s.
47#[derive(Debug, thiserror::Error)]
48pub enum FileError {
49    #[error("Failed to open a File.")]
50    OpenError(#[from] fuchsia_fs::node::OpenError),
51
52    #[error("Couldn't read a file")]
53    ReadError(#[from] fuchsia_fs::file::ReadError),
54
55    #[error("FIDL call to retrieve a file's buffer failed")]
56    GetBufferError(#[source] fidl::Error),
57}
58
59/// Retrieve the bytes in `data`, returning a reference if it's a `Data::Bytes` and a copy of
60/// the bytes read from the VMO if it's a `Data::Buffer`.
61pub fn bytes_from_data(data: &fmem::Data) -> Result<Cow<'_, [u8]>, DataError> {
62    Ok(match data {
63        fmem::Data::Buffer(buf) => {
64            let size = buf.size as usize;
65            let mut raw_bytes = vec![0; size];
66            buf.vmo.read(&mut raw_bytes, 0).map_err(DataError::VmoReadError)?;
67            Cow::Owned(raw_bytes)
68        }
69        fmem::Data::Bytes(b) => Cow::Borrowed(b),
70        fmem::DataUnknown!() => return Err(DataError::UnrecognizedDataVariant),
71    })
72}
73
74/// Errors that can occur when operating on `fuchsia.mem.Data` values.
75#[derive(Debug, thiserror::Error, PartialEq, Eq)]
76pub enum DataError {
77    #[error("Couldn't read from VMO")]
78    VmoReadError(#[source] zxs::Status),
79
80    #[error("Encountered an unrecognized variant of fuchsia.mem.Data")]
81    UnrecognizedDataVariant,
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use futures::StreamExt;
88    use std::sync::Arc;
89    use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
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::{ObjectRequestRef, pseudo_directory};
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 = vfs::directory::serve_read_only(fs, ExecutionScope::new());
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 = vfs::directory::serve_read_only(fs, ExecutionScope::new());
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 = vfs::directory::serve_read_only(fs, ExecutionScope::new());
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}