use fidl_fuchsia_io as fio;
use fidl_fuchsia_mem as fmem;
use fuchsia_zircon_status as zxs;
use std::borrow::Cow;
pub async fn open_file_data(
parent: &fio::DirectoryProxy,
path: &str,
) -> Result<fmem::Data, FileError> {
let file =
fuchsia_fs::directory::open_file_no_describe(parent, path, fio::OpenFlags::RIGHT_READABLE)?;
match file
.get_backing_memory(fio::VmoFlags::READ)
.await
.map_err(|e| {
tracing::debug!("error for path={}: {}:", path, e);
FileError::GetBufferError(e)
})?
.map_err(zxs::Status::from_raw)
{
Ok(vmo) => {
let size = vmo.get_content_size().expect("failed to get VMO size");
Ok(fmem::Data::Buffer(fmem::Buffer { vmo, size }))
}
Err(e) => {
let _: zxs::Status = e;
let bytes = fuchsia_fs::file::read(&file).await?;
Ok(fmem::Data::Bytes(bytes))
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum FileError {
#[error("Failed to open a File.")]
OpenError(#[from] fuchsia_fs::node::OpenError),
#[error("Couldn't read a file")]
ReadError(#[from] fuchsia_fs::file::ReadError),
#[error("FIDL call to retrieve a file's buffer failed")]
GetBufferError(#[source] fidl::Error),
}
pub fn bytes_from_data<'d>(data: &'d fmem::Data) -> Result<Cow<'d, [u8]>, DataError> {
Ok(match data {
fmem::Data::Buffer(buf) => {
let size = buf.size as usize;
let mut raw_bytes = Vec::with_capacity(size);
raw_bytes.resize(size, 0);
buf.vmo.read(&mut raw_bytes, 0).map_err(DataError::VmoReadError)?;
Cow::Owned(raw_bytes)
}
fmem::Data::Bytes(b) => Cow::Borrowed(b),
fmem::DataUnknown!() => return Err(DataError::UnrecognizedDataVariant),
})
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum DataError {
#[error("Couldn't read from VMO")]
VmoReadError(#[source] zxs::Status),
#[error("Encountered an unrecognized variant of fuchsia.mem.Data")]
UnrecognizedDataVariant,
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::{create_proxy, ServerEnd};
use futures::StreamExt;
use std::sync::Arc;
use vfs::{
directory::entry::DirectoryEntry,
execution_scope::ExecutionScope,
file::vmo::read_only,
pseudo_directory,
remote::{remote_boxed_with_type, RoutingFn},
};
#[fuchsia::test]
async fn bytes_from_read_only() {
let fs = pseudo_directory! {
"foo" => read_only("hello, world!"),
};
let directory = serve_vfs_dir(fs);
let data = open_file_data(&directory, "foo").await.unwrap();
match bytes_from_data(&data).unwrap() {
Cow::Owned(b) => assert_eq!(b, b"hello, world!"),
_ => panic!("must produce an owned value from reading contents of fmem::Data::Buffer"),
}
}
#[fuchsia::test]
async fn bytes_from_vmo_from_get_buffer() {
let channel_only_foo: RoutingFn = Box::new(|scope, _flags, _path, server_end| {
let server_end: ServerEnd<fio::FileMarker> = ServerEnd::new(server_end.into_channel());
let (mut file_requests, control) = server_end.into_stream_and_control_handle().unwrap();
control
.send_on_open_(
zxs::Status::OK.into_raw(),
Some(fio::NodeInfoDeprecated::File(fio::FileObject {
event: None,
stream: None,
})),
)
.unwrap();
scope.spawn(async move {
while let Some(Ok(request)) = file_requests.next().await {
let vmo = fidl::Vmo::create(13).unwrap();
vmo.write(b"hello, world!", 0).unwrap();
match request {
fio::FileRequest::GetBackingMemory { flags: _, responder } => {
responder.send(Ok(vmo)).unwrap()
}
unexpected => unimplemented!("{:#?}", unexpected),
}
}
});
});
let fs = pseudo_directory! {
"foo" => remote_boxed_with_type(channel_only_foo, fio::DirentType::File),
};
let directory = serve_vfs_dir(fs);
let data = open_file_data(&directory, "foo").await.unwrap();
match bytes_from_data(&data).unwrap() {
Cow::Owned(b) => assert_eq!(b, b"hello, world!"),
_ => panic!("must produce an owned value from reading contents of fmem::Data::Buffer"),
}
}
#[fuchsia::test]
async fn bytes_from_channel_fallback() {
let channel_only_foo: RoutingFn = Box::new(|scope, _flags, _path, server_end| {
let server_end: ServerEnd<fio::FileMarker> = ServerEnd::new(server_end.into_channel());
let (mut file_requests, control) = server_end.into_stream_and_control_handle().unwrap();
control
.send_on_open_(
zxs::Status::OK.into_raw(),
Some(fio::NodeInfoDeprecated::File(fio::FileObject {
event: None,
stream: None,
})),
)
.unwrap();
scope.spawn(async move {
let mut have_sent_bytes = false;
while let Some(Ok(request)) = file_requests.next().await {
match request {
fio::FileRequest::GetBackingMemory { flags: _, responder } => {
responder.send(Err(zxs::Status::NOT_SUPPORTED.into_raw())).unwrap()
}
fio::FileRequest::Read { count: _, responder } => {
let to_send: &[u8] = if !have_sent_bytes {
have_sent_bytes = true;
b"hello, world!"
} else {
&[]
};
responder.send(Ok(to_send)).unwrap();
}
unexpected => unimplemented!("{:#?}", unexpected),
}
}
});
});
let fs = pseudo_directory! {
"foo" => remote_boxed_with_type(channel_only_foo, fio::DirentType::File),
};
let directory = serve_vfs_dir(fs);
let data = open_file_data(&directory, "foo").await.unwrap();
let data = bytes_from_data(&data).unwrap();
assert_eq!(
data,
Cow::Borrowed(b"hello, world!"),
"must produce a borrowed value from fmem::Data::Bytes"
);
}
fn serve_vfs_dir(root: Arc<impl DirectoryEntry>) -> fio::DirectoryProxy {
let fs_scope = ExecutionScope::new();
let (client, server) = create_proxy::<fio::DirectoryMarker>().unwrap();
root.open(
fs_scope.clone(),
fio::OpenFlags::RIGHT_READABLE,
vfs::path::Path::dot(),
ServerEnd::new(server.into_channel()),
);
client
}
}