1use std::borrow::Cow;
8use {fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem, zx_status as zxs};
9
10pub 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 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 let bytes = fuchsia_fs::file::read(&file).await?;
39 Ok(fmem::Data::Bytes(bytes))
40 }
41 }
42}
43
44#[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
57pub 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#[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 "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 #[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 #[fuchsia::test]
131 async fn bytes_from_channel_fallback() {
132 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}