1use crate::common::{
6 decode_extended_attribute_value, encode_extended_attribute_value, extended_attributes_sender,
7 io1_to_io2_attrs,
8};
9use crate::execution_scope::ExecutionScope;
10use crate::file::common::new_connection_validate_options;
11use crate::file::{File, FileIo, FileOptions, RawFileIoConnection, SyncMode};
12use crate::name::parse_name;
13use crate::node::OpenNode;
14use crate::object_request::{
15 ConnectionCreator, ObjectRequest, Representation, run_synchronous_future_or_spawn,
16};
17use crate::protocols::ToFileOptions;
18use crate::request_handler::{RequestHandler, RequestListener};
19use crate::{ObjectRequestRef, ProtocolsExt};
20use anyhow::Error;
21use fidl::endpoints::{DiscoverableProtocolMarker as _, ServerEnd};
22use fidl_fuchsia_io as fio;
23use static_assertions::assert_eq_size;
24use std::convert::TryInto as _;
25use std::future::Future;
26use std::ops::{ControlFlow, Deref, DerefMut};
27use std::pin::Pin;
28use std::sync::Arc;
29use storage_trace::{self as trace, TraceFutureExt};
30use zx_status::Status;
31
32#[cfg(target_os = "fuchsia")]
33use {
34 crate::file::common::get_backing_memory_validate_flags,
35 crate::temp_clone::{TempClonable, unblock},
36 std::io::SeekFrom,
37 zx::{self as zx, HandleBased},
38};
39
40async fn create_connection<
42 T: 'static + File,
43 U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin,
44>(
45 scope: ExecutionScope,
46 file: U,
47 options: FileOptions,
48 object_request: ObjectRequestRef<'_>,
49) -> Result<(), Status> {
50 new_connection_validate_options(&options, file.readable(), file.writable(), file.executable())?;
51
52 file.open_file(&options).await?;
53 if object_request.truncate {
54 file.truncate(0).await?;
55 }
56
57 let connection = FileConnection { scope: scope.clone(), file, options };
58 if let Ok(requests) = object_request.take().into_request_stream(&connection).await {
59 scope.spawn(RequestListener::new(requests, Some(connection)));
60 }
61 Ok(())
62}
63
64trait IoOpHandler: Send + Sync + Sized + 'static {
66 fn read(&mut self, count: u64) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
69
70 fn read_at(
72 &self,
73 offset: u64,
74 count: u64,
75 ) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
76
77 fn write(&mut self, data: Vec<u8>) -> impl Future<Output = Result<u64, Status>> + Send;
81
82 fn write_at(
84 &self,
85 offset: u64,
86 data: Vec<u8>,
87 ) -> impl Future<Output = Result<u64, Status>> + Send;
88
89 fn seek(
91 &mut self,
92 offset: i64,
93 origin: fio::SeekOrigin,
94 ) -> impl Future<Output = Result<u64, Status>> + Send;
95
96 fn set_flags(&mut self, flags: fio::Flags) -> Result<(), Status>;
98
99 #[cfg(target_os = "fuchsia")]
102 fn duplicate_stream(&self) -> Result<Option<zx::Stream>, Status>;
103
104 fn clone_connection(&self, options: FileOptions) -> Result<Self, Status>;
106}
107
108pub struct FidlIoConnection<T: 'static + File> {
111 file: OpenNode<T>,
113
114 seek: u64,
126
127 is_append: bool,
129}
130
131impl<T: 'static + File> Deref for FidlIoConnection<T> {
132 type Target = OpenNode<T>;
133
134 fn deref(&self) -> &Self::Target {
135 &self.file
136 }
137}
138
139impl<T: 'static + File> DerefMut for FidlIoConnection<T> {
140 fn deref_mut(&mut self) -> &mut Self::Target {
141 &mut self.file
142 }
143}
144
145impl<T: 'static + File + FileIo> FidlIoConnection<T> {
146 pub async fn create(
152 scope: ExecutionScope,
153 file: Arc<T>,
154 options: impl ToFileOptions,
155 object_request: ObjectRequestRef<'_>,
156 ) -> Result<(), Status> {
157 let file = OpenNode::new(file);
158 let options = options.to_file_options()?;
159 create_connection(
160 scope,
161 FidlIoConnection { file, seek: 0, is_append: options.is_append },
162 options,
163 object_request,
164 )
165 .await
166 }
167
168 pub fn create_sync(
171 scope: ExecutionScope,
172 file: Arc<T>,
173 options: impl ToFileOptions,
174 object_request: ObjectRequest,
175 ) {
176 run_synchronous_future_or_spawn(
177 scope.clone(),
178 object_request.handle_async(async |object_request| {
179 Self::create(scope, file, options, object_request).await
180 }),
181 )
182 }
183}
184
185impl<T: 'static + File + FileIo> ConnectionCreator<T> for FidlIoConnection<T> {
186 async fn create<'a>(
187 scope: ExecutionScope,
188 node: Arc<T>,
189 protocols: impl ProtocolsExt,
190 object_request: ObjectRequestRef<'a>,
191 ) -> Result<(), Status> {
192 Self::create(scope, node, protocols, object_request).await
193 }
194}
195
196impl<T: 'static + File + FileIo> IoOpHandler for FidlIoConnection<T> {
197 async fn read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
198 let buffer = self.read_at(self.seek, count).await?;
199 let count: u64 = buffer.len().try_into().unwrap();
200 self.seek += count;
201 Ok(buffer)
202 }
203
204 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
205 let mut buffer = vec![0u8; count as usize];
206 let count = self.file.read_at(offset, &mut buffer[..]).await?;
207 buffer.truncate(count.try_into().unwrap());
208 Ok(buffer)
209 }
210
211 async fn write(&mut self, data: Vec<u8>) -> Result<u64, Status> {
212 if self.is_append {
213 let (bytes, offset) = self.file.append(&data).await?;
214 self.seek = offset;
215 Ok(bytes)
216 } else {
217 let actual = self.write_at(self.seek, data).await?;
218 self.seek += actual;
219 Ok(actual)
220 }
221 }
222
223 async fn write_at(&self, offset: u64, data: Vec<u8>) -> Result<u64, Status> {
224 self.file.write_at(offset, &data).await
225 }
226
227 async fn seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
228 let new_seek = match origin {
230 fio::SeekOrigin::Start => offset as i128,
231 fio::SeekOrigin::Current => {
232 assert_eq_size!(usize, i64);
233 self.seek as i128 + offset as i128
234 }
235 fio::SeekOrigin::End => {
236 let size = self.file.get_size().await?;
237 assert_eq_size!(usize, i64, u64);
238 size as i128 + offset as i128
239 }
240 };
241
242 if let Ok(new_seek) = u64::try_from(new_seek) {
246 self.seek = new_seek;
247 Ok(self.seek)
248 } else {
249 Err(Status::OUT_OF_RANGE)
250 }
251 }
252
253 fn set_flags(&mut self, flags: fio::Flags) -> Result<(), Status> {
254 self.is_append = flags.intersects(fio::Flags::FILE_APPEND);
255 Ok(())
256 }
257
258 #[cfg(target_os = "fuchsia")]
259 fn duplicate_stream(&self) -> Result<Option<zx::Stream>, Status> {
260 Ok(None)
261 }
262
263 fn clone_connection(&self, options: FileOptions) -> Result<Self, Status> {
264 self.file.will_clone();
265 Ok(Self { file: OpenNode::new(self.file.clone()), seek: 0, is_append: options.is_append })
266 }
267}
268
269pub struct RawIoConnection<T: 'static + File> {
270 file: OpenNode<T>,
271}
272
273impl<T: 'static + File + RawFileIoConnection> RawIoConnection<T> {
274 pub async fn create(
275 scope: ExecutionScope,
276 file: Arc<T>,
277 options: impl ToFileOptions,
278 object_request: ObjectRequestRef<'_>,
279 ) -> Result<(), Status> {
280 let file = OpenNode::new(file);
281 create_connection(
282 scope,
283 RawIoConnection { file },
284 options.to_file_options()?,
285 object_request,
286 )
287 .await
288 }
289}
290
291impl<T: 'static + File + RawFileIoConnection> ConnectionCreator<T> for RawIoConnection<T> {
292 async fn create<'a>(
293 scope: ExecutionScope,
294 node: Arc<T>,
295 protocols: impl crate::ProtocolsExt,
296 object_request: ObjectRequestRef<'a>,
297 ) -> Result<(), Status> {
298 Self::create(scope, node, protocols, object_request).await
299 }
300}
301
302impl<T: 'static + File> Deref for RawIoConnection<T> {
303 type Target = OpenNode<T>;
304
305 fn deref(&self) -> &Self::Target {
306 &self.file
307 }
308}
309
310impl<T: 'static + File> DerefMut for RawIoConnection<T> {
311 fn deref_mut(&mut self) -> &mut Self::Target {
312 &mut self.file
313 }
314}
315
316impl<T: 'static + File + RawFileIoConnection> IoOpHandler for RawIoConnection<T> {
317 async fn read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
318 self.file.read(count).await
319 }
320
321 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
322 self.file.read_at(offset, count).await
323 }
324
325 async fn write(&mut self, data: Vec<u8>) -> Result<u64, Status> {
326 self.file.write(&data).await
327 }
328
329 async fn write_at(&self, offset: u64, data: Vec<u8>) -> Result<u64, Status> {
330 self.file.write_at(offset, &data).await
331 }
332
333 async fn seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
334 self.file.seek(offset, origin).await
335 }
336
337 fn set_flags(&mut self, flags: fio::Flags) -> Result<(), Status> {
338 self.file.set_flags(flags)
339 }
340
341 #[cfg(target_os = "fuchsia")]
342 fn duplicate_stream(&self) -> Result<Option<zx::Stream>, Status> {
343 Ok(None)
344 }
345
346 fn clone_connection(&self, _options: FileOptions) -> Result<Self, Status> {
347 self.file.will_clone();
348 Ok(Self { file: OpenNode::new(self.file.clone()) })
349 }
350}
351
352#[cfg(target_os = "fuchsia")]
353mod stream_io {
354 use super::*;
355 pub trait GetVmo {
356 const PAGER_ON_FIDL_EXECUTOR: bool = false;
362
363 fn get_vmo(&self) -> &zx::Vmo;
365 }
366
367 pub struct StreamIoConnection<T: 'static + File + GetVmo> {
370 file: OpenNode<T>,
372
373 stream: TempClonable<zx::Stream>,
375 }
376
377 impl<T: 'static + File + GetVmo> Deref for StreamIoConnection<T> {
378 type Target = OpenNode<T>;
379
380 fn deref(&self) -> &Self::Target {
381 &self.file
382 }
383 }
384
385 impl<T: 'static + File + GetVmo> DerefMut for StreamIoConnection<T> {
386 fn deref_mut(&mut self) -> &mut Self::Target {
387 &mut self.file
388 }
389 }
390
391 impl<T: 'static + File + GetVmo> StreamIoConnection<T> {
392 pub async fn create(
397 scope: ExecutionScope,
398 file: Arc<T>,
399 options: impl ToFileOptions,
400 object_request: ObjectRequestRef<'_>,
401 ) -> Result<(), Status> {
402 let file = OpenNode::new(file);
403 let options = options.to_file_options()?;
404 let stream = TempClonable::new(zx::Stream::create(
405 options.to_stream_options(),
406 file.get_vmo(),
407 0,
408 )?);
409 create_connection(scope, StreamIoConnection { file, stream }, options, object_request)
410 .await
411 }
412
413 pub fn create_sync(
416 scope: ExecutionScope,
417 file: Arc<T>,
418 options: impl ToFileOptions,
419 object_request: ObjectRequest,
420 ) {
421 run_synchronous_future_or_spawn(
422 scope.clone(),
423 object_request.handle_async(async |object_request| {
424 Self::create(scope, file, options, object_request).await
425 }),
426 )
427 }
428
429 async fn maybe_unblock<F, R>(&self, f: F) -> R
430 where
431 F: FnOnce(&zx::Stream) -> R + Send + 'static,
432 R: Send + 'static,
433 {
434 if T::PAGER_ON_FIDL_EXECUTOR {
435 let stream = self.stream.temp_clone();
436 unblock(move || f(&*stream)).await
437 } else {
438 f(&*self.stream)
439 }
440 }
441 }
442
443 impl<T: 'static + File + GetVmo> ConnectionCreator<T> for StreamIoConnection<T> {
444 async fn create<'a>(
445 scope: ExecutionScope,
446 node: Arc<T>,
447 protocols: impl crate::ProtocolsExt,
448 object_request: ObjectRequestRef<'a>,
449 ) -> Result<(), Status> {
450 Self::create(scope, node, protocols, object_request).await
451 }
452 }
453
454 impl<T: 'static + File + GetVmo> IoOpHandler for StreamIoConnection<T> {
455 async fn read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
456 self.maybe_unblock(move |stream| {
457 stream.read_to_vec(zx::StreamReadOptions::empty(), count as usize)
458 })
459 .await
460 }
461
462 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
463 self.maybe_unblock(move |stream| {
464 stream.read_at_to_vec(zx::StreamReadOptions::empty(), offset, count as usize)
465 })
466 .await
467 }
468
469 async fn write(&mut self, data: Vec<u8>) -> Result<u64, Status> {
470 self.maybe_unblock(move |stream| {
471 let actual = stream.write(zx::StreamWriteOptions::empty(), &data)?;
472 Ok(actual as u64)
473 })
474 .await
475 }
476
477 async fn write_at(&self, offset: u64, data: Vec<u8>) -> Result<u64, Status> {
478 self.maybe_unblock(move |stream| {
479 let actual = stream.write_at(zx::StreamWriteOptions::empty(), offset, &data)?;
480 Ok(actual as u64)
481 })
482 .await
483 }
484
485 async fn seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
486 let position = match origin {
487 fio::SeekOrigin::Start => {
488 if offset < 0 {
489 return Err(Status::INVALID_ARGS);
490 }
491 SeekFrom::Start(offset as u64)
492 }
493 fio::SeekOrigin::Current => SeekFrom::Current(offset),
494 fio::SeekOrigin::End => SeekFrom::End(offset),
495 };
496 self.stream.seek(position)
497 }
498
499 fn set_flags(&mut self, flags: fio::Flags) -> Result<(), Status> {
500 let append_mode = if flags.contains(fio::Flags::FILE_APPEND) { 1 } else { 0 };
501 self.stream.set_mode_append(&append_mode)
502 }
503
504 fn duplicate_stream(&self) -> Result<Option<zx::Stream>, Status> {
505 self.stream.duplicate_handle(zx::Rights::SAME_RIGHTS).map(|s| Some(s))
506 }
507
508 fn clone_connection(&self, options: FileOptions) -> Result<Self, Status> {
509 let stream = TempClonable::new(zx::Stream::create(
510 options.to_stream_options(),
511 self.file.get_vmo(),
512 0,
513 )?);
514 self.file.will_clone();
515 Ok(Self { file: OpenNode::new(self.file.clone()), stream })
516 }
517 }
518}
519
520#[cfg(target_os = "fuchsia")]
521pub use stream_io::*;
522
523enum ConnectionState {
525 Alive,
527 Closed(fio::FileCloseResponder),
530 Dropped,
533}
534
535struct FileConnection<U> {
537 scope: ExecutionScope,
540
541 file: U,
543
544 options: FileOptions,
546}
547
548impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin>
549 FileConnection<U>
550{
551 async fn handle_request(&mut self, req: fio::FileRequest) -> Result<ConnectionState, Error> {
554 match req {
555 #[cfg(any(
556 fuchsia_api_level_at_least = "PLATFORM",
557 not(fuchsia_api_level_at_least = "NEXT")
558 ))]
559 fio::FileRequest::DeprecatedClone { flags, object, control_handle: _ } => {
560 trace::duration!(c"storage", c"File::DeprecatedClone");
561 crate::common::send_on_open_with_error(
562 flags.contains(fio::OpenFlags::DESCRIBE),
563 object,
564 Status::NOT_SUPPORTED,
565 );
566 }
567 fio::FileRequest::Clone { request, control_handle: _ } => {
568 trace::duration!(c"storage", c"File::Clone");
569 self.handle_clone(ServerEnd::new(request.into_channel()));
570 }
571 fio::FileRequest::Close { responder } => {
572 return Ok(ConnectionState::Closed(responder));
573 }
574 #[cfg(not(target_os = "fuchsia"))]
575 fio::FileRequest::Describe { responder } => {
576 responder.send(fio::FileInfo { stream: None, ..Default::default() })?;
577 }
578 #[cfg(target_os = "fuchsia")]
579 fio::FileRequest::Describe { responder } => {
580 trace::duration!(c"storage", c"File::Describe");
581 let stream = self.file.duplicate_stream()?;
582 responder.send(fio::FileInfo { stream, ..Default::default() })?;
583 }
584 fio::FileRequest::LinkInto { dst_parent_token, dst, responder } => {
585 async move {
586 responder.send(
587 self.handle_link_into(dst_parent_token, dst)
588 .await
589 .map_err(Status::into_raw),
590 )
591 }
592 .trace(trace::trace_future_args!(c"storage", c"File::LinkInto"))
593 .await?;
594 }
595 fio::FileRequest::Sync { responder } => {
596 async move {
597 responder.send(self.file.sync(SyncMode::Normal).await.map_err(Status::into_raw))
598 }
599 .trace(trace::trace_future_args!(c"storage", c"File::Sync"))
600 .await?;
601 }
602 #[cfg(fuchsia_api_level_at_least = "28")]
603 fio::FileRequest::DeprecatedGetAttr { responder } => {
604 async move {
605 let (status, attrs) =
606 crate::common::io2_to_io1_attrs(self.file.as_ref(), self.options.rights)
607 .await;
608 responder.send(status.into_raw(), &attrs)
609 }
610 .trace(trace::trace_future_args!(c"storage", c"File::GetAttr"))
611 .await?;
612 }
613 #[cfg(not(fuchsia_api_level_at_least = "28"))]
614 fio::FileRequest::GetAttr { responder } => {
615 async move {
616 let (status, attrs) =
617 crate::common::io2_to_io1_attrs(self.file.as_ref(), self.options.rights)
618 .await;
619 responder.send(status.into_raw(), &attrs)
620 }
621 .trace(trace::trace_future_args!(c"storage", c"File::GetAttr"))
622 .await?;
623 }
624 #[cfg(fuchsia_api_level_at_least = "28")]
625 fio::FileRequest::DeprecatedSetAttr { flags, attributes, responder } => {
626 async move {
627 let result =
628 self.handle_update_attributes(io1_to_io2_attrs(flags, attributes)).await;
629 responder.send(Status::from_result(result).into_raw())
630 }
631 .trace(trace::trace_future_args!(c"storage", c"File::SetAttr"))
632 .await?;
633 }
634 #[cfg(not(fuchsia_api_level_at_least = "28"))]
635 fio::FileRequest::SetAttr { flags, attributes, responder } => {
636 async move {
637 let result =
638 self.handle_update_attributes(io1_to_io2_attrs(flags, attributes)).await;
639 responder.send(Status::from_result(result).into_raw())
640 }
641 .trace(trace::trace_future_args!(c"storage", c"File::SetAttr"))
642 .await?;
643 }
644 fio::FileRequest::GetAttributes { query, responder } => {
645 async move {
646 let attrs = self.file.get_attributes(query).await;
648 responder.send(
649 attrs
650 .as_ref()
651 .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
652 .map_err(|status| status.into_raw()),
653 )
654 }
655 .trace(trace::trace_future_args!(c"storage", c"File::GetAttributes"))
656 .await?;
657 }
658 fio::FileRequest::UpdateAttributes { payload, responder } => {
659 async move {
660 let result =
661 self.handle_update_attributes(payload).await.map_err(Status::into_raw);
662 responder.send(result)
663 }
664 .trace(trace::trace_future_args!(c"storage", c"File::UpdateAttributes"))
665 .await?;
666 }
667 fio::FileRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
668 self.handle_list_extended_attribute(iterator)
669 .trace(trace::trace_future_args!(c"storage", c"File::ListExtendedAttributes"))
670 .await;
671 }
672 fio::FileRequest::GetExtendedAttribute { name, responder } => {
673 async move {
674 let res =
675 self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
676 responder.send(res)
677 }
678 .trace(trace::trace_future_args!(c"storage", c"File::GetExtendedAttribute"))
679 .await?;
680 }
681 fio::FileRequest::SetExtendedAttribute { name, value, mode, responder } => {
682 async move {
683 let res = self
684 .handle_set_extended_attribute(name, value, mode)
685 .await
686 .map_err(Status::into_raw);
687 responder.send(res)
688 }
689 .trace(trace::trace_future_args!(c"storage", c"File::SetExtendedAttribute"))
690 .await?;
691 }
692 fio::FileRequest::RemoveExtendedAttribute { name, responder } => {
693 async move {
694 let res =
695 self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
696 responder.send(res)
697 }
698 .trace(trace::trace_future_args!(c"storage", c"File::RemoveExtendedAttribute"))
699 .await?;
700 }
701 #[cfg(fuchsia_api_level_at_least = "HEAD")]
702 fio::FileRequest::EnableVerity { options, responder } => {
703 async move {
704 let res = self.handle_enable_verity(options).await.map_err(Status::into_raw);
705 responder.send(res)
706 }
707 .trace(trace::trace_future_args!(c"storage", c"File::EnableVerity"))
708 .await?;
709 }
710 fio::FileRequest::Read { count, responder } => {
711 let trace_args =
712 trace::trace_future_args!(c"storage", c"File::Read", "bytes" => count);
713 async move {
714 let result = self.handle_read(count).await;
715 responder.send(result.as_deref().map_err(|s| s.into_raw()))
716 }
717 .trace(trace_args)
718 .await?;
719 }
720 fio::FileRequest::ReadAt { offset, count, responder } => {
721 let trace_args = trace::trace_future_args!(
722 c"storage",
723 c"File::ReadAt",
724 "offset" => offset,
725 "bytes" => count
726 );
727 async move {
728 let result = self.handle_read_at(offset, count).await;
729 responder.send(result.as_deref().map_err(|s| s.into_raw()))
730 }
731 .trace(trace_args)
732 .await?;
733 }
734 fio::FileRequest::Write { data, responder } => {
735 let trace_args =
736 trace::trace_future_args!(c"storage", c"File::Write", "bytes" => data.len());
737 async move {
738 let result = self.handle_write(data).await;
739 responder.send(result.map_err(Status::into_raw))
740 }
741 .trace(trace_args)
742 .await?;
743 }
744 fio::FileRequest::WriteAt { offset, data, responder } => {
745 let trace_args = trace::trace_future_args!(
746 c"storage",
747 c"File::WriteAt",
748 "offset" => offset,
749 "bytes" => data.len()
750 );
751 async move {
752 let result = self.handle_write_at(offset, data).await;
753 responder.send(result.map_err(Status::into_raw))
754 }
755 .trace(trace_args)
756 .await?;
757 }
758 fio::FileRequest::Seek { origin, offset, responder } => {
759 async move {
760 let result = self.handle_seek(offset, origin).await;
761 responder.send(result.map_err(Status::into_raw))
762 }
763 .trace(trace::trace_future_args!(c"storage", c"File::Seek"))
764 .await?;
765 }
766 fio::FileRequest::Resize { length, responder } => {
767 async move {
768 let result = self.handle_truncate(length).await;
769 responder.send(result.map_err(Status::into_raw))
770 }
771 .trace(trace::trace_future_args!(c"storage", c"File::Resize"))
772 .await?;
773 }
774 #[cfg(fuchsia_api_level_at_least = "27")]
775 fio::FileRequest::GetFlags { responder } => {
776 trace::duration!(c"storage", c"File::GetFlags");
777 responder.send(Ok(fio::Flags::from(&self.options)))?;
778 }
779 #[cfg(fuchsia_api_level_at_least = "27")]
780 fio::FileRequest::SetFlags { flags, responder } => {
781 trace::duration!(c"storage", c"File::SetFlags");
782 if flags.is_empty() || flags == fio::Flags::FILE_APPEND {
784 self.options.is_append = flags.contains(fio::Flags::FILE_APPEND);
785 responder.send(self.file.set_flags(flags).map_err(Status::into_raw))?;
786 } else {
787 responder.send(Err(Status::INVALID_ARGS.into_raw()))?;
788 }
789 }
790 #[cfg(fuchsia_api_level_at_least = "27")]
791 fio::FileRequest::DeprecatedGetFlags { responder } => {
792 trace::duration!(c"storage", c"File::DeprecatedGetFlags");
793 responder.send(Status::OK.into_raw(), self.options.to_io1())?;
794 }
795 #[cfg(fuchsia_api_level_at_least = "27")]
796 fio::FileRequest::DeprecatedSetFlags { flags, responder } => {
797 trace::duration!(c"storage", c"File::DeprecatedSetFlags");
798 let is_append = flags.contains(fio::OpenFlags::APPEND);
800 self.options.is_append = is_append;
801 let flags = if is_append { fio::Flags::FILE_APPEND } else { fio::Flags::empty() };
802 responder.send(Status::from_result(self.file.set_flags(flags)).into_raw())?;
803 }
804 #[cfg(not(fuchsia_api_level_at_least = "27"))]
805 fio::FileRequest::GetFlags { responder } => {
806 trace::duration!(c"storage", c"File::GetFlags");
807 responder.send(Status::OK.into_raw(), self.options.to_io1())?;
808 }
809 #[cfg(not(fuchsia_api_level_at_least = "27"))]
810 fio::FileRequest::SetFlags { flags, responder } => {
811 trace::duration!(c"storage", c"File::SetFlags");
812 let is_append = flags.contains(fio::OpenFlags::APPEND);
814 self.options.is_append = is_append;
815 let flags = if is_append { fio::Flags::FILE_APPEND } else { fio::Flags::empty() };
816 responder.send(Status::from_result(self.file.set_flags(flags)).into_raw())?;
817 }
818 #[cfg(target_os = "fuchsia")]
819 fio::FileRequest::GetBackingMemory { flags, responder } => {
820 async move {
821 let result = self.handle_get_backing_memory(flags).await;
822 responder.send(result.map_err(Status::into_raw))
823 }
824 .trace(trace::trace_future_args!(c"storage", c"File::GetBackingMemory"))
825 .await?;
826 }
827
828 #[cfg(not(target_os = "fuchsia"))]
829 fio::FileRequest::GetBackingMemory { flags: _, responder } => {
830 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
831 }
832 fio::FileRequest::AdvisoryLock { request: _, responder } => {
833 trace::duration!(c"storage", c"File::AdvisoryLock");
834 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
835 }
836 fio::FileRequest::Query { responder } => {
837 trace::duration!(c"storage", c"File::Query");
838 responder.send(fio::FileMarker::PROTOCOL_NAME.as_bytes())?;
839 }
840 fio::FileRequest::QueryFilesystem { responder } => {
841 trace::duration!(c"storage", c"File::QueryFilesystem");
842 match self.file.query_filesystem() {
843 Err(status) => responder.send(status.into_raw(), None)?,
844 Ok(info) => responder.send(0, Some(&info))?,
845 }
846 }
847 #[cfg(fuchsia_api_level_at_least = "HEAD")]
848 fio::FileRequest::Allocate { offset, length, mode, responder } => {
849 async move {
850 let result = self.handle_allocate(offset, length, mode).await;
851 responder.send(result.map_err(Status::into_raw))
852 }
853 .trace(trace::trace_future_args!(c"storage", c"File::Allocate"))
854 .await?;
855 }
856 fio::FileRequest::_UnknownMethod { .. } => (),
857 }
858 Ok(ConnectionState::Alive)
859 }
860
861 fn handle_clone(&mut self, server_end: ServerEnd<fio::FileMarker>) {
862 let connection = match self.file.clone_connection(self.options) {
863 Ok(file) => Self { scope: self.scope.clone(), file, options: self.options },
864 Err(status) => {
865 let _ = server_end.close_with_epitaph(status);
866 return;
867 }
868 };
869 self.scope.spawn(RequestListener::new(server_end.into_stream(), Some(connection)));
870 }
871
872 async fn handle_read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
873 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
874 return Err(Status::BAD_HANDLE);
875 }
876
877 if count > fio::MAX_TRANSFER_SIZE {
878 return Err(Status::OUT_OF_RANGE);
879 }
880 self.file.read(count).await
881 }
882
883 async fn handle_read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
884 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
885 return Err(Status::BAD_HANDLE);
886 }
887 if count > fio::MAX_TRANSFER_SIZE {
888 return Err(Status::OUT_OF_RANGE);
889 }
890 self.file.read_at(offset, count).await
891 }
892
893 async fn handle_write(&mut self, content: Vec<u8>) -> Result<u64, Status> {
894 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
895 return Err(Status::BAD_HANDLE);
896 }
897 self.file.write(content).await
898 }
899
900 async fn handle_write_at(&self, offset: u64, content: Vec<u8>) -> Result<u64, Status> {
901 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
902 return Err(Status::BAD_HANDLE);
903 }
904
905 self.file.write_at(offset, content).await
906 }
907
908 async fn handle_seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
910 self.file.seek(offset, origin).await
911 }
912
913 async fn handle_update_attributes(
914 &mut self,
915 attributes: fio::MutableNodeAttributes,
916 ) -> Result<(), Status> {
917 if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
918 return Err(Status::BAD_HANDLE);
919 }
920
921 self.file.update_attributes(attributes).await
922 }
923
924 #[cfg(fuchsia_api_level_at_least = "HEAD")]
925 async fn handle_enable_verity(
926 &mut self,
927 options: fio::VerificationOptions,
928 ) -> Result<(), Status> {
929 if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
930 return Err(Status::BAD_HANDLE);
931 }
932 self.file.enable_verity(options).await
933 }
934
935 async fn handle_truncate(&mut self, length: u64) -> Result<(), Status> {
936 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
937 return Err(Status::BAD_HANDLE);
938 }
939
940 self.file.truncate(length).await
941 }
942
943 #[cfg(target_os = "fuchsia")]
944 async fn handle_get_backing_memory(&mut self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
945 get_backing_memory_validate_flags(flags, self.options)?;
946 self.file.get_backing_memory(flags).await
947 }
948
949 async fn handle_list_extended_attribute(
950 &mut self,
951 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
952 ) {
953 let attributes = match self.file.list_extended_attributes().await {
954 Ok(attributes) => attributes,
955 Err(status) => {
956 #[cfg(any(test, feature = "use_log"))]
957 log::error!(status:?; "list extended attributes failed");
958 #[allow(clippy::unnecessary_lazy_evaluations)]
959 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
960 #[cfg(any(test, feature = "use_log"))]
961 log::error!(_error:?; "failed to send epitaph")
962 });
963 return;
964 }
965 };
966 self.scope.spawn(extended_attributes_sender(iterator, attributes));
967 }
968
969 async fn handle_get_extended_attribute(
970 &mut self,
971 name: Vec<u8>,
972 ) -> Result<fio::ExtendedAttributeValue, Status> {
973 let value = self.file.get_extended_attribute(name).await?;
974 encode_extended_attribute_value(value)
975 }
976
977 async fn handle_set_extended_attribute(
978 &mut self,
979 name: Vec<u8>,
980 value: fio::ExtendedAttributeValue,
981 mode: fio::SetExtendedAttributeMode,
982 ) -> Result<(), Status> {
983 if name.contains(&0) {
984 return Err(Status::INVALID_ARGS);
985 }
986 let val = decode_extended_attribute_value(value)?;
987 self.file.set_extended_attribute(name, val, mode).await
988 }
989
990 async fn handle_remove_extended_attribute(&mut self, name: Vec<u8>) -> Result<(), Status> {
991 self.file.remove_extended_attribute(name).await
992 }
993
994 async fn handle_link_into(
995 &mut self,
996 target_parent_token: fidl::Event,
997 target_name: String,
998 ) -> Result<(), Status> {
999 let target_name = parse_name(target_name).map_err(|_| Status::INVALID_ARGS)?;
1000
1001 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1002 if !self.options.is_linkable {
1003 return Err(Status::NOT_FOUND);
1004 }
1005
1006 if !self.options.rights.contains(
1007 fio::Operations::READ_BYTES
1008 | fio::Operations::WRITE_BYTES
1009 | fio::Operations::GET_ATTRIBUTES
1010 | fio::Operations::UPDATE_ATTRIBUTES,
1011 ) {
1012 return Err(Status::ACCESS_DENIED);
1013 }
1014
1015 let target_parent = self
1016 .scope
1017 .token_registry()
1018 .get_owner(target_parent_token.into())?
1019 .ok_or(Err(Status::NOT_FOUND))?;
1020
1021 self.file.clone().link_into(target_parent, target_name).await
1022 }
1023
1024 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1025 async fn handle_allocate(
1026 &mut self,
1027 offset: u64,
1028 length: u64,
1029 mode: fio::AllocateMode,
1030 ) -> Result<(), Status> {
1031 self.file.allocate(offset, length, mode).await
1032 }
1033
1034 fn should_sync_before_close(&self) -> bool {
1035 self.options
1036 .rights
1037 .intersects(fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES)
1038 }
1039}
1040
1041impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin>
1044 RequestHandler for Option<FileConnection<U>>
1045{
1046 type Request = Result<fio::FileRequest, fidl::Error>;
1047
1048 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
1049 let option_this = self.get_mut();
1050 let this = option_this.as_mut().unwrap();
1051 let Some(_guard) = this.scope.try_active_guard() else { return ControlFlow::Break(()) };
1052 let state = match request {
1053 Ok(request) => {
1054 this.handle_request(request)
1055 .await
1056 .unwrap_or(ConnectionState::Dropped)
1059 }
1060 Err(_) => {
1061 ConnectionState::Dropped
1065 }
1066 };
1067 match state {
1068 ConnectionState::Alive => ControlFlow::Continue(()),
1069 ConnectionState::Dropped => {
1070 if this.should_sync_before_close() {
1071 let _ = this.file.sync(SyncMode::PreClose).await;
1072 }
1073 ControlFlow::Break(())
1074 }
1075 ConnectionState::Closed(responder) => {
1076 async move {
1077 let this = option_this.as_mut().unwrap();
1078 let _ = responder.send({
1079 let result = if this.should_sync_before_close() {
1080 this.file.sync(SyncMode::PreClose).await.map_err(Status::into_raw)
1081 } else {
1082 Ok(())
1083 };
1084 std::mem::drop(option_this.take());
1087 result
1088 });
1089 }
1090 .trace(trace::trace_future_args!(c"storage", c"File::Close"))
1091 .await;
1092 ControlFlow::Break(())
1093 }
1094 }
1095 }
1096
1097 async fn stream_closed(self: Pin<&mut Self>) {
1098 let this = self.get_mut().as_mut().unwrap();
1099 if this.should_sync_before_close() {
1100 if let Some(_guard) = this.scope.try_active_guard() {
1101 let _ = this.file.sync(SyncMode::PreClose).await;
1102 }
1103 }
1104 }
1105}
1106
1107impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + IoOpHandler> Representation
1108 for FileConnection<U>
1109{
1110 type Protocol = fio::FileMarker;
1111
1112 async fn get_representation(
1113 &self,
1114 requested_attributes: fio::NodeAttributesQuery,
1115 ) -> Result<fio::Representation, Status> {
1116 Ok(fio::Representation::File(fio::FileInfo {
1118 is_append: Some(self.options.is_append),
1119 #[cfg(target_os = "fuchsia")]
1120 stream: self.file.duplicate_stream()?,
1121 #[cfg(not(target_os = "fuchsia"))]
1122 stream: None,
1123 attributes: if requested_attributes.is_empty() {
1124 None
1125 } else {
1126 Some(self.file.get_attributes(requested_attributes).await?)
1127 },
1128 ..Default::default()
1129 }))
1130 }
1131
1132 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
1133 #[cfg(target_os = "fuchsia")]
1134 let stream = self.file.duplicate_stream()?;
1135 #[cfg(not(target_os = "fuchsia"))]
1136 let stream = None;
1137 Ok(fio::NodeInfoDeprecated::File(fio::FileObject { event: None, stream }))
1138 }
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143 use super::*;
1144 use crate::ToObjectRequest;
1145 use crate::directory::entry::{EntryInfo, GetEntryInfo};
1146 use crate::node::Node;
1147 use assert_matches::assert_matches;
1148 use fuchsia_sync::Mutex;
1149 use futures::prelude::*;
1150
1151 const RIGHTS_R: fio::Operations =
1152 fio::Operations::READ_BYTES.union(fio::Operations::GET_ATTRIBUTES);
1153 const RIGHTS_W: fio::Operations =
1154 fio::Operations::WRITE_BYTES.union(fio::Operations::UPDATE_ATTRIBUTES);
1155 const RIGHTS_RW: fio::Operations = fio::Operations::READ_BYTES
1156 .union(fio::Operations::WRITE_BYTES)
1157 .union(fio::Operations::GET_ATTRIBUTES)
1158 .union(fio::Operations::UPDATE_ATTRIBUTES);
1159
1160 const FLAGS_R: fio::Flags = fio::Flags::PERM_READ_BYTES.union(fio::Flags::PERM_GET_ATTRIBUTES);
1165 const FLAGS_W: fio::Flags =
1166 fio::Flags::PERM_WRITE_BYTES.union(fio::Flags::PERM_UPDATE_ATTRIBUTES);
1167 const FLAGS_RW: fio::Flags = FLAGS_R.union(FLAGS_W);
1168
1169 #[derive(Debug, PartialEq)]
1170 enum FileOperation {
1171 Init {
1172 options: FileOptions,
1173 },
1174 ReadAt {
1175 offset: u64,
1176 count: u64,
1177 },
1178 WriteAt {
1179 offset: u64,
1180 content: Vec<u8>,
1181 },
1182 Append {
1183 content: Vec<u8>,
1184 },
1185 Truncate {
1186 length: u64,
1187 },
1188 #[cfg(target_os = "fuchsia")]
1189 GetBackingMemory {
1190 flags: fio::VmoFlags,
1191 },
1192 GetSize,
1193 GetAttributes {
1194 query: fio::NodeAttributesQuery,
1195 },
1196 UpdateAttributes {
1197 attrs: fio::MutableNodeAttributes,
1198 },
1199 Close,
1200 Sync,
1201 }
1202
1203 type MockCallbackType = Box<dyn Fn(&FileOperation) -> Status + Sync + Send>;
1204 struct MockFile {
1206 operations: Mutex<Vec<FileOperation>>,
1208 callback: MockCallbackType,
1210 file_size: u64,
1212 #[cfg(target_os = "fuchsia")]
1213 vmo: zx::Vmo,
1215 }
1216
1217 const MOCK_FILE_SIZE: u64 = 256;
1218 const MOCK_FILE_ID: u64 = 10;
1219 const MOCK_FILE_LINKS: u64 = 2;
1220 const MOCK_FILE_CREATION_TIME: u64 = 10;
1221 const MOCK_FILE_MODIFICATION_TIME: u64 = 100;
1222 impl MockFile {
1223 fn new(callback: MockCallbackType) -> Arc<Self> {
1224 Arc::new(MockFile {
1225 operations: Mutex::new(Vec::new()),
1226 callback,
1227 file_size: MOCK_FILE_SIZE,
1228 #[cfg(target_os = "fuchsia")]
1229 vmo: zx::Handle::invalid().into(),
1230 })
1231 }
1232
1233 #[cfg(target_os = "fuchsia")]
1234 fn new_with_vmo(callback: MockCallbackType, vmo: zx::Vmo) -> Arc<Self> {
1235 Arc::new(MockFile {
1236 operations: Mutex::new(Vec::new()),
1237 callback,
1238 file_size: MOCK_FILE_SIZE,
1239 vmo,
1240 })
1241 }
1242
1243 fn handle_operation(&self, operation: FileOperation) -> Result<(), Status> {
1244 let result = (self.callback)(&operation);
1245 self.operations.lock().push(operation);
1246 match result {
1247 Status::OK => Ok(()),
1248 err => Err(err),
1249 }
1250 }
1251 }
1252
1253 impl GetEntryInfo for MockFile {
1254 fn entry_info(&self) -> EntryInfo {
1255 EntryInfo::new(MOCK_FILE_ID, fio::DirentType::File)
1256 }
1257 }
1258
1259 impl Node for MockFile {
1260 async fn get_attributes(
1261 &self,
1262 query: fio::NodeAttributesQuery,
1263 ) -> Result<fio::NodeAttributes2, Status> {
1264 self.handle_operation(FileOperation::GetAttributes { query })?;
1265 Ok(attributes!(
1266 query,
1267 Mutable {
1268 creation_time: MOCK_FILE_CREATION_TIME,
1269 modification_time: MOCK_FILE_MODIFICATION_TIME,
1270 },
1271 Immutable {
1272 protocols: fio::NodeProtocolKinds::FILE,
1273 abilities: fio::Operations::GET_ATTRIBUTES
1274 | fio::Operations::UPDATE_ATTRIBUTES
1275 | fio::Operations::READ_BYTES
1276 | fio::Operations::WRITE_BYTES,
1277 content_size: self.file_size,
1278 storage_size: 2 * self.file_size,
1279 link_count: MOCK_FILE_LINKS,
1280 id: MOCK_FILE_ID,
1281 }
1282 ))
1283 }
1284
1285 fn close(self: Arc<Self>) {
1286 let _ = self.handle_operation(FileOperation::Close);
1287 }
1288 }
1289
1290 impl File for MockFile {
1291 fn writable(&self) -> bool {
1292 true
1293 }
1294
1295 async fn open_file(&self, options: &FileOptions) -> Result<(), Status> {
1296 self.handle_operation(FileOperation::Init { options: *options })?;
1297 Ok(())
1298 }
1299
1300 async fn truncate(&self, length: u64) -> Result<(), Status> {
1301 self.handle_operation(FileOperation::Truncate { length })
1302 }
1303
1304 #[cfg(target_os = "fuchsia")]
1305 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
1306 self.handle_operation(FileOperation::GetBackingMemory { flags })?;
1307 Err(Status::NOT_SUPPORTED)
1308 }
1309
1310 async fn get_size(&self) -> Result<u64, Status> {
1311 self.handle_operation(FileOperation::GetSize)?;
1312 Ok(self.file_size)
1313 }
1314
1315 async fn update_attributes(&self, attrs: fio::MutableNodeAttributes) -> Result<(), Status> {
1316 self.handle_operation(FileOperation::UpdateAttributes { attrs })?;
1317 Ok(())
1318 }
1319
1320 async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
1321 self.handle_operation(FileOperation::Sync)
1322 }
1323 }
1324
1325 impl FileIo for MockFile {
1326 async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
1327 let count = buffer.len() as u64;
1328 self.handle_operation(FileOperation::ReadAt { offset, count })?;
1329
1330 let mut i = offset;
1332 buffer.fill_with(|| {
1333 let v = (i % 256) as u8;
1334 i += 1;
1335 v
1336 });
1337 Ok(count)
1338 }
1339
1340 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
1341 self.handle_operation(FileOperation::WriteAt { offset, content: content.to_vec() })?;
1342 Ok(content.len() as u64)
1343 }
1344
1345 async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
1346 self.handle_operation(FileOperation::Append { content: content.to_vec() })?;
1347 Ok((content.len() as u64, self.file_size + content.len() as u64))
1348 }
1349 }
1350
1351 #[cfg(target_os = "fuchsia")]
1352 impl GetVmo for MockFile {
1353 fn get_vmo(&self) -> &zx::Vmo {
1354 &self.vmo
1355 }
1356 }
1357
1358 fn only_allow_init(op: &FileOperation) -> Status {
1360 match op {
1361 FileOperation::Init { .. } => Status::OK,
1362 _ => Status::IO,
1363 }
1364 }
1365
1366 fn always_succeed_callback(_op: &FileOperation) -> Status {
1368 Status::OK
1369 }
1370
1371 struct TestEnv {
1372 pub file: Arc<MockFile>,
1373 pub proxy: fio::FileProxy,
1374 pub scope: ExecutionScope,
1375 }
1376
1377 fn init_mock_file(callback: MockCallbackType, flags: fio::Flags) -> TestEnv {
1378 let file = MockFile::new(callback);
1379 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
1380
1381 let scope = ExecutionScope::new();
1382
1383 flags.to_object_request(server_end).create_connection_sync::<FidlIoConnection<_>, _>(
1384 scope.clone(),
1385 file.clone(),
1386 flags,
1387 );
1388
1389 TestEnv { file, proxy, scope }
1390 }
1391
1392 #[fuchsia::test]
1393 async fn test_open_flag_truncate() {
1394 let env = init_mock_file(
1395 Box::new(always_succeed_callback),
1396 fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1397 );
1398 let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1400 let events = env.file.operations.lock();
1401 assert_eq!(
1402 *events,
1403 vec![
1404 FileOperation::Init {
1405 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1406 },
1407 FileOperation::Truncate { length: 0 },
1408 FileOperation::Sync,
1409 ]
1410 );
1411 }
1412
1413 #[fuchsia::test]
1414 async fn test_close_succeeds() {
1415 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1416 let () = env.proxy.close().await.unwrap().map_err(Status::from_raw).unwrap();
1417
1418 let events = env.file.operations.lock();
1419 assert_eq!(
1420 *events,
1421 vec![
1422 FileOperation::Init {
1423 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1424 },
1425 FileOperation::Close {},
1426 ]
1427 );
1428 }
1429
1430 #[fuchsia::test]
1431 async fn test_close_fails() {
1432 let env =
1433 init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE | fio::PERM_WRITABLE);
1434 let status = env.proxy.close().await.unwrap().map_err(Status::from_raw);
1435 assert_eq!(status, Err(Status::IO));
1436
1437 let events = env.file.operations.lock();
1438 assert_eq!(
1439 *events,
1440 vec![
1441 FileOperation::Init {
1442 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1443 },
1444 FileOperation::Sync,
1445 FileOperation::Close,
1446 ]
1447 );
1448 }
1449
1450 #[fuchsia::test]
1451 async fn test_close_called_when_dropped() {
1452 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1453 let _ = env.proxy.sync().await;
1454 std::mem::drop(env.proxy);
1455 env.scope.shutdown();
1456 env.scope.wait().await;
1457 let events = env.file.operations.lock();
1458 assert_eq!(
1459 *events,
1460 vec![
1461 FileOperation::Init {
1462 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1463 },
1464 FileOperation::Sync,
1465 FileOperation::Close,
1466 ]
1467 );
1468 }
1469
1470 #[fuchsia::test]
1471 async fn test_query() {
1472 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1473 let protocol = env.proxy.query().await.unwrap();
1474 assert_eq!(protocol, fio::FileMarker::PROTOCOL_NAME.as_bytes());
1475 }
1476
1477 #[fuchsia::test]
1478 async fn test_get_attributes() {
1479 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1480 let (mutable_attributes, immutable_attributes) = env
1481 .proxy
1482 .get_attributes(fio::NodeAttributesQuery::all())
1483 .await
1484 .unwrap()
1485 .map_err(Status::from_raw)
1486 .unwrap();
1487 let expected = attributes!(
1488 fio::NodeAttributesQuery::all(),
1489 Mutable {
1490 creation_time: MOCK_FILE_CREATION_TIME,
1491 modification_time: MOCK_FILE_MODIFICATION_TIME,
1492 },
1493 Immutable {
1494 protocols: fio::NodeProtocolKinds::FILE,
1495 abilities: fio::Operations::GET_ATTRIBUTES
1496 | fio::Operations::UPDATE_ATTRIBUTES
1497 | fio::Operations::READ_BYTES
1498 | fio::Operations::WRITE_BYTES,
1499 content_size: MOCK_FILE_SIZE,
1500 storage_size: 2 * MOCK_FILE_SIZE,
1501 link_count: MOCK_FILE_LINKS,
1502 id: MOCK_FILE_ID,
1503 }
1504 );
1505 assert_eq!(mutable_attributes, expected.mutable_attributes);
1506 assert_eq!(immutable_attributes, expected.immutable_attributes);
1507
1508 let events = env.file.operations.lock();
1509 assert_eq!(
1510 *events,
1511 vec![
1512 FileOperation::Init {
1513 options: FileOptions {
1514 rights: fio::Operations::empty(),
1515 is_append: false,
1516 is_linkable: true
1517 }
1518 },
1519 FileOperation::GetAttributes { query: fio::NodeAttributesQuery::all() }
1520 ]
1521 );
1522 }
1523
1524 #[fuchsia::test]
1525 async fn test_getbuffer() {
1526 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1527 let result = env
1528 .proxy
1529 .get_backing_memory(fio::VmoFlags::READ)
1530 .await
1531 .unwrap()
1532 .map_err(Status::from_raw);
1533 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1534 let events = env.file.operations.lock();
1535 assert_eq!(
1536 *events,
1537 vec![
1538 FileOperation::Init {
1539 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1540 },
1541 #[cfg(target_os = "fuchsia")]
1542 FileOperation::GetBackingMemory { flags: fio::VmoFlags::READ },
1543 ]
1544 );
1545 }
1546
1547 #[fuchsia::test]
1548 async fn test_getbuffer_no_perms() {
1549 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1550 let result = env
1551 .proxy
1552 .get_backing_memory(fio::VmoFlags::READ)
1553 .await
1554 .unwrap()
1555 .map_err(Status::from_raw);
1556 #[cfg(target_os = "fuchsia")]
1558 assert_eq!(result, Err(Status::ACCESS_DENIED));
1559 #[cfg(not(target_os = "fuchsia"))]
1560 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1561 let events = env.file.operations.lock();
1562 assert_eq!(
1563 *events,
1564 vec![FileOperation::Init {
1565 options: FileOptions {
1566 rights: fio::Operations::empty(),
1567 is_append: false,
1568 is_linkable: true
1569 }
1570 },]
1571 );
1572 }
1573
1574 #[fuchsia::test]
1575 async fn test_getbuffer_vmo_exec_requires_right_executable() {
1576 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1577 let result = env
1578 .proxy
1579 .get_backing_memory(fio::VmoFlags::EXECUTE)
1580 .await
1581 .unwrap()
1582 .map_err(Status::from_raw);
1583 #[cfg(target_os = "fuchsia")]
1585 assert_eq!(result, Err(Status::ACCESS_DENIED));
1586 #[cfg(not(target_os = "fuchsia"))]
1587 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1588 let events = env.file.operations.lock();
1589 assert_eq!(
1590 *events,
1591 vec![FileOperation::Init {
1592 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1593 },]
1594 );
1595 }
1596
1597 #[fuchsia::test]
1598 async fn test_get_flags() {
1599 let env = init_mock_file(
1600 Box::new(always_succeed_callback),
1601 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1602 );
1603 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::from_raw).unwrap();
1604 assert_eq!(flags, FLAGS_RW | fio::Flags::PROTOCOL_FILE);
1606 let events = env.file.operations.lock();
1607 assert_eq!(
1608 *events,
1609 vec![
1610 FileOperation::Init {
1611 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1612 },
1613 FileOperation::Truncate { length: 0 }
1614 ]
1615 );
1616 }
1617
1618 #[fuchsia::test]
1619 async fn test_open_flag_send_representation() {
1620 let env = init_mock_file(
1621 Box::new(always_succeed_callback),
1622 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
1623 );
1624 let event = env.proxy.take_event_stream().try_next().await.unwrap();
1625 match event {
1626 Some(fio::FileEvent::OnRepresentation { payload }) => {
1627 assert_eq!(
1628 payload,
1629 fio::Representation::File(fio::FileInfo {
1630 is_append: Some(false),
1631 ..Default::default()
1632 })
1633 );
1634 }
1635 e => panic!(
1636 "Expected OnRepresentation event with fio::Representation::File, got {:?}",
1637 e
1638 ),
1639 }
1640 let events = env.file.operations.lock();
1641 assert_eq!(
1642 *events,
1643 vec![FileOperation::Init {
1644 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true },
1645 }]
1646 );
1647 }
1648
1649 #[fuchsia::test]
1650 async fn test_read_succeeds() {
1651 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1652 let data = env.proxy.read(10).await.unwrap().map_err(Status::from_raw).unwrap();
1653 assert_eq!(data, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1654
1655 let events = env.file.operations.lock();
1656 assert_eq!(
1657 *events,
1658 vec![
1659 FileOperation::Init {
1660 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1661 },
1662 FileOperation::ReadAt { offset: 0, count: 10 },
1663 ]
1664 );
1665 }
1666
1667 #[fuchsia::test]
1668 async fn test_read_not_readable() {
1669 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_WRITABLE);
1670 let result = env.proxy.read(10).await.unwrap().map_err(Status::from_raw);
1671 assert_eq!(result, Err(Status::BAD_HANDLE));
1672 }
1673
1674 #[fuchsia::test]
1675 async fn test_read_validates_count() {
1676 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1677 let result =
1678 env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
1679 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1680 }
1681
1682 #[fuchsia::test]
1683 async fn test_read_at_succeeds() {
1684 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1685 let data = env.proxy.read_at(5, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1686 assert_eq!(data, vec![10, 11, 12, 13, 14]);
1687
1688 let events = env.file.operations.lock();
1689 assert_eq!(
1690 *events,
1691 vec![
1692 FileOperation::Init {
1693 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1694 },
1695 FileOperation::ReadAt { offset: 10, count: 5 },
1696 ]
1697 );
1698 }
1699
1700 #[fuchsia::test]
1701 async fn test_read_at_validates_count() {
1702 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1703 let result = env
1704 .proxy
1705 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
1706 .await
1707 .unwrap()
1708 .map_err(Status::from_raw);
1709 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1710 }
1711
1712 #[fuchsia::test]
1713 async fn test_seek_start() {
1714 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1715 let offset = env
1716 .proxy
1717 .seek(fio::SeekOrigin::Start, 10)
1718 .await
1719 .unwrap()
1720 .map_err(Status::from_raw)
1721 .unwrap();
1722 assert_eq!(offset, 10);
1723
1724 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1725 assert_eq!(data, vec![10]);
1726 let events = env.file.operations.lock();
1727 assert_eq!(
1728 *events,
1729 vec![
1730 FileOperation::Init {
1731 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1732 },
1733 FileOperation::ReadAt { offset: 10, count: 1 },
1734 ]
1735 );
1736 }
1737
1738 #[fuchsia::test]
1739 async fn test_seek_cur() {
1740 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1741 let offset = env
1742 .proxy
1743 .seek(fio::SeekOrigin::Start, 10)
1744 .await
1745 .unwrap()
1746 .map_err(Status::from_raw)
1747 .unwrap();
1748 assert_eq!(offset, 10);
1749
1750 let offset = env
1751 .proxy
1752 .seek(fio::SeekOrigin::Current, -2)
1753 .await
1754 .unwrap()
1755 .map_err(Status::from_raw)
1756 .unwrap();
1757 assert_eq!(offset, 8);
1758
1759 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1760 assert_eq!(data, vec![8]);
1761 let events = env.file.operations.lock();
1762 assert_eq!(
1763 *events,
1764 vec![
1765 FileOperation::Init {
1766 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1767 },
1768 FileOperation::ReadAt { offset: 8, count: 1 },
1769 ]
1770 );
1771 }
1772
1773 #[fuchsia::test]
1774 async fn test_seek_before_start() {
1775 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1776 let result =
1777 env.proxy.seek(fio::SeekOrigin::Current, -4).await.unwrap().map_err(Status::from_raw);
1778 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1779 }
1780
1781 #[fuchsia::test]
1782 async fn test_seek_end() {
1783 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1784 let offset = env
1785 .proxy
1786 .seek(fio::SeekOrigin::End, -4)
1787 .await
1788 .unwrap()
1789 .map_err(Status::from_raw)
1790 .unwrap();
1791 assert_eq!(offset, MOCK_FILE_SIZE - 4);
1792
1793 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1794 assert_eq!(data, vec![(offset % 256) as u8]);
1795 let events = env.file.operations.lock();
1796 assert_eq!(
1797 *events,
1798 vec![
1799 FileOperation::Init {
1800 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1801 },
1802 FileOperation::GetSize, FileOperation::ReadAt { offset, count: 1 },
1804 ]
1805 );
1806 }
1807
1808 #[fuchsia::test]
1809 async fn test_update_attributes() {
1810 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1811 let attributes = fio::MutableNodeAttributes {
1812 creation_time: Some(40000),
1813 modification_time: Some(100000),
1814 mode: Some(1),
1815 ..Default::default()
1816 };
1817 let () = env
1818 .proxy
1819 .update_attributes(&attributes)
1820 .await
1821 .unwrap()
1822 .map_err(Status::from_raw)
1823 .unwrap();
1824
1825 let events = env.file.operations.lock();
1826 assert_eq!(
1827 *events,
1828 vec![
1829 FileOperation::Init {
1830 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1831 },
1832 FileOperation::UpdateAttributes { attrs: attributes },
1833 ]
1834 );
1835 }
1836
1837 #[fuchsia::test]
1838 async fn test_set_flags() {
1839 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1840 env.proxy
1841 .set_flags(fio::Flags::FILE_APPEND)
1842 .await
1843 .unwrap()
1844 .map_err(Status::from_raw)
1845 .unwrap();
1846 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::from_raw).unwrap();
1847 assert_eq!(flags, FLAGS_W | fio::Flags::FILE_APPEND | fio::Flags::PROTOCOL_FILE);
1848 }
1849
1850 #[fuchsia::test]
1851 async fn test_sync() {
1852 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1853 let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1854 let events = env.file.operations.lock();
1855 assert_eq!(
1856 *events,
1857 vec![
1858 FileOperation::Init {
1859 options: FileOptions {
1860 rights: fio::Operations::empty(),
1861 is_append: false,
1862 is_linkable: true
1863 }
1864 },
1865 FileOperation::Sync
1866 ]
1867 );
1868 }
1869
1870 #[fuchsia::test]
1871 async fn test_resize() {
1872 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1873 let () = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw).unwrap();
1874 let events = env.file.operations.lock();
1875 assert_matches!(
1876 &events[..],
1877 [
1878 FileOperation::Init {
1879 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1880 },
1881 FileOperation::Truncate { length: 10 },
1882 ]
1883 );
1884 }
1885
1886 #[fuchsia::test]
1887 async fn test_resize_no_perms() {
1888 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1889 let result = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw);
1890 assert_eq!(result, Err(Status::BAD_HANDLE));
1891 let events = env.file.operations.lock();
1892 assert_eq!(
1893 *events,
1894 vec![FileOperation::Init {
1895 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1896 },]
1897 );
1898 }
1899
1900 #[fuchsia::test]
1901 async fn test_write() {
1902 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1903 let data = "Hello, world!".as_bytes();
1904 let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
1905 assert_eq!(count, data.len() as u64);
1906 let events = env.file.operations.lock();
1907 assert_matches!(
1908 &events[..],
1909 [
1910 FileOperation::Init {
1911 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1912 },
1913 FileOperation::WriteAt { offset: 0, .. },
1914 ]
1915 );
1916 if let FileOperation::WriteAt { content, .. } = &events[1] {
1917 assert_eq!(content.as_slice(), data);
1918 } else {
1919 unreachable!();
1920 }
1921 }
1922
1923 #[fuchsia::test]
1924 async fn test_write_no_perms() {
1925 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1926 let data = "Hello, world!".as_bytes();
1927 let result = env.proxy.write(data).await.unwrap().map_err(Status::from_raw);
1928 assert_eq!(result, Err(Status::BAD_HANDLE));
1929 let events = env.file.operations.lock();
1930 assert_eq!(
1931 *events,
1932 vec![FileOperation::Init {
1933 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1934 },]
1935 );
1936 }
1937
1938 #[fuchsia::test]
1939 async fn test_write_at() {
1940 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1941 let data = "Hello, world!".as_bytes();
1942 let count = env.proxy.write_at(data, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1943 assert_eq!(count, data.len() as u64);
1944 let events = env.file.operations.lock();
1945 assert_matches!(
1946 &events[..],
1947 [
1948 FileOperation::Init {
1949 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1950 },
1951 FileOperation::WriteAt { offset: 10, .. },
1952 ]
1953 );
1954 if let FileOperation::WriteAt { content, .. } = &events[1] {
1955 assert_eq!(content.as_slice(), data);
1956 } else {
1957 unreachable!();
1958 }
1959 }
1960
1961 #[fuchsia::test]
1962 async fn test_append() {
1963 let env = init_mock_file(
1964 Box::new(always_succeed_callback),
1965 fio::PERM_WRITABLE | fio::Flags::FILE_APPEND,
1966 );
1967 let data = "Hello, world!".as_bytes();
1968 let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
1969 assert_eq!(count, data.len() as u64);
1970 let offset = env
1971 .proxy
1972 .seek(fio::SeekOrigin::Current, 0)
1973 .await
1974 .unwrap()
1975 .map_err(Status::from_raw)
1976 .unwrap();
1977 assert_eq!(offset, MOCK_FILE_SIZE + data.len() as u64);
1978 let events = env.file.operations.lock();
1979 assert_matches!(
1980 &events[..],
1981 [
1982 FileOperation::Init {
1983 options: FileOptions { rights: RIGHTS_W, is_append: true, .. }
1984 },
1985 FileOperation::Append { .. }
1986 ]
1987 );
1988 if let FileOperation::Append { content } = &events[1] {
1989 assert_eq!(content.as_slice(), data);
1990 } else {
1991 unreachable!();
1992 }
1993 }
1994
1995 #[cfg(target_os = "fuchsia")]
1996 mod stream_tests {
1997 use super::*;
1998
1999 fn init_mock_stream_file(vmo: zx::Vmo, flags: fio::Flags) -> TestEnv {
2000 let file = MockFile::new_with_vmo(Box::new(always_succeed_callback), vmo);
2001 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
2002
2003 let scope = ExecutionScope::new();
2004
2005 let cloned_file = file.clone();
2006 let cloned_scope = scope.clone();
2007
2008 flags.to_object_request(server_end).create_connection_sync::<StreamIoConnection<_>, _>(
2009 cloned_scope,
2010 cloned_file,
2011 flags,
2012 );
2013
2014 TestEnv { file, proxy, scope }
2015 }
2016
2017 #[fuchsia::test]
2018 async fn test_stream_describe() {
2019 const VMO_CONTENTS: &[u8] = b"hello there";
2020 let vmo = zx::Vmo::create(VMO_CONTENTS.len() as u64).unwrap();
2021 vmo.write(VMO_CONTENTS, 0).unwrap();
2022 let flags = fio::PERM_READABLE | fio::PERM_WRITABLE;
2023 let env = init_mock_stream_file(vmo, flags);
2024
2025 let fio::FileInfo { stream: Some(stream), .. } = env.proxy.describe().await.unwrap()
2026 else {
2027 panic!("Missing stream")
2028 };
2029 let contents =
2030 stream.read_to_vec(zx::StreamReadOptions::empty(), 20).expect("read failed");
2031 assert_eq!(contents, VMO_CONTENTS);
2032 }
2033
2034 #[fuchsia::test]
2035 async fn test_stream_read() {
2036 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2037 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2038 vmo.write(&vmo_contents, 0).unwrap();
2039 let flags = fio::PERM_READABLE;
2040 let env = init_mock_stream_file(vmo, flags);
2041
2042 let data = env
2043 .proxy
2044 .read(vmo_contents.len() as u64)
2045 .await
2046 .unwrap()
2047 .map_err(Status::from_raw)
2048 .unwrap();
2049 assert_eq!(data, vmo_contents);
2050
2051 let events = env.file.operations.lock();
2052 assert_eq!(
2053 *events,
2054 [FileOperation::Init {
2055 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2056 },]
2057 );
2058 }
2059
2060 #[fuchsia::test]
2061 async fn test_stream_read_at() {
2062 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2063 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2064 vmo.write(&vmo_contents, 0).unwrap();
2065 let flags = fio::PERM_READABLE;
2066 let env = init_mock_stream_file(vmo, flags);
2067
2068 const OFFSET: u64 = 4;
2069 let data = env
2070 .proxy
2071 .read_at((vmo_contents.len() as u64) - OFFSET, OFFSET)
2072 .await
2073 .unwrap()
2074 .map_err(Status::from_raw)
2075 .unwrap();
2076 assert_eq!(data, vmo_contents[OFFSET as usize..]);
2077
2078 let events = env.file.operations.lock();
2079 assert_eq!(
2080 *events,
2081 [FileOperation::Init {
2082 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2083 },]
2084 );
2085 }
2086
2087 #[fuchsia::test]
2088 async fn test_stream_write() {
2089 const DATA_SIZE: u64 = 10;
2090 let vmo = zx::Vmo::create(DATA_SIZE).unwrap();
2091 let flags = fio::PERM_WRITABLE;
2092 let env = init_mock_stream_file(
2093 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2094 flags,
2095 );
2096
2097 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2098 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2099 assert_eq!(written, DATA_SIZE);
2100 let mut vmo_contents = [0; DATA_SIZE as usize];
2101 vmo.read(&mut vmo_contents, 0).unwrap();
2102 assert_eq!(vmo_contents, data);
2103
2104 let events = env.file.operations.lock();
2105 assert_eq!(
2106 *events,
2107 [FileOperation::Init {
2108 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2109 },]
2110 );
2111 }
2112
2113 #[fuchsia::test]
2114 async fn test_stream_write_at() {
2115 const OFFSET: u64 = 4;
2116 const DATA_SIZE: u64 = 10;
2117 let vmo = zx::Vmo::create(DATA_SIZE + OFFSET).unwrap();
2118 let flags = fio::PERM_WRITABLE;
2119 let env = init_mock_stream_file(
2120 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2121 flags,
2122 );
2123
2124 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2125 let written =
2126 env.proxy.write_at(&data, OFFSET).await.unwrap().map_err(Status::from_raw).unwrap();
2127 assert_eq!(written, DATA_SIZE);
2128 let mut vmo_contents = [0; DATA_SIZE as usize];
2129 vmo.read(&mut vmo_contents, OFFSET).unwrap();
2130 assert_eq!(vmo_contents, data);
2131
2132 let events = env.file.operations.lock();
2133 assert_eq!(
2134 *events,
2135 [FileOperation::Init {
2136 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2137 }]
2138 );
2139 }
2140
2141 #[fuchsia::test]
2142 async fn test_stream_seek() {
2143 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2144 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2145 vmo.write(&vmo_contents, 0).unwrap();
2146 let flags = fio::PERM_READABLE;
2147 let env = init_mock_stream_file(vmo, flags);
2148
2149 let position = env
2150 .proxy
2151 .seek(fio::SeekOrigin::Start, 8)
2152 .await
2153 .unwrap()
2154 .map_err(Status::from_raw)
2155 .unwrap();
2156 assert_eq!(position, 8);
2157 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2158 assert_eq!(data, [1, 0]);
2159
2160 let position = env
2161 .proxy
2162 .seek(fio::SeekOrigin::Current, -4)
2163 .await
2164 .unwrap()
2165 .map_err(Status::from_raw)
2166 .unwrap();
2167 assert_eq!(position, 6);
2169 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2170 assert_eq!(data, [3, 2]);
2171
2172 let position = env
2173 .proxy
2174 .seek(fio::SeekOrigin::End, -6)
2175 .await
2176 .unwrap()
2177 .map_err(Status::from_raw)
2178 .unwrap();
2179 assert_eq!(position, 4);
2180 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2181 assert_eq!(data, [5, 4]);
2182
2183 let e = env
2184 .proxy
2185 .seek(fio::SeekOrigin::Start, -1)
2186 .await
2187 .unwrap()
2188 .map_err(Status::from_raw)
2189 .expect_err("Seeking before the start of a file should be an error");
2190 assert_eq!(e, Status::INVALID_ARGS);
2191 }
2192
2193 #[fuchsia::test]
2194 async fn test_stream_set_flags() {
2195 let data = [0, 1, 2, 3, 4];
2196 let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 100).unwrap();
2197 let flags = fio::PERM_WRITABLE;
2198 let env = init_mock_stream_file(
2199 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2200 flags,
2201 );
2202
2203 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2204 assert_eq!(written, data.len() as u64);
2205 assert_eq!(vmo.get_content_size().unwrap(), 100);
2207
2208 env.proxy
2210 .set_flags(fio::Flags::FILE_APPEND)
2211 .await
2212 .unwrap()
2213 .map_err(Status::from_raw)
2214 .unwrap();
2215 env.proxy
2216 .seek(fio::SeekOrigin::Start, 0)
2217 .await
2218 .unwrap()
2219 .map_err(Status::from_raw)
2220 .unwrap();
2221 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2222 assert_eq!(written, data.len() as u64);
2223 assert_eq!(vmo.get_content_size().unwrap(), 105);
2225
2226 env.proxy
2228 .set_flags(fio::Flags::empty())
2229 .await
2230 .unwrap()
2231 .map_err(Status::from_raw)
2232 .unwrap();
2233 env.proxy
2234 .seek(fio::SeekOrigin::Start, 0)
2235 .await
2236 .unwrap()
2237 .map_err(Status::from_raw)
2238 .unwrap();
2239 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2240 assert_eq!(written, data.len() as u64);
2241 assert_eq!(vmo.get_content_size().unwrap(), 105);
2243 }
2244
2245 #[fuchsia::test]
2246 async fn test_stream_read_validates_count() {
2247 let vmo = zx::Vmo::create(10).unwrap();
2248 let flags = fio::PERM_READABLE;
2249 let env = init_mock_stream_file(vmo, flags);
2250 let result =
2251 env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
2252 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2253 }
2254
2255 #[fuchsia::test]
2256 async fn test_stream_read_at_validates_count() {
2257 let vmo = zx::Vmo::create(10).unwrap();
2258 let flags = fio::PERM_READABLE;
2259 let env = init_mock_stream_file(vmo, flags);
2260 let result = env
2261 .proxy
2262 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
2263 .await
2264 .unwrap()
2265 .map_err(Status::from_raw);
2266 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2267 }
2268 }
2269}