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