1use fidl_fuchsia_io as fio;
8use fidl_fuchsia_mem as fmem;
9use std::borrow::Cow;
10use zx_status as zxs;
11
12pub 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 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 let bytes = fuchsia_fs::file::read(&file).await?;
41 Ok(fmem::Data::Bytes(bytes))
42 }
43 }
44}
45
46#[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
59pub 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#[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 "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 #[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 #[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 = 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}