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 fn create<'a>(
186 scope: ExecutionScope,
187 node: Arc<T>,
188 protocols: impl ProtocolsExt,
189 object_request: ObjectRequestRef<'a>,
190 ) -> impl Future<Output = Result<(), Status>> + 'a {
191 Self::create(scope, node, protocols, object_request)
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 fn create<'a>(
292 scope: ExecutionScope,
293 node: Arc<T>,
294 protocols: impl crate::ProtocolsExt,
295 object_request: ObjectRequestRef<'a>,
296 ) -> impl Future<Output = Result<(), Status>> + 'a {
297 Self::create(scope, node, protocols, object_request)
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 fn create<'a>(
444 scope: ExecutionScope,
445 node: Arc<T>,
446 protocols: impl crate::ProtocolsExt,
447 object_request: ObjectRequestRef<'a>,
448 ) -> impl Future<Output = Result<(), Status>> + 'a {
449 Self::create(scope, node, protocols, object_request)
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, &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, &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::result_into_raw(result))
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::result_into_raw(result))
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(zx_status::sys::ZX_OK, 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::result_into_raw(self.file.set_flags(flags)))?;
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(zx_status::sys::ZX_OK, 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(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 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
1036 return Err(Status::BAD_HANDLE);
1037 }
1038 self.file.allocate(offset, length, mode).await
1039 }
1040
1041 fn should_sync_before_close(&self) -> bool {
1042 self.options
1043 .rights
1044 .intersects(fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES)
1045 }
1046}
1047
1048impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin>
1051 RequestHandler for Option<FileConnection<U>>
1052{
1053 type Request = Result<fio::FileRequest, fidl::Error>;
1054
1055 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
1056 let option_this = self.get_mut();
1057 let this = option_this.as_mut().unwrap();
1058 let Some(_guard) = this.scope.try_active_guard() else { return ControlFlow::Break(()) };
1059 let state = match request {
1060 Ok(request) => {
1061 this.handle_request(request)
1062 .await
1063 .unwrap_or(ConnectionState::Dropped)
1066 }
1067 Err(_) => {
1068 ConnectionState::Dropped
1072 }
1073 };
1074 match state {
1075 ConnectionState::Alive => ControlFlow::Continue(()),
1076 ConnectionState::Dropped => {
1077 if this.should_sync_before_close() {
1078 let _ = this.file.sync(SyncMode::PreClose).await;
1079 }
1080 ControlFlow::Break(())
1081 }
1082 ConnectionState::Closed(responder) => {
1083 async move {
1084 let this = option_this.as_mut().unwrap();
1085 let _ = responder.send({
1086 let result = if this.should_sync_before_close() {
1087 this.file.sync(SyncMode::PreClose).await.map_err(Status::into_raw)
1088 } else {
1089 Ok(())
1090 };
1091 std::mem::drop(option_this.take());
1094 result
1095 });
1096 }
1097 .trace(trace::trace_future_args!("storage", "File::Close"))
1098 .await;
1099 ControlFlow::Break(())
1100 }
1101 }
1102 }
1103
1104 async fn stream_closed(self: Pin<&mut Self>) {
1105 let this = self.get_mut().as_mut().unwrap();
1106 if this.should_sync_before_close() {
1107 if let Some(_guard) = this.scope.try_active_guard() {
1108 let _ = this.file.sync(SyncMode::PreClose).await;
1109 }
1110 }
1111 }
1112}
1113
1114impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + IoOpHandler> Representation
1115 for FileConnection<U>
1116{
1117 type Protocol = fio::FileMarker;
1118
1119 async fn get_representation(
1120 &self,
1121 requested_attributes: fio::NodeAttributesQuery,
1122 ) -> Result<fio::Representation, Status> {
1123 Ok(fio::Representation::File(fio::FileInfo {
1125 is_append: Some(self.options.is_append),
1126 #[cfg(target_os = "fuchsia")]
1127 stream: self.file.duplicate_stream()?,
1128 #[cfg(not(target_os = "fuchsia"))]
1129 stream: None,
1130 attributes: if requested_attributes.is_empty() {
1131 None
1132 } else {
1133 Some(self.file.get_attributes(requested_attributes).await?)
1134 },
1135 ..Default::default()
1136 }))
1137 }
1138
1139 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
1140 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
1141 #[cfg(target_os = "fuchsia")]
1142 let stream = self.file.duplicate_stream()?;
1143 #[cfg(not(target_os = "fuchsia"))]
1144 let stream = None;
1145 Ok(fio::NodeInfoDeprecated::File(fio::FileObject { event: None, stream }))
1146 }
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151 use super::*;
1152 use crate::ToObjectRequest;
1153 use crate::directory::entry::{EntryInfo, GetEntryInfo};
1154 use crate::node::Node;
1155 use assert_matches::assert_matches;
1156 use fuchsia_sync::Mutex;
1157 use futures::prelude::*;
1158
1159 const RIGHTS_R: fio::Operations =
1160 fio::Operations::READ_BYTES.union(fio::Operations::GET_ATTRIBUTES);
1161 const RIGHTS_W: fio::Operations =
1162 fio::Operations::WRITE_BYTES.union(fio::Operations::UPDATE_ATTRIBUTES);
1163 const RIGHTS_RW: fio::Operations = fio::Operations::READ_BYTES
1164 .union(fio::Operations::WRITE_BYTES)
1165 .union(fio::Operations::GET_ATTRIBUTES)
1166 .union(fio::Operations::UPDATE_ATTRIBUTES);
1167
1168 const FLAGS_R: fio::Flags = fio::Flags::PERM_READ_BYTES.union(fio::Flags::PERM_GET_ATTRIBUTES);
1173 const FLAGS_W: fio::Flags =
1174 fio::Flags::PERM_WRITE_BYTES.union(fio::Flags::PERM_UPDATE_ATTRIBUTES);
1175 const FLAGS_RW: fio::Flags = FLAGS_R.union(FLAGS_W);
1176
1177 #[derive(Debug, PartialEq)]
1178 enum FileOperation {
1179 Init {
1180 options: FileOptions,
1181 },
1182 ReadAt {
1183 offset: u64,
1184 count: u64,
1185 },
1186 WriteAt {
1187 offset: u64,
1188 content: Vec<u8>,
1189 },
1190 Append {
1191 content: Vec<u8>,
1192 },
1193 Truncate {
1194 length: u64,
1195 },
1196 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1197 Allocate {
1198 offset: u64,
1199 length: u64,
1200 mode: fio::AllocateMode,
1201 },
1202 #[cfg(target_os = "fuchsia")]
1203 GetBackingMemory {
1204 flags: fio::VmoFlags,
1205 },
1206 GetSize,
1207 GetAttributes {
1208 query: fio::NodeAttributesQuery,
1209 },
1210 UpdateAttributes {
1211 attrs: fio::MutableNodeAttributes,
1212 },
1213 Close,
1214 Sync,
1215 }
1216
1217 type MockCallbackType = Box<dyn Fn(&FileOperation) -> Result<(), Status> + Sync + Send>;
1218 struct MockFile {
1220 operations: Mutex<Vec<FileOperation>>,
1222 callback: MockCallbackType,
1224 file_size: u64,
1226 #[cfg(target_os = "fuchsia")]
1227 vmo: zx::Vmo,
1229 }
1230
1231 const MOCK_FILE_SIZE: u64 = 256;
1232 const MOCK_FILE_ID: u64 = 10;
1233 const MOCK_FILE_LINKS: u64 = 2;
1234 const MOCK_FILE_CREATION_TIME: u64 = 10;
1235 const MOCK_FILE_MODIFICATION_TIME: u64 = 100;
1236 impl MockFile {
1237 fn new(callback: MockCallbackType) -> Arc<Self> {
1238 Arc::new(MockFile {
1239 operations: Mutex::new(Vec::new()),
1240 callback,
1241 file_size: MOCK_FILE_SIZE,
1242 #[cfg(target_os = "fuchsia")]
1243 vmo: zx::NullableHandle::invalid().into(),
1244 })
1245 }
1246
1247 #[cfg(target_os = "fuchsia")]
1248 fn new_with_vmo(callback: MockCallbackType, vmo: zx::Vmo) -> Arc<Self> {
1249 Arc::new(MockFile {
1250 operations: Mutex::new(Vec::new()),
1251 callback,
1252 file_size: MOCK_FILE_SIZE,
1253 vmo,
1254 })
1255 }
1256
1257 fn handle_operation(&self, operation: FileOperation) -> Result<(), Status> {
1258 let result = (self.callback)(&operation);
1259 self.operations.lock().push(operation);
1260 result
1261 }
1262 }
1263
1264 impl GetEntryInfo for MockFile {
1265 fn entry_info(&self) -> EntryInfo {
1266 EntryInfo::new(MOCK_FILE_ID, fio::DirentType::File)
1267 }
1268 }
1269
1270 impl Node for MockFile {
1271 async fn get_attributes(
1272 &self,
1273 query: fio::NodeAttributesQuery,
1274 ) -> Result<fio::NodeAttributes2, Status> {
1275 self.handle_operation(FileOperation::GetAttributes { query })?;
1276 Ok(attributes!(
1277 query,
1278 Mutable {
1279 creation_time: MOCK_FILE_CREATION_TIME,
1280 modification_time: MOCK_FILE_MODIFICATION_TIME,
1281 },
1282 Immutable {
1283 protocols: fio::NodeProtocolKinds::FILE,
1284 abilities: fio::Operations::GET_ATTRIBUTES
1285 | fio::Operations::UPDATE_ATTRIBUTES
1286 | fio::Operations::READ_BYTES
1287 | fio::Operations::WRITE_BYTES,
1288 content_size: self.file_size,
1289 storage_size: 2 * self.file_size,
1290 link_count: MOCK_FILE_LINKS,
1291 id: MOCK_FILE_ID,
1292 }
1293 ))
1294 }
1295
1296 fn close(self: Arc<Self>) {
1297 let _ = self.handle_operation(FileOperation::Close);
1298 }
1299 }
1300
1301 impl File for MockFile {
1302 fn writable(&self) -> bool {
1303 true
1304 }
1305
1306 async fn open_file(&self, options: &FileOptions) -> Result<(), Status> {
1307 self.handle_operation(FileOperation::Init { options: *options })?;
1308 Ok(())
1309 }
1310
1311 async fn truncate(&self, length: u64) -> Result<(), Status> {
1312 self.handle_operation(FileOperation::Truncate { length })
1313 }
1314
1315 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1316 async fn allocate(
1317 &self,
1318 offset: u64,
1319 length: u64,
1320 mode: fio::AllocateMode,
1321 ) -> Result<(), Status> {
1322 self.handle_operation(FileOperation::Allocate { offset, length, mode })
1323 }
1324
1325 #[cfg(target_os = "fuchsia")]
1326 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
1327 self.handle_operation(FileOperation::GetBackingMemory { flags })?;
1328 Err(Status::NOT_SUPPORTED)
1329 }
1330
1331 async fn get_size(&self) -> Result<u64, Status> {
1332 self.handle_operation(FileOperation::GetSize)?;
1333 Ok(self.file_size)
1334 }
1335
1336 async fn update_attributes(&self, attrs: fio::MutableNodeAttributes) -> Result<(), Status> {
1337 self.handle_operation(FileOperation::UpdateAttributes { attrs })?;
1338 Ok(())
1339 }
1340
1341 async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
1342 self.handle_operation(FileOperation::Sync)
1343 }
1344 }
1345
1346 impl FileIo for MockFile {
1347 async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
1348 let count = buffer.len() as u64;
1349 self.handle_operation(FileOperation::ReadAt { offset, count })?;
1350
1351 let mut i = offset;
1353 buffer.fill_with(|| {
1354 let v = (i % 256) as u8;
1355 i += 1;
1356 v
1357 });
1358 Ok(count)
1359 }
1360
1361 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
1362 self.handle_operation(FileOperation::WriteAt { offset, content: content.to_vec() })?;
1363 Ok(content.len() as u64)
1364 }
1365
1366 async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
1367 self.handle_operation(FileOperation::Append { content: content.to_vec() })?;
1368 Ok((content.len() as u64, self.file_size + content.len() as u64))
1369 }
1370 }
1371
1372 #[cfg(target_os = "fuchsia")]
1373 impl GetVmo for MockFile {
1374 fn get_vmo(&self) -> &zx::Vmo {
1375 &self.vmo
1376 }
1377 }
1378
1379 fn only_allow_init(op: &FileOperation) -> Result<(), Status> {
1381 match op {
1382 FileOperation::Init { .. } => Ok(()),
1383 _ => Err(Status::IO),
1384 }
1385 }
1386
1387 fn always_succeed_callback(_op: &FileOperation) -> Result<(), Status> {
1389 Ok(())
1390 }
1391
1392 struct TestEnv {
1393 pub file: Arc<MockFile>,
1394 pub proxy: fio::FileProxy,
1395 pub scope: ExecutionScope,
1396 }
1397
1398 fn init_mock_file(callback: MockCallbackType, flags: fio::Flags) -> TestEnv {
1399 let file = MockFile::new(callback);
1400 #[cfg(feature = "fdomain")]
1401 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
1402 #[cfg(not(feature = "fdomain"))]
1403 let scope = crate::execution_scope::ExecutionScope::new();
1404
1405 let (proxy, server_end) = scope.domain().create_proxy::<fio::FileMarker>();
1406
1407 flags.to_object_request(server_end).create_connection_sync::<FidlIoConnection<_>, _>(
1408 scope.clone(),
1409 file.clone(),
1410 flags,
1411 );
1412
1413 TestEnv { file, proxy, scope }
1414 }
1415
1416 #[fuchsia::test]
1417 async fn test_open_flag_truncate() {
1418 let env = init_mock_file(
1419 Box::new(always_succeed_callback),
1420 fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1421 );
1422 let () = env.proxy.sync().await.unwrap().map_err(Status::err_from_raw).unwrap();
1424 let events = env.file.operations.lock();
1425 assert_eq!(
1426 *events,
1427 vec![
1428 FileOperation::Init {
1429 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1430 },
1431 FileOperation::Truncate { length: 0 },
1432 FileOperation::Sync,
1433 ]
1434 );
1435 }
1436
1437 #[fuchsia::test]
1438 async fn test_close_succeeds() {
1439 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1440 let () = env.proxy.close().await.unwrap().map_err(Status::err_from_raw).unwrap();
1441
1442 let events = env.file.operations.lock();
1443 assert_eq!(
1444 *events,
1445 vec![
1446 FileOperation::Init {
1447 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1448 },
1449 FileOperation::Close {},
1450 ]
1451 );
1452 }
1453
1454 #[fuchsia::test]
1455 async fn test_close_fails() {
1456 let env =
1457 init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE | fio::PERM_WRITABLE);
1458 let status = env.proxy.close().await.unwrap().map_err(Status::err_from_raw);
1459 assert_eq!(status, Err(Status::IO));
1460
1461 let events = env.file.operations.lock();
1462 assert_eq!(
1463 *events,
1464 vec![
1465 FileOperation::Init {
1466 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1467 },
1468 FileOperation::Sync,
1469 FileOperation::Close,
1470 ]
1471 );
1472 }
1473
1474 #[fuchsia::test]
1475 async fn test_close_called_when_dropped() {
1476 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1477 let _ = env.proxy.sync().await;
1478 std::mem::drop(env.proxy);
1479 env.scope.shutdown();
1480 env.scope.wait().await;
1481 let events = env.file.operations.lock();
1482 assert_eq!(
1483 *events,
1484 vec![
1485 FileOperation::Init {
1486 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1487 },
1488 FileOperation::Sync,
1489 FileOperation::Close,
1490 ]
1491 );
1492 }
1493
1494 #[fuchsia::test]
1495 async fn test_query() {
1496 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1497 let protocol = env.proxy.query().await.unwrap();
1498 assert_eq!(protocol, fio::FileMarker::PROTOCOL_NAME.as_bytes());
1499 }
1500
1501 #[fuchsia::test]
1502 async fn test_get_attributes() {
1503 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1504 let (mutable_attributes, immutable_attributes) = env
1505 .proxy
1506 .get_attributes(fio::NodeAttributesQuery::all())
1507 .await
1508 .unwrap()
1509 .map_err(Status::err_from_raw)
1510 .unwrap();
1511 let expected = attributes!(
1512 fio::NodeAttributesQuery::all(),
1513 Mutable {
1514 creation_time: MOCK_FILE_CREATION_TIME,
1515 modification_time: MOCK_FILE_MODIFICATION_TIME,
1516 },
1517 Immutable {
1518 protocols: fio::NodeProtocolKinds::FILE,
1519 abilities: fio::Operations::GET_ATTRIBUTES
1520 | fio::Operations::UPDATE_ATTRIBUTES
1521 | fio::Operations::READ_BYTES
1522 | fio::Operations::WRITE_BYTES,
1523 content_size: MOCK_FILE_SIZE,
1524 storage_size: 2 * MOCK_FILE_SIZE,
1525 link_count: MOCK_FILE_LINKS,
1526 id: MOCK_FILE_ID,
1527 }
1528 );
1529 assert_eq!(mutable_attributes, expected.mutable_attributes);
1530 assert_eq!(immutable_attributes, expected.immutable_attributes);
1531
1532 let events = env.file.operations.lock();
1533 assert_eq!(
1534 *events,
1535 vec![
1536 FileOperation::Init {
1537 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1538 },
1539 FileOperation::GetAttributes { query: fio::NodeAttributesQuery::all() }
1540 ]
1541 );
1542 }
1543
1544 #[fuchsia::test]
1545 async fn test_getbuffer() {
1546 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1547 let result = env
1548 .proxy
1549 .get_backing_memory(fio::VmoFlags::READ)
1550 .await
1551 .unwrap()
1552 .map_err(Status::err_from_raw);
1553 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1554 let events = env.file.operations.lock();
1555 assert_eq!(
1556 *events,
1557 vec![
1558 FileOperation::Init {
1559 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1560 },
1561 #[cfg(target_os = "fuchsia")]
1562 FileOperation::GetBackingMemory { flags: fio::VmoFlags::READ },
1563 ]
1564 );
1565 }
1566
1567 #[fuchsia::test]
1568 async fn test_getbuffer_no_perms() {
1569 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1570 let result = env
1571 .proxy
1572 .get_backing_memory(fio::VmoFlags::READ)
1573 .await
1574 .unwrap()
1575 .map_err(Status::err_from_raw);
1576 #[cfg(target_os = "fuchsia")]
1578 assert_eq!(result, Err(Status::ACCESS_DENIED));
1579 #[cfg(not(target_os = "fuchsia"))]
1580 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1581 let events = env.file.operations.lock();
1582 assert_eq!(
1583 *events,
1584 vec![FileOperation::Init {
1585 options: FileOptions {
1586 rights: fio::Operations::empty(),
1587 is_append: false,
1588 is_linkable: true
1589 }
1590 },]
1591 );
1592 }
1593
1594 #[fuchsia::test]
1595 async fn test_getbuffer_vmo_exec_requires_right_executable() {
1596 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1597 let result = env
1598 .proxy
1599 .get_backing_memory(fio::VmoFlags::EXECUTE)
1600 .await
1601 .unwrap()
1602 .map_err(Status::err_from_raw);
1603 #[cfg(target_os = "fuchsia")]
1605 assert_eq!(result, Err(Status::ACCESS_DENIED));
1606 #[cfg(not(target_os = "fuchsia"))]
1607 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1608 let events = env.file.operations.lock();
1609 assert_eq!(
1610 *events,
1611 vec![FileOperation::Init {
1612 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1613 },]
1614 );
1615 }
1616
1617 #[fuchsia::test]
1618 async fn test_get_flags() {
1619 let env = init_mock_file(
1620 Box::new(always_succeed_callback),
1621 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1622 );
1623 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::err_from_raw).unwrap();
1624 assert_eq!(flags, FLAGS_RW | fio::Flags::PROTOCOL_FILE);
1626 let events = env.file.operations.lock();
1627 assert_eq!(
1628 *events,
1629 vec![
1630 FileOperation::Init {
1631 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1632 },
1633 FileOperation::Truncate { length: 0 }
1634 ]
1635 );
1636 }
1637
1638 #[fuchsia::test]
1639 async fn test_open_flag_send_representation() {
1640 let env = init_mock_file(
1641 Box::new(always_succeed_callback),
1642 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
1643 );
1644 let event = env.proxy.take_event_stream().try_next().await.unwrap();
1645 match event {
1646 Some(fio::FileEvent::OnRepresentation { payload }) => {
1647 assert_eq!(
1648 payload,
1649 fio::Representation::File(fio::FileInfo {
1650 is_append: Some(false),
1651 ..Default::default()
1652 })
1653 );
1654 }
1655 e => panic!(
1656 "Expected OnRepresentation event with fio::Representation::File, got {:?}",
1657 e
1658 ),
1659 }
1660 let events = env.file.operations.lock();
1661 assert_eq!(
1662 *events,
1663 vec![FileOperation::Init {
1664 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true },
1665 }]
1666 );
1667 }
1668
1669 #[fuchsia::test]
1670 async fn test_read_succeeds() {
1671 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1672 let data = env.proxy.read(10).await.unwrap().map_err(Status::err_from_raw).unwrap();
1673 assert_eq!(data, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1674
1675 let events = env.file.operations.lock();
1676 assert_eq!(
1677 *events,
1678 vec![
1679 FileOperation::Init {
1680 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1681 },
1682 FileOperation::ReadAt { offset: 0, count: 10 },
1683 ]
1684 );
1685 }
1686
1687 #[fuchsia::test]
1688 async fn test_read_not_readable() {
1689 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_WRITABLE);
1690 let result = env.proxy.read(10).await.unwrap().map_err(Status::err_from_raw);
1691 assert_eq!(result, Err(Status::BAD_HANDLE));
1692 }
1693
1694 #[fuchsia::test]
1695 async fn test_read_validates_count() {
1696 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1697 let result =
1698 env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::err_from_raw);
1699 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1700 }
1701
1702 #[fuchsia::test]
1703 async fn test_read_at_succeeds() {
1704 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1705 let data = env.proxy.read_at(5, 10).await.unwrap().map_err(Status::err_from_raw).unwrap();
1706 assert_eq!(data, vec![10, 11, 12, 13, 14]);
1707
1708 let events = env.file.operations.lock();
1709 assert_eq!(
1710 *events,
1711 vec![
1712 FileOperation::Init {
1713 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1714 },
1715 FileOperation::ReadAt { offset: 10, count: 5 },
1716 ]
1717 );
1718 }
1719
1720 #[fuchsia::test]
1721 async fn test_read_at_validates_count() {
1722 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1723 let result = env
1724 .proxy
1725 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
1726 .await
1727 .unwrap()
1728 .map_err(Status::err_from_raw);
1729 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1730 }
1731
1732 #[fuchsia::test]
1733 async fn test_seek_start() {
1734 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1735 let offset = env
1736 .proxy
1737 .seek(fio::SeekOrigin::Start, 10)
1738 .await
1739 .unwrap()
1740 .map_err(Status::err_from_raw)
1741 .unwrap();
1742 assert_eq!(offset, 10);
1743
1744 let data = env.proxy.read(1).await.unwrap().map_err(Status::err_from_raw).unwrap();
1745 assert_eq!(data, vec![10]);
1746 let events = env.file.operations.lock();
1747 assert_eq!(
1748 *events,
1749 vec![
1750 FileOperation::Init {
1751 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1752 },
1753 FileOperation::ReadAt { offset: 10, count: 1 },
1754 ]
1755 );
1756 }
1757
1758 #[fuchsia::test]
1759 async fn test_seek_cur() {
1760 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1761 let offset = env
1762 .proxy
1763 .seek(fio::SeekOrigin::Start, 10)
1764 .await
1765 .unwrap()
1766 .map_err(Status::err_from_raw)
1767 .unwrap();
1768 assert_eq!(offset, 10);
1769
1770 let offset = env
1771 .proxy
1772 .seek(fio::SeekOrigin::Current, -2)
1773 .await
1774 .unwrap()
1775 .map_err(Status::err_from_raw)
1776 .unwrap();
1777 assert_eq!(offset, 8);
1778
1779 let data = env.proxy.read(1).await.unwrap().map_err(Status::err_from_raw).unwrap();
1780 assert_eq!(data, vec![8]);
1781 let events = env.file.operations.lock();
1782 assert_eq!(
1783 *events,
1784 vec![
1785 FileOperation::Init {
1786 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1787 },
1788 FileOperation::ReadAt { offset: 8, count: 1 },
1789 ]
1790 );
1791 }
1792
1793 #[fuchsia::test]
1794 async fn test_seek_before_start() {
1795 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1796 let result = env
1797 .proxy
1798 .seek(fio::SeekOrigin::Current, -4)
1799 .await
1800 .unwrap()
1801 .map_err(Status::err_from_raw);
1802 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1803 }
1804
1805 #[fuchsia::test]
1806 async fn test_seek_end() {
1807 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1808 let offset = env
1809 .proxy
1810 .seek(fio::SeekOrigin::End, -4)
1811 .await
1812 .unwrap()
1813 .map_err(Status::err_from_raw)
1814 .unwrap();
1815 assert_eq!(offset, MOCK_FILE_SIZE - 4);
1816
1817 let data = env.proxy.read(1).await.unwrap().map_err(Status::err_from_raw).unwrap();
1818 assert_eq!(data, vec![(offset % 256) as u8]);
1819 let events = env.file.operations.lock();
1820 assert_eq!(
1821 *events,
1822 vec![
1823 FileOperation::Init {
1824 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1825 },
1826 FileOperation::GetSize, FileOperation::ReadAt { offset, count: 1 },
1828 ]
1829 );
1830 }
1831
1832 #[fuchsia::test]
1833 async fn test_update_attributes() {
1834 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1835 let attributes = fio::MutableNodeAttributes {
1836 creation_time: Some(40000),
1837 modification_time: Some(100000),
1838 mode: Some(1),
1839 ..Default::default()
1840 };
1841 let () = env
1842 .proxy
1843 .update_attributes(&attributes)
1844 .await
1845 .unwrap()
1846 .map_err(Status::err_from_raw)
1847 .unwrap();
1848
1849 let events = env.file.operations.lock();
1850 assert_eq!(
1851 *events,
1852 vec![
1853 FileOperation::Init {
1854 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1855 },
1856 FileOperation::UpdateAttributes { attrs: attributes },
1857 ]
1858 );
1859 }
1860
1861 #[fuchsia::test]
1862 async fn test_set_flags() {
1863 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1864 env.proxy
1865 .set_flags(fio::Flags::FILE_APPEND)
1866 .await
1867 .unwrap()
1868 .map_err(Status::err_from_raw)
1869 .unwrap();
1870 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::err_from_raw).unwrap();
1871 assert_eq!(flags, FLAGS_W | fio::Flags::FILE_APPEND | fio::Flags::PROTOCOL_FILE);
1872 }
1873
1874 #[fuchsia::test]
1875 async fn test_sync() {
1876 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1877 let () = env.proxy.sync().await.unwrap().map_err(Status::err_from_raw).unwrap();
1878 let events = env.file.operations.lock();
1879 assert_eq!(
1880 *events,
1881 vec![
1882 FileOperation::Init {
1883 options: FileOptions {
1884 rights: fio::Operations::empty(),
1885 is_append: false,
1886 is_linkable: true
1887 }
1888 },
1889 FileOperation::Sync
1890 ]
1891 );
1892 }
1893
1894 #[fuchsia::test]
1895 async fn test_resize() {
1896 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1897 let () = env.proxy.resize(10).await.unwrap().map_err(Status::err_from_raw).unwrap();
1898 let events = env.file.operations.lock();
1899 assert_matches!(
1900 &events[..],
1901 [
1902 FileOperation::Init {
1903 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1904 },
1905 FileOperation::Truncate { length: 10 },
1906 ]
1907 );
1908 }
1909
1910 #[fuchsia::test]
1911 async fn test_resize_no_perms() {
1912 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1913 let result = env.proxy.resize(10).await.unwrap().map_err(Status::err_from_raw);
1914 assert_eq!(result, Err(Status::BAD_HANDLE));
1915 let events = env.file.operations.lock();
1916 assert_eq!(
1917 *events,
1918 vec![FileOperation::Init {
1919 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1920 },]
1921 );
1922 }
1923
1924 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1925 #[fuchsia::test]
1926 async fn test_allocate() {
1927 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1928 let () = env
1929 .proxy
1930 .allocate(0, 10, fio::AllocateMode::empty())
1931 .await
1932 .unwrap()
1933 .map_err(Status::err_from_raw)
1934 .unwrap();
1935 let events = env.file.operations.lock();
1936 assert_eq!(
1937 *events,
1938 vec![
1939 FileOperation::Init {
1940 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1941 },
1942 FileOperation::Allocate { offset: 0, length: 10, mode: fio::AllocateMode::empty() },
1943 ]
1944 );
1945 }
1946
1947 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1948 #[fuchsia::test]
1949 async fn test_allocate_no_perms() {
1950 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1951 let result = env
1952 .proxy
1953 .allocate(0, 10, fio::AllocateMode::empty())
1954 .await
1955 .unwrap()
1956 .map_err(Status::err_from_raw);
1957 assert_eq!(result, Err(Status::BAD_HANDLE));
1958 let events = env.file.operations.lock();
1959 assert_eq!(
1960 *events,
1961 vec![FileOperation::Init {
1962 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1963 },]
1964 );
1965 }
1966
1967 #[fuchsia::test]
1968 async fn test_write() {
1969 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1970 let data = "Hello, world!".as_bytes();
1971 let count = env.proxy.write(data).await.unwrap().map_err(Status::err_from_raw).unwrap();
1972 assert_eq!(count, data.len() as u64);
1973 let events = env.file.operations.lock();
1974 assert_matches!(
1975 &events[..],
1976 [
1977 FileOperation::Init {
1978 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1979 },
1980 FileOperation::WriteAt { offset: 0, .. },
1981 ]
1982 );
1983 if let FileOperation::WriteAt { content, .. } = &events[1] {
1984 assert_eq!(content.as_slice(), data);
1985 } else {
1986 unreachable!();
1987 }
1988 }
1989
1990 #[fuchsia::test]
1991 async fn test_write_no_perms() {
1992 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1993 let data = "Hello, world!".as_bytes();
1994 let result = env.proxy.write(data).await.unwrap().map_err(Status::err_from_raw);
1995 assert_eq!(result, Err(Status::BAD_HANDLE));
1996 let events = env.file.operations.lock();
1997 assert_eq!(
1998 *events,
1999 vec![FileOperation::Init {
2000 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2001 },]
2002 );
2003 }
2004
2005 #[fuchsia::test]
2006 async fn test_write_at() {
2007 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
2008 let data = "Hello, world!".as_bytes();
2009 let count =
2010 env.proxy.write_at(data, 10).await.unwrap().map_err(Status::err_from_raw).unwrap();
2011 assert_eq!(count, data.len() as u64);
2012 let events = env.file.operations.lock();
2013 assert_matches!(
2014 &events[..],
2015 [
2016 FileOperation::Init {
2017 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2018 },
2019 FileOperation::WriteAt { offset: 10, .. },
2020 ]
2021 );
2022 if let FileOperation::WriteAt { content, .. } = &events[1] {
2023 assert_eq!(content.as_slice(), data);
2024 } else {
2025 unreachable!();
2026 }
2027 }
2028
2029 #[fuchsia::test]
2030 async fn test_append() {
2031 let env = init_mock_file(
2032 Box::new(always_succeed_callback),
2033 fio::PERM_WRITABLE | fio::Flags::FILE_APPEND,
2034 );
2035 let data = "Hello, world!".as_bytes();
2036 let count = env.proxy.write(data).await.unwrap().map_err(Status::err_from_raw).unwrap();
2037 assert_eq!(count, data.len() as u64);
2038 let offset = env
2039 .proxy
2040 .seek(fio::SeekOrigin::Current, 0)
2041 .await
2042 .unwrap()
2043 .map_err(Status::err_from_raw)
2044 .unwrap();
2045 assert_eq!(offset, MOCK_FILE_SIZE + data.len() as u64);
2046 let events = env.file.operations.lock();
2047 assert_matches!(
2048 &events[..],
2049 [
2050 FileOperation::Init {
2051 options: FileOptions { rights: RIGHTS_W, is_append: true, .. }
2052 },
2053 FileOperation::Append { .. }
2054 ]
2055 );
2056 if let FileOperation::Append { content } = &events[1] {
2057 assert_eq!(content.as_slice(), data);
2058 } else {
2059 unreachable!();
2060 }
2061 }
2062
2063 #[cfg(target_os = "fuchsia")]
2064 mod stream_tests {
2065 use super::*;
2066
2067 fn init_mock_stream_file(vmo: zx::Vmo, flags: fio::Flags) -> TestEnv {
2068 let file = MockFile::new_with_vmo(Box::new(always_succeed_callback), vmo);
2069 #[cfg(feature = "fdomain")]
2070 let scope =
2071 crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
2072 #[cfg(not(feature = "fdomain"))]
2073 let scope = crate::execution_scope::ExecutionScope::new();
2074
2075 let (proxy, server_end) = scope.domain().create_proxy::<fio::FileMarker>();
2076
2077 let cloned_file = file.clone();
2078 let cloned_scope = scope.clone();
2079
2080 flags.to_object_request(server_end).create_connection_sync::<StreamIoConnection<_>, _>(
2081 cloned_scope,
2082 cloned_file,
2083 flags,
2084 );
2085
2086 TestEnv { file, proxy, scope }
2087 }
2088
2089 #[fuchsia::test]
2090 async fn test_stream_describe() {
2091 const VMO_CONTENTS: &[u8] = b"hello there";
2092 let vmo = zx::Vmo::create(VMO_CONTENTS.len() as u64).unwrap();
2093 vmo.write(VMO_CONTENTS, 0).unwrap();
2094 let flags = fio::PERM_READABLE | fio::PERM_WRITABLE;
2095 let env = init_mock_stream_file(vmo, flags);
2096
2097 let fio::FileInfo { stream: Some(stream), .. } = env.proxy.describe().await.unwrap()
2098 else {
2099 panic!("Missing stream")
2100 };
2101 let contents =
2102 stream.read_to_vec(zx::StreamReadOptions::empty(), 20).expect("read failed");
2103 assert_eq!(contents, VMO_CONTENTS);
2104 }
2105
2106 #[fuchsia::test]
2107 async fn test_stream_read() {
2108 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2109 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2110 vmo.write(&vmo_contents, 0).unwrap();
2111 let flags = fio::PERM_READABLE;
2112 let env = init_mock_stream_file(vmo, flags);
2113
2114 let data = env
2115 .proxy
2116 .read(vmo_contents.len() as u64)
2117 .await
2118 .unwrap()
2119 .map_err(Status::err_from_raw)
2120 .unwrap();
2121 assert_eq!(data, vmo_contents);
2122
2123 let events = env.file.operations.lock();
2124 assert_eq!(
2125 *events,
2126 [FileOperation::Init {
2127 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2128 },]
2129 );
2130 }
2131
2132 #[fuchsia::test]
2133 async fn test_stream_read_at() {
2134 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2135 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2136 vmo.write(&vmo_contents, 0).unwrap();
2137 let flags = fio::PERM_READABLE;
2138 let env = init_mock_stream_file(vmo, flags);
2139
2140 const OFFSET: u64 = 4;
2141 let data = env
2142 .proxy
2143 .read_at((vmo_contents.len() as u64) - OFFSET, OFFSET)
2144 .await
2145 .unwrap()
2146 .map_err(Status::err_from_raw)
2147 .unwrap();
2148 assert_eq!(data, vmo_contents[OFFSET as usize..]);
2149
2150 let events = env.file.operations.lock();
2151 assert_eq!(
2152 *events,
2153 [FileOperation::Init {
2154 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2155 },]
2156 );
2157 }
2158
2159 #[fuchsia::test]
2160 async fn test_stream_write() {
2161 const DATA_SIZE: u64 = 10;
2162 let vmo = zx::Vmo::create(DATA_SIZE).unwrap();
2163 let flags = fio::PERM_WRITABLE;
2164 let env = init_mock_stream_file(
2165 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2166 flags,
2167 );
2168
2169 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2170 let written =
2171 env.proxy.write(&data).await.unwrap().map_err(Status::err_from_raw).unwrap();
2172 assert_eq!(written, DATA_SIZE);
2173 let mut vmo_contents = [0; DATA_SIZE as usize];
2174 vmo.read(&mut vmo_contents, 0).unwrap();
2175 assert_eq!(vmo_contents, data);
2176
2177 let events = env.file.operations.lock();
2178 assert_eq!(
2179 *events,
2180 [FileOperation::Init {
2181 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2182 },]
2183 );
2184 }
2185
2186 #[fuchsia::test]
2187 async fn test_stream_write_at() {
2188 const OFFSET: u64 = 4;
2189 const DATA_SIZE: u64 = 10;
2190 let vmo = zx::Vmo::create(DATA_SIZE + OFFSET).unwrap();
2191 let flags = fio::PERM_WRITABLE;
2192 let env = init_mock_stream_file(
2193 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2194 flags,
2195 );
2196
2197 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2198 let written = env
2199 .proxy
2200 .write_at(&data, OFFSET)
2201 .await
2202 .unwrap()
2203 .map_err(Status::err_from_raw)
2204 .unwrap();
2205 assert_eq!(written, DATA_SIZE);
2206 let mut vmo_contents = [0; DATA_SIZE as usize];
2207 vmo.read(&mut vmo_contents, OFFSET).unwrap();
2208 assert_eq!(vmo_contents, data);
2209
2210 let events = env.file.operations.lock();
2211 assert_eq!(
2212 *events,
2213 [FileOperation::Init {
2214 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2215 }]
2216 );
2217 }
2218
2219 #[fuchsia::test]
2220 async fn test_stream_seek() {
2221 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2222 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2223 vmo.write(&vmo_contents, 0).unwrap();
2224 let flags = fio::PERM_READABLE;
2225 let env = init_mock_stream_file(vmo, flags);
2226
2227 let position = env
2228 .proxy
2229 .seek(fio::SeekOrigin::Start, 8)
2230 .await
2231 .unwrap()
2232 .map_err(Status::err_from_raw)
2233 .unwrap();
2234 assert_eq!(position, 8);
2235 let data = env.proxy.read(2).await.unwrap().map_err(Status::err_from_raw).unwrap();
2236 assert_eq!(data, [1, 0]);
2237
2238 let position = env
2239 .proxy
2240 .seek(fio::SeekOrigin::Current, -4)
2241 .await
2242 .unwrap()
2243 .map_err(Status::err_from_raw)
2244 .unwrap();
2245 assert_eq!(position, 6);
2247 let data = env.proxy.read(2).await.unwrap().map_err(Status::err_from_raw).unwrap();
2248 assert_eq!(data, [3, 2]);
2249
2250 let position = env
2251 .proxy
2252 .seek(fio::SeekOrigin::End, -6)
2253 .await
2254 .unwrap()
2255 .map_err(Status::err_from_raw)
2256 .unwrap();
2257 assert_eq!(position, 4);
2258 let data = env.proxy.read(2).await.unwrap().map_err(Status::err_from_raw).unwrap();
2259 assert_eq!(data, [5, 4]);
2260
2261 let e = env
2262 .proxy
2263 .seek(fio::SeekOrigin::Start, -1)
2264 .await
2265 .unwrap()
2266 .map_err(Status::err_from_raw)
2267 .expect_err("Seeking before the start of a file should be an error");
2268 assert_eq!(e, Status::INVALID_ARGS);
2269 }
2270
2271 #[fuchsia::test]
2272 async fn test_stream_set_flags() {
2273 let data = [0, 1, 2, 3, 4];
2274 let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 100).unwrap();
2275 let flags = fio::PERM_WRITABLE;
2276 let env = init_mock_stream_file(
2277 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2278 flags,
2279 );
2280
2281 let written =
2282 env.proxy.write(&data).await.unwrap().map_err(Status::err_from_raw).unwrap();
2283 assert_eq!(written, data.len() as u64);
2284 assert_eq!(vmo.get_content_size().unwrap(), 100);
2286
2287 env.proxy
2289 .set_flags(fio::Flags::FILE_APPEND)
2290 .await
2291 .unwrap()
2292 .map_err(Status::err_from_raw)
2293 .unwrap();
2294 env.proxy
2295 .seek(fio::SeekOrigin::Start, 0)
2296 .await
2297 .unwrap()
2298 .map_err(Status::err_from_raw)
2299 .unwrap();
2300 let written =
2301 env.proxy.write(&data).await.unwrap().map_err(Status::err_from_raw).unwrap();
2302 assert_eq!(written, data.len() as u64);
2303 assert_eq!(vmo.get_content_size().unwrap(), 105);
2305
2306 env.proxy
2308 .set_flags(fio::Flags::empty())
2309 .await
2310 .unwrap()
2311 .map_err(Status::err_from_raw)
2312 .unwrap();
2313 env.proxy
2314 .seek(fio::SeekOrigin::Start, 0)
2315 .await
2316 .unwrap()
2317 .map_err(Status::err_from_raw)
2318 .unwrap();
2319 let written =
2320 env.proxy.write(&data).await.unwrap().map_err(Status::err_from_raw).unwrap();
2321 assert_eq!(written, data.len() as u64);
2322 assert_eq!(vmo.get_content_size().unwrap(), 105);
2324 }
2325
2326 #[fuchsia::test]
2327 async fn test_stream_read_validates_count() {
2328 let vmo = zx::Vmo::create(10).unwrap();
2329 let flags = fio::PERM_READABLE;
2330 let env = init_mock_stream_file(vmo, flags);
2331 let result = env
2332 .proxy
2333 .read(fio::MAX_TRANSFER_SIZE + 1)
2334 .await
2335 .unwrap()
2336 .map_err(Status::err_from_raw);
2337 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2338 }
2339
2340 #[fuchsia::test]
2341 async fn test_stream_read_at_validates_count() {
2342 let vmo = zx::Vmo::create(10).unwrap();
2343 let flags = fio::PERM_READABLE;
2344 let env = init_mock_stream_file(vmo, flags);
2345 let result = env
2346 .proxy
2347 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
2348 .await
2349 .unwrap()
2350 .map_err(Status::err_from_raw);
2351 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2352 }
2353 }
2354}