1use crate::node::{CloseError, OpenError};
8use fidl::{Persistable, persist, unpersist};
9use flex_fuchsia_io as fio;
10use thiserror::Error;
11
12mod async_reader;
13pub use async_reader::AsyncReader;
14
15mod async_read_at;
16pub use async_read_at::{Adapter, AsyncFile, AsyncGetSize, AsyncGetSizeExt, AsyncReadAt};
17mod async_read_at_ext;
18pub use async_read_at_ext::AsyncReadAtExt;
19mod buffered_async_read_at;
20pub use buffered_async_read_at::BufferedAsyncReadAt;
21
22#[cfg(target_os = "fuchsia")]
23#[cfg(not(feature = "fdomain"))]
24pub use fuchsia::*;
25
26#[cfg(target_os = "fuchsia")]
27#[cfg(not(feature = "fdomain"))]
28mod fuchsia {
29 use super::*;
30 use crate::node::{Kind, take_on_open_event};
31
32 #[derive(Debug, Error)]
34 #[error("error reading '{path}': {source}")]
35 pub struct ReadNamedError {
36 pub(super) path: String,
37
38 #[source]
39 pub(super) source: ReadError,
40 }
41
42 impl ReadNamedError {
43 pub fn path(&self) -> &str {
45 &self.path
46 }
47
48 pub fn into_inner(self) -> ReadError {
50 self.source
51 }
52
53 pub fn is_not_found_error(&self) -> bool {
55 self.source.is_not_found_error()
56 }
57 }
58
59 #[derive(Debug, Error)]
61 #[error("error writing '{path}': {source}")]
62 pub struct WriteNamedError {
63 pub(super) path: String,
64
65 #[source]
66 pub(super) source: WriteError,
67 }
68
69 impl WriteNamedError {
70 pub fn path(&self) -> &str {
72 &self.path
73 }
74
75 pub fn into_inner(self) -> WriteError {
77 self.source
78 }
79 }
80
81 pub fn open_in_namespace(path: &str, flags: fio::Flags) -> Result<fio::FileProxy, OpenError> {
91 let (node, request) = fidl::endpoints::create_proxy();
92 open_channel_in_namespace(path, flags, request)?;
93 Ok(node)
94 }
95
96 pub fn open_channel_in_namespace(
106 path: &str,
107 flags: fio::Flags,
108 request: fidl::endpoints::ServerEnd<fio::FileMarker>,
109 ) -> Result<(), OpenError> {
110 let flags = flags | fio::Flags::PROTOCOL_FILE;
111 let namespace = fdio::Namespace::installed().map_err(OpenError::Namespace)?;
112 namespace.open(path, flags, request.into_channel()).map_err(OpenError::Namespace)
113 }
114
115 pub async fn write_in_namespace<D>(path: &str, data: D) -> Result<(), WriteNamedError>
120 where
121 D: AsRef<[u8]>,
122 {
123 async {
124 let flags =
125 fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::FILE_TRUNCATE | fio::PERM_WRITABLE;
126 let file = open_in_namespace(path, flags)?;
127
128 write(&file, data).await?;
129
130 let _ = close(file).await;
131 Ok(())
132 }
133 .await
134 .map_err(|source| WriteNamedError { path: path.to_owned(), source })
135 }
136
137 pub async fn write_fidl_in_namespace<T: Persistable>(
142 path: &str,
143 data: &mut T,
144 ) -> Result<(), WriteNamedError> {
145 let data = persist(data)
146 .map_err(|source| WriteNamedError { path: path.to_owned(), source: source.into() })?;
147 write_in_namespace(path, data).await?;
148 Ok(())
149 }
150
151 pub async fn read_in_namespace(path: &str) -> Result<Vec<u8>, ReadNamedError> {
154 async {
155 let file = open_in_namespace(
156 path,
157 fio::Flags::FLAG_SEND_REPRESENTATION
158 | fio::PERM_READABLE
159 | fio::Flags::PROTOCOL_FILE,
160 )?;
161 read_file_with_on_open_event(file).await
162 }
163 .await
164 .map_err(|source| ReadNamedError { path: path.to_owned(), source })
165 }
166
167 pub async fn read_in_namespace_to_string(path: &str) -> Result<String, ReadNamedError> {
170 let bytes = read_in_namespace(path).await?;
171 let string = String::from_utf8(bytes)
172 .map_err(|source| ReadNamedError { path: path.to_owned(), source: source.into() })?;
173 Ok(string)
174 }
175
176 pub async fn read_in_namespace_to_fidl<T: Persistable>(
182 path: &str,
183 ) -> Result<T, ReadNamedError> {
184 let bytes = read_in_namespace(path).await?;
185 unpersist(&bytes)
186 .map_err(|source| ReadNamedError { path: path.to_owned(), source: source.into() })
187 }
188
189 pub(super) fn extract_stream_from_on_open_event(
191 event: fio::FileEvent,
192 ) -> Result<Option<zx::Stream>, OpenError> {
193 match event {
194 #[cfg(any(
195 fuchsia_api_level_at_least = "PLATFORM",
196 not(fuchsia_api_level_at_least = "NEXT")
197 ))]
198 fio::FileEvent::OnOpen_ { s: status, info } => {
199 zx::Status::ok(status).map_err(OpenError::OpenError)?;
200 let node_info = info.ok_or(OpenError::MissingOnOpenInfo)?;
201 match *node_info {
202 fio::NodeInfoDeprecated::File(file_info) => Ok(file_info.stream),
203 node_info @ _ => Err(OpenError::UnexpectedNodeKind {
204 expected: Kind::File,
205 actual: Kind::kind_of(&node_info),
206 }),
207 }
208 }
209 fio::FileEvent::OnRepresentation { payload } => match payload {
210 fio::Representation::File(file_info) => Ok(file_info.stream),
211 representation @ _ => Err(OpenError::UnexpectedNodeKind {
212 expected: Kind::File,
213 actual: Kind::kind_of2(&representation),
214 }),
215 },
216 fio::FileEvent::_UnknownEvent { ordinal, .. } => {
217 Err(OpenError::UnknownEvent { ordinal })
218 }
219 }
220 }
221
222 pub(super) fn read_contents_of_stream(stream: zx::Stream) -> Result<Vec<u8>, ReadError> {
224 let file_size =
226 stream.seek(std::io::SeekFrom::End(0)).map_err(ReadError::ReadError)? as usize;
227 let mut data = Vec::with_capacity(file_size);
228 let mut remaining = file_size;
229 while remaining > 0 {
230 let actual = stream
234 .read_at_uninit(
235 zx::StreamReadOptions::empty(),
236 data.len() as u64,
237 &mut data.spare_capacity_mut()[0..remaining],
238 )
239 .map_err(ReadError::ReadError)?;
240 if actual == 0 {
243 break;
244 }
245 unsafe { data.set_len(data.len() + actual) };
247 remaining -= actual;
248 }
249 Ok(data)
250 }
251
252 #[cfg(fuchsia_api_level_at_least = "HEAD")]
254 pub(super) fn write_to_stream(stream: zx::Stream, contents: &[u8]) -> Result<(), WriteError> {
255 let mut offset: usize = 0;
256 while offset < contents.len() {
257 let actual = stream
258 .write(zx::StreamWriteOptions::empty(), &contents[offset..])
259 .map_err(WriteError::WriteError)?;
260 debug_assert!(actual > 0);
263 offset += actual;
264 }
265 Ok(())
266 }
267
268 pub(crate) async fn read_file_with_on_open_event(
271 file: fio::FileProxy,
272 ) -> Result<Vec<u8>, ReadError> {
273 let event = take_on_open_event(&file).await.map_err(ReadError::Open)?;
274 let stream = extract_stream_from_on_open_event(event).map_err(ReadError::Open)?;
275
276 if let Some(stream) = stream {
277 read_contents_of_stream(stream)
278 } else {
279 read(&file).await
281 }
282 }
283
284 #[cfg(fuchsia_api_level_at_least = "HEAD")]
287 pub(crate) async fn write_file_with_on_open_event(
288 file: &fio::FileProxy,
289 contents: &[u8],
290 ) -> Result<(), WriteError> {
291 let event = take_on_open_event(file).await.map_err(WriteError::Open)?;
292 let stream = extract_stream_from_on_open_event(event).map_err(WriteError::Open)?;
293
294 if let Some(stream) = stream {
295 write_to_stream(stream, contents)
296 } else {
297 write(file, contents).await
299 }
300 }
301}
302
303#[derive(Debug, Error)]
305#[allow(missing_docs)]
306pub enum ReadError {
307 #[error("while opening the file: {0:?}")]
308 Open(#[from] OpenError),
309
310 #[error("read call failed: {0:?}")]
311 Fidl(#[from] fidl::Error),
312
313 #[error("read failed with status: {0}")]
314 ReadError(#[source] zx_status::Status),
315
316 #[error("file was not a utf-8 encoded string: {0}")]
317 InvalidUtf8(#[from] std::string::FromUtf8Error),
318}
319
320impl ReadError {
321 pub fn is_not_found_error(&self) -> bool {
323 matches!(self, ReadError::Open(e) if e.is_not_found_error())
324 }
325}
326
327#[derive(Debug, Error)]
329#[allow(missing_docs)]
330pub enum WriteError {
331 #[error("while opening the file: {0}")]
332 Open(#[from] OpenError),
333
334 #[error("while linking the file: {0}")]
335 Link(#[from] zx_status::Status),
336
337 #[error("while renaming the file: {0}")]
338 #[cfg(fuchsia_api_level_at_least = "HEAD")]
339 Rename(#[from] crate::node::RenameError),
340
341 #[error("write call failed: {0}")]
342 Fidl(#[from] fidl::Error),
343
344 #[error("write failed with status: {0}")]
345 WriteError(#[source] zx_status::Status),
346
347 #[error("file endpoint reported more bytes written than were provided")]
348 Overwrite,
349}
350
351pub async fn close(file: fio::FileProxy) -> Result<(), CloseError> {
353 let result = file.close().await.map_err(CloseError::SendCloseRequest)?;
354 result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
355}
356
357pub async fn write<D>(file: &fio::FileProxy, data: D) -> Result<(), WriteError>
359where
360 D: AsRef<[u8]>,
361{
362 let mut data = data.as_ref();
363
364 while !data.is_empty() {
365 let bytes_written = file
366 .write(&data[..std::cmp::min(fio::MAX_BUF as usize, data.len())])
367 .await?
368 .map_err(|s| WriteError::WriteError(zx_status::Status::from_raw(s)))?;
369
370 if bytes_written > data.len() as u64 {
371 return Err(WriteError::Overwrite);
372 }
373
374 data = &data[bytes_written as usize..];
375 }
376 Ok(())
377}
378
379pub async fn write_fidl<T: Persistable>(
381 file: &fio::FileProxy,
382 data: &mut T,
383) -> Result<(), WriteError> {
384 write(file, persist(data)?).await?;
385 Ok(())
386}
387
388pub async fn read(file: &fio::FileProxy) -> Result<Vec<u8>, ReadError> {
390 let mut out = Vec::new();
391
392 loop {
393 let mut bytes = file
394 .read(fio::MAX_BUF)
395 .await?
396 .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
397 if bytes.is_empty() {
398 break;
399 }
400 out.append(&mut bytes);
401 }
402 Ok(out)
403}
404
405pub async fn read_num_bytes(file: &fio::FileProxy, num_bytes: u64) -> Result<Vec<u8>, ReadError> {
408 let mut data = vec![];
409
410 let mut bytes_left = num_bytes;
413 while bytes_left > 0 {
414 let bytes_to_read = std::cmp::min(bytes_left, fio::MAX_BUF);
415 let mut bytes = file
416 .read(bytes_to_read)
417 .await?
418 .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
419
420 if bytes.is_empty() {
421 break;
422 }
423
424 bytes_left -= bytes.len() as u64;
425 data.append(&mut bytes);
426 }
427
428 let num_bytes = num_bytes as usize;
430 if data.len() > num_bytes {
431 data.drain(num_bytes..data.len());
432 }
433
434 Ok(data)
435}
436
437pub async fn read_to_string(file: &fio::FileProxy) -> Result<String, ReadError> {
439 let bytes = read(file).await?;
440 let string = String::from_utf8(bytes)?;
441 Ok(string)
442}
443
444pub async fn read_fidl<T: Persistable>(file: &fio::FileProxy) -> Result<T, ReadError> {
449 let bytes = read(file).await?;
450 Ok(unpersist(&bytes)?)
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::directory;
457 use crate::node::{Kind, take_on_open_event};
458 use assert_matches::assert_matches;
459 use fidl_fidl_test_schema::{DataTable1, DataTable2};
460 use fuchsia_async as fasync;
461 use std::path::Path;
462 use std::sync::Arc;
463 use tempfile::TempDir;
464 use vfs::ToObjectRequest;
465 use vfs::execution_scope::ExecutionScope;
466 use vfs::file::vmo::{VmoFile, read_only};
467
468 const DATA_FILE_CONTENTS: &str = "Hello World!\n";
469
470 #[fasync::run_singlethreaded(test)]
473 async fn open_in_namespace_opens_real_file() {
474 let exists = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
475 assert_matches!(close(exists).await, Ok(()));
476 }
477
478 #[fasync::run_singlethreaded(test)]
479 async fn open_in_namespace_opens_fake_file_under_of_root_namespace_entry() {
480 let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
481 assert_matches!(close(notfound).await, Err(_));
483 }
484
485 #[fasync::run_singlethreaded(test)]
486 async fn open_in_namespace_rejects_fake_root_namespace_entry() {
487 assert_matches!(
488 open_in_namespace("/fake", fio::PERM_READABLE),
489 Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
490 );
491 }
492
493 #[fasync::run_singlethreaded(test)]
496 async fn write_in_namespace_creates_file() {
497 let tempdir = TempDir::new().unwrap();
498 let path = tempdir.path().join(Path::new("new-file")).to_str().unwrap().to_owned();
499
500 let data = b"\x80"; write_in_namespace(&path, data).await.unwrap();
503
504 let contents = std::fs::read(&path).unwrap();
506 assert_eq!(&contents, &data);
507 }
508
509 #[fasync::run_singlethreaded(test)]
510 async fn write_in_namespace_overwrites_existing_file() {
511 let tempdir = TempDir::new().unwrap();
512 let path = tempdir.path().join(Path::new("existing-file")).to_str().unwrap().to_owned();
513
514 let original_data = b"\x80\x81"; write_in_namespace(&path, original_data).await.unwrap();
517
518 let new_data = b"\x82"; write_in_namespace(&path, new_data).await.unwrap();
521
522 let contents = std::fs::read(&path).unwrap();
524 assert_eq!(&contents, &new_data);
525 }
526
527 #[fasync::run_singlethreaded(test)]
528 async fn write_in_namespace_fails_on_invalid_namespace_entry() {
529 assert_matches!(
530 write_in_namespace("/fake", b"").await,
531 Err(WriteNamedError { path, source: WriteError::Open(_) }) if path == "/fake"
532 );
533 let err = write_in_namespace("/fake", b"").await.unwrap_err();
534 assert_eq!(err.path(), "/fake");
535 assert_matches!(err.into_inner(), WriteError::Open(_));
536 }
537
538 #[fasync::run_singlethreaded(test)]
541 async fn write_writes_to_file() {
542 let tempdir = TempDir::new().unwrap();
543 let dir = directory::open_in_namespace(
544 tempdir.path().to_str().unwrap(),
545 fio::PERM_READABLE | fio::PERM_WRITABLE,
546 )
547 .unwrap();
548
549 let file =
551 directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
552 .await
553 .unwrap();
554 let data = b"\x80"; write(&file, data).await.unwrap();
556
557 let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
559 assert_eq!(&contents, &data);
560 }
561
562 #[fasync::run_singlethreaded(test)]
563 async fn write_writes_to_file_in_chunks_if_needed() {
564 let tempdir = TempDir::new().unwrap();
565 let dir = directory::open_in_namespace(
566 tempdir.path().to_str().unwrap(),
567 fio::PERM_READABLE | fio::PERM_WRITABLE,
568 )
569 .unwrap();
570
571 let file =
573 directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
574 .await
575 .unwrap();
576 let data = "abc".repeat(10000);
577 write(&file, &data).await.unwrap();
578
579 let contents = std::fs::read_to_string(tempdir.path().join(Path::new("file"))).unwrap();
581 assert_eq!(&contents, &data);
582 }
583
584 #[fasync::run_singlethreaded(test)]
585 async fn write_appends_to_file() {
586 let tempdir = TempDir::new().unwrap();
587 let dir = directory::open_in_namespace(
588 tempdir.path().to_str().unwrap(),
589 fio::PERM_READABLE | fio::PERM_WRITABLE,
590 )
591 .unwrap();
592
593 let file =
595 directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
596 .await
597 .unwrap();
598 write(&file, "Hello ").await.unwrap();
599 write(&file, "World!\n").await.unwrap();
600 close(file).await.unwrap();
601
602 let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
604 assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
605 }
606
607 #[fasync::run_singlethreaded(test)]
608 async fn write_stream() {
609 let tempdir = TempDir::new().unwrap();
610 let dir = directory::open_in_namespace(
611 tempdir.path().to_str().unwrap(),
612 fio::PERM_READABLE | fio::PERM_WRITABLE,
613 )
614 .unwrap();
615
616 let file =
618 directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
619 .await
620 .unwrap();
621 write(&file, "Hello ").await.unwrap();
622 write(&file, "World!\n").await.unwrap();
623 close(file).await.unwrap();
624
625 let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
627 assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
628 }
629
630 #[fasync::run_singlethreaded(test)]
633 async fn read_reads_to_end_of_file() {
634 let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
635
636 let contents = read(&file).await.unwrap();
637 assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
638 }
639
640 #[fasync::run_singlethreaded(test)]
641 async fn read_reads_from_current_position() {
642 let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
643
644 let _: Vec<u8> = file.read(1).await.unwrap().unwrap();
646
647 let contents = read(&file).await.unwrap();
649 assert_eq!(&contents[..], "ello World!\n".as_bytes());
650 }
651
652 #[fasync::run_singlethreaded(test)]
655 async fn read_in_namespace_reads_contents() {
656 let contents = read_in_namespace("/pkg/data/file").await.unwrap();
657 assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
658 }
659
660 #[fasync::run_singlethreaded(test)]
661 async fn read_in_namespace_fails_on_invalid_namespace_entry() {
662 assert_matches!(
663 read_in_namespace("/fake").await,
664 Err(ReadNamedError { path, source: ReadError::Open(_) }) if path == "/fake"
665 );
666 let err = read_in_namespace("/fake").await.unwrap_err();
667 assert_eq!(err.path(), "/fake");
668 assert_matches!(err.into_inner(), ReadError::Open(_));
669 }
670
671 #[fasync::run_singlethreaded(test)]
674 async fn read_to_string_reads_data_file() {
675 let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
676 assert_eq!(read_to_string(&file).await.unwrap(), DATA_FILE_CONTENTS);
677 }
678
679 #[fasync::run_singlethreaded(test)]
682 async fn read_in_namespace_to_string_reads_data_file() {
683 assert_eq!(
684 read_in_namespace_to_string("/pkg/data/file").await.unwrap(),
685 DATA_FILE_CONTENTS
686 );
687 }
688
689 #[fasync::run_singlethreaded(test)]
692 async fn write_fidl_writes_to_file() {
693 let tempdir = TempDir::new().unwrap();
694 let dir = directory::open_in_namespace(
695 tempdir.path().to_str().unwrap(),
696 fio::PERM_READABLE | fio::PERM_WRITABLE,
697 )
698 .unwrap();
699
700 let file =
702 directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
703 .await
704 .unwrap();
705
706 let mut data = DataTable1 {
707 num: Some(42),
708 string: Some(DATA_FILE_CONTENTS.to_string()),
709 ..Default::default()
710 };
711
712 let fidl_bytes = persist(&data).unwrap();
714
715 write_fidl(&file, &mut data).await.unwrap();
716
717 let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
719 assert_eq!(&contents, &fidl_bytes);
720 }
721
722 #[fasync::run_singlethreaded(test)]
723 async fn read_fidl_reads_from_file() {
724 let file = open_in_namespace("/pkg/data/fidl_file", fio::PERM_READABLE).unwrap();
725
726 let contents = read_fidl::<DataTable2>(&file).await.unwrap();
727
728 let data = DataTable2 {
729 num: Some(42),
730 string: Some(DATA_FILE_CONTENTS.to_string()),
731 new_field: None,
732 ..Default::default()
733 };
734 assert_eq!(&contents, &data);
735 }
736
737 #[test]
738 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
739 fn extract_stream_from_on_open_event_with_stream() {
740 let vmo = zx::Vmo::create(0).unwrap();
741 let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
742 let event = fio::FileEvent::OnOpen_ {
743 s: 0,
744 info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
745 stream: Some(stream),
746 event: None,
747 }))),
748 };
749 let stream = extract_stream_from_on_open_event(event)
750 .expect("Not a file")
751 .expect("Stream not present");
752 assert!(!stream.is_invalid());
753 }
754
755 #[test]
756 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
757 fn extract_stream_from_on_open_event_without_stream() {
758 let event = fio::FileEvent::OnOpen_ {
759 s: 0,
760 info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
761 stream: None,
762 event: None,
763 }))),
764 };
765 let stream = extract_stream_from_on_open_event(event).expect("Not a file");
766 assert!(stream.is_none());
767 }
768
769 #[test]
770 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
771 fn extract_stream_from_on_open_event_with_open_error() {
772 let event = fio::FileEvent::OnOpen_ { s: zx::Status::NOT_FOUND.into_raw(), info: None };
773 let result = extract_stream_from_on_open_event(event);
774 assert_matches!(result, Err(OpenError::OpenError(zx::Status::NOT_FOUND)));
775 }
776
777 #[test]
778 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
779 fn extract_stream_from_on_open_event_not_a_file() {
780 let event = fio::FileEvent::OnOpen_ {
781 s: 0,
782 info: Some(Box::new(fio::NodeInfoDeprecated::Service(fio::Service))),
783 };
784 let result = extract_stream_from_on_open_event(event);
785 assert_matches!(
786 result,
787 Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Service })
788 );
789 }
790
791 #[test]
792 fn extract_stream_from_on_representation_event_with_stream() {
793 let vmo = zx::Vmo::create(0).unwrap();
794 let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
795 let event = fio::FileEvent::OnRepresentation {
796 payload: fio::Representation::File(fio::FileInfo {
797 stream: Some(stream),
798 ..Default::default()
799 }),
800 };
801 let stream = extract_stream_from_on_open_event(event)
802 .expect("Not a file")
803 .expect("Stream not present");
804 assert!(!stream.is_invalid());
805 }
806
807 #[test]
808 fn extract_stream_from_on_representation_event_without_stream() {
809 let event = fio::FileEvent::OnRepresentation {
810 payload: fio::Representation::File(fio::FileInfo::default()),
811 };
812 let stream = extract_stream_from_on_open_event(event).expect("Not a file");
813 assert!(stream.is_none());
814 }
815
816 #[test]
817 fn extract_stream_from_on_representation_event_not_a_file() {
818 let event = fio::FileEvent::OnRepresentation {
819 payload: fio::Representation::Directory(Default::default()),
820 };
821 let result = extract_stream_from_on_open_event(event);
822 assert_matches!(
823 result,
824 Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Directory })
825 );
826 }
827
828 #[test]
829 fn read_contents_of_stream_with_contents() {
830 let data = b"file-contents".repeat(1000);
831 let vmo = zx::Vmo::create(data.len() as u64).unwrap();
832 vmo.write(&data, 0).unwrap();
833 let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
834 let contents = read_contents_of_stream(stream).unwrap();
835 assert_eq!(contents, data);
836 }
837
838 #[test]
839 fn read_contents_of_stream_with_empty_stream() {
840 let vmo = zx::Vmo::create(0).unwrap();
841 let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
842 let contents = read_contents_of_stream(stream).unwrap();
843 assert!(contents.is_empty());
844 }
845
846 fn serve_file(file: Arc<VmoFile>, flags: fio::Flags) -> fio::FileProxy {
847 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
848 flags.to_object_request(server_end).handle(|object_request| {
849 vfs::file::serve(file, ExecutionScope::new(), &flags, object_request)
850 });
851 proxy
852 }
853
854 #[fasync::run_singlethreaded(test)]
855 async fn read_file_with_on_open_event_with_stream() {
856 let data = b"file-contents".repeat(1000);
857 let vmo_file = read_only(&data);
858 const FLAGS: fio::Flags = fio::PERM_READABLE.union(fio::Flags::FLAG_SEND_REPRESENTATION);
859
860 {
861 let file = serve_file(vmo_file.clone(), FLAGS);
863 let event = take_on_open_event(&file).await.unwrap();
864 extract_stream_from_on_open_event(event).unwrap().expect("Stream not present");
865 }
866
867 let file = serve_file(vmo_file.clone(), FLAGS);
868 let contents = read_file_with_on_open_event(file).await.unwrap();
869 assert_eq!(contents, data);
870 }
871
872 #[fasync::run_singlethreaded(test)]
873 async fn read_missing_file_in_namespace() {
874 assert_matches!(
875 read_in_namespace("/pkg/data/missing").await,
876 Err(e) if e.is_not_found_error()
877 );
878 }
879
880 #[test]
881 fn write_to_stream_with_contents() {
882 let data = b"file-contents".repeat(1000);
883 let vmo = zx::Vmo::create(data.len() as u64).unwrap();
884 let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
885 write_to_stream(stream, &data).unwrap();
886
887 let mut read_back = vec![0; data.len()];
888 vmo.read(&mut read_back, 0).unwrap();
889 assert_eq!(read_back, data);
890 }
891
892 #[test]
893 fn write_to_stream_empty() {
894 let data = b"";
895 let vmo = zx::Vmo::create(0).unwrap();
896 let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
897 write_to_stream(stream, data).unwrap();
898 assert_eq!(vmo.get_size().unwrap(), 0);
899 }
900}