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 let attrs = self.file.get_attributes(query).await;
647 responder.send(
648 attrs
649 .as_ref()
650 .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
651 .map_err(|status| status.into_raw()),
652 )
653 }
654 .trace(trace::trace_future_args!("storage", "File::GetAttributes"))
655 .await?;
656 }
657 fio::FileRequest::UpdateAttributes { payload, responder } => {
658 async move {
659 let result =
660 self.handle_update_attributes(payload).await.map_err(Status::into_raw);
661 responder.send(result)
662 }
663 .trace(trace::trace_future_args!("storage", "File::UpdateAttributes"))
664 .await?;
665 }
666 fio::FileRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
667 self.handle_list_extended_attribute(iterator)
668 .trace(trace::trace_future_args!("storage", "File::ListExtendedAttributes"))
669 .await;
670 }
671 fio::FileRequest::GetExtendedAttribute { name, responder } => {
672 async move {
673 let res =
674 self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
675 responder.send(res)
676 }
677 .trace(trace::trace_future_args!("storage", "File::GetExtendedAttribute"))
678 .await?;
679 }
680 fio::FileRequest::SetExtendedAttribute { name, value, mode, responder } => {
681 async move {
682 let res = self
683 .handle_set_extended_attribute(name, value, mode)
684 .await
685 .map_err(Status::into_raw);
686 responder.send(res)
687 }
688 .trace(trace::trace_future_args!("storage", "File::SetExtendedAttribute"))
689 .await?;
690 }
691 fio::FileRequest::RemoveExtendedAttribute { name, responder } => {
692 async move {
693 let res =
694 self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
695 responder.send(res)
696 }
697 .trace(trace::trace_future_args!("storage", "File::RemoveExtendedAttribute"))
698 .await?;
699 }
700 #[cfg(fuchsia_api_level_at_least = "HEAD")]
701 fio::FileRequest::EnableVerity { options, responder } => {
702 async move {
703 let res = self.handle_enable_verity(options).await.map_err(Status::into_raw);
704 responder.send(res)
705 }
706 .trace(trace::trace_future_args!("storage", "File::EnableVerity"))
707 .await?;
708 }
709 fio::FileRequest::Read { count, responder } => {
710 let trace_args =
711 trace::trace_future_args!("storage", "File::Read", "bytes" => count);
712 async move {
713 let result = self.handle_read(count).await;
714 responder.send(result.as_deref().map_err(|s| s.into_raw()))
715 }
716 .trace(trace_args)
717 .await?;
718 }
719 fio::FileRequest::ReadAt { offset, count, responder } => {
720 let trace_args = trace::trace_future_args!(
721 "storage",
722 "File::ReadAt",
723 "offset" => offset,
724 "bytes" => count
725 );
726 async move {
727 let result = self.handle_read_at(offset, count).await;
728 responder.send(result.as_deref().map_err(|s| s.into_raw()))
729 }
730 .trace(trace_args)
731 .await?;
732 }
733 fio::FileRequest::Write { data, responder } => {
734 let trace_args =
735 trace::trace_future_args!("storage", "File::Write", "bytes" => data.len());
736 async move {
737 let result = self.handle_write(data).await;
738 responder.send(result.map_err(Status::into_raw))
739 }
740 .trace(trace_args)
741 .await?;
742 }
743 fio::FileRequest::WriteAt { offset, data, responder } => {
744 let trace_args = trace::trace_future_args!(
745 "storage",
746 "File::WriteAt",
747 "offset" => offset,
748 "bytes" => data.len()
749 );
750 async move {
751 let result = self.handle_write_at(offset, data).await;
752 responder.send(result.map_err(Status::into_raw))
753 }
754 .trace(trace_args)
755 .await?;
756 }
757 fio::FileRequest::Seek { origin, offset, responder } => {
758 async move {
759 let result = self.handle_seek(offset, origin).await;
760 responder.send(result.map_err(Status::into_raw))
761 }
762 .trace(trace::trace_future_args!("storage", "File::Seek"))
763 .await?;
764 }
765 fio::FileRequest::Resize { length, responder } => {
766 async move {
767 let result = self.handle_truncate(length).await;
768 responder.send(result.map_err(Status::into_raw))
769 }
770 .trace(trace::trace_future_args!("storage", "File::Resize"))
771 .await?;
772 }
773 fio::FileRequest::GetFlags { responder } => {
774 trace::duration!("storage", "File::GetFlags");
775 responder.send(Ok(fio::Flags::from(&self.options)))?;
776 }
777 fio::FileRequest::SetFlags { flags, responder } => {
778 trace::duration!("storage", "File::SetFlags");
779 if flags.is_empty() || flags == fio::Flags::FILE_APPEND {
781 self.options.is_append = flags.contains(fio::Flags::FILE_APPEND);
782 responder.send(self.file.set_flags(flags).map_err(Status::into_raw))?;
783 } else {
784 responder.send(Err(Status::INVALID_ARGS.into_raw()))?;
785 }
786 }
787 fio::FileRequest::DeprecatedGetFlags { responder } => {
788 trace::duration!("storage", "File::DeprecatedGetFlags");
789 responder.send(Status::OK.into_raw(), self.options.to_io1())?;
790 }
791 fio::FileRequest::DeprecatedSetFlags { flags, responder } => {
792 trace::duration!("storage", "File::DeprecatedSetFlags");
793 let is_append = flags.contains(fio::OpenFlags::APPEND);
795 self.options.is_append = is_append;
796 let flags = if is_append { fio::Flags::FILE_APPEND } else { fio::Flags::empty() };
797 responder.send(Status::from_result(self.file.set_flags(flags)).into_raw())?;
798 }
799 #[cfg(target_os = "fuchsia")]
800 fio::FileRequest::GetBackingMemory { flags, responder } => {
801 async move {
802 let result = self.handle_get_backing_memory(flags).await;
803 responder.send(result.map_err(Status::into_raw))
804 }
805 .trace(trace::trace_future_args!("storage", "File::GetBackingMemory"))
806 .await?;
807 }
808
809 #[cfg(not(target_os = "fuchsia"))]
810 fio::FileRequest::GetBackingMemory { flags: _, responder } => {
811 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
812 }
813 fio::FileRequest::AdvisoryLock { request: _, responder } => {
814 trace::duration!("storage", "File::AdvisoryLock");
815 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
816 }
817 fio::FileRequest::Query { responder } => {
818 trace::duration!("storage", "File::Query");
819 responder.send(fio::FileMarker::PROTOCOL_NAME.as_bytes())?;
820 }
821 fio::FileRequest::QueryFilesystem { responder } => {
822 trace::duration!("storage", "File::QueryFilesystem");
823 match self.file.query_filesystem() {
824 Err(status) => responder.send(status.into_raw(), None)?,
825 Ok(info) => responder.send(0, Some(&info))?,
826 }
827 }
828 #[cfg(fuchsia_api_level_at_least = "HEAD")]
829 fio::FileRequest::Allocate { offset, length, mode, responder } => {
830 async move {
831 let result = self.handle_allocate(offset, length, mode).await;
832 responder.send(result.map_err(Status::into_raw))
833 }
834 .trace(trace::trace_future_args!("storage", "File::Allocate"))
835 .await?;
836 }
837 fio::FileRequest::_UnknownMethod { .. } => (),
838 }
839 Ok(ConnectionState::Alive)
840 }
841
842 fn handle_clone(&mut self, server_end: ServerEnd<fio::FileMarker>) {
843 let connection = match self.file.clone_connection(self.options) {
844 Ok(file) => Self { scope: self.scope.clone(), file, options: self.options },
845 Err(status) => {
846 let _ = server_end.close_with_epitaph(status);
847 return;
848 }
849 };
850 self.scope.spawn(RequestListener::new(server_end.into_stream(), Some(connection)));
851 }
852
853 async fn handle_read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
854 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
855 return Err(Status::BAD_HANDLE);
856 }
857
858 if count > fio::MAX_TRANSFER_SIZE {
859 return Err(Status::OUT_OF_RANGE);
860 }
861 self.file.read(count).await
862 }
863
864 async fn handle_read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
865 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
866 return Err(Status::BAD_HANDLE);
867 }
868 if count > fio::MAX_TRANSFER_SIZE {
869 return Err(Status::OUT_OF_RANGE);
870 }
871 self.file.read_at(offset, count).await
872 }
873
874 async fn handle_write(&mut self, content: Vec<u8>) -> Result<u64, Status> {
875 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
876 return Err(Status::BAD_HANDLE);
877 }
878 self.file.write(content).await
879 }
880
881 async fn handle_write_at(&self, offset: u64, content: Vec<u8>) -> Result<u64, Status> {
882 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
883 return Err(Status::BAD_HANDLE);
884 }
885
886 self.file.write_at(offset, content).await
887 }
888
889 async fn handle_seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
891 self.file.seek(offset, origin).await
892 }
893
894 async fn handle_update_attributes(
895 &mut self,
896 attributes: fio::MutableNodeAttributes,
897 ) -> Result<(), Status> {
898 if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
899 return Err(Status::BAD_HANDLE);
900 }
901
902 self.file.update_attributes(attributes).await
903 }
904
905 #[cfg(fuchsia_api_level_at_least = "HEAD")]
906 async fn handle_enable_verity(
907 &mut self,
908 options: fio::VerificationOptions,
909 ) -> Result<(), Status> {
910 if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
911 return Err(Status::BAD_HANDLE);
912 }
913 self.file.enable_verity(options).await
914 }
915
916 async fn handle_truncate(&mut self, length: u64) -> Result<(), Status> {
917 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
918 return Err(Status::BAD_HANDLE);
919 }
920
921 self.file.truncate(length).await
922 }
923
924 #[cfg(target_os = "fuchsia")]
925 async fn handle_get_backing_memory(&mut self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
926 get_backing_memory_validate_flags(flags, self.options)?;
927 self.file.get_backing_memory(flags).await
928 }
929
930 async fn handle_list_extended_attribute(
931 &mut self,
932 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
933 ) {
934 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
935 let _ = iterator.close_with_epitaph(Status::BAD_HANDLE);
936 return;
937 }
938 let attributes = match self.file.list_extended_attributes().await {
939 Ok(attributes) => attributes,
940 Err(status) => {
941 #[cfg(any(test, feature = "use_log"))]
942 log::error!(status:?; "list extended attributes failed");
943 #[allow(clippy::unnecessary_lazy_evaluations)]
944 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
945 #[cfg(any(test, feature = "use_log"))]
946 log::error!(_error:?; "failed to send epitaph")
947 });
948 return;
949 }
950 };
951 self.scope.spawn(extended_attributes_sender(iterator, attributes));
952 }
953
954 async fn handle_get_extended_attribute(
955 &mut self,
956 name: Vec<u8>,
957 ) -> Result<fio::ExtendedAttributeValue, Status> {
958 if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
959 return Err(Status::BAD_HANDLE);
960 }
961 let value = self.file.get_extended_attribute(name).await?;
962 encode_extended_attribute_value(value)
963 }
964
965 async fn handle_set_extended_attribute(
966 &mut self,
967 name: Vec<u8>,
968 value: fio::ExtendedAttributeValue,
969 mode: fio::SetExtendedAttributeMode,
970 ) -> Result<(), Status> {
971 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
972 return Err(Status::BAD_HANDLE);
973 }
974 if name.contains(&0) {
975 return Err(Status::INVALID_ARGS);
976 }
977 let val = decode_extended_attribute_value(value)?;
978 self.file.set_extended_attribute(name, val, mode).await
979 }
980
981 async fn handle_remove_extended_attribute(&mut self, name: Vec<u8>) -> Result<(), Status> {
982 if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
983 return Err(Status::BAD_HANDLE);
984 }
985 self.file.remove_extended_attribute(name).await
986 }
987
988 async fn handle_link_into(
989 &mut self,
990 target_parent_token: flex_client::Event,
991 target_name: String,
992 ) -> Result<(), Status> {
993 let target_name = Name::try_from(target_name).map_err(|_| Status::INVALID_ARGS)?;
994
995 #[cfg(fuchsia_api_level_at_least = "HEAD")]
996 if !self.options.is_linkable {
997 return Err(Status::NOT_FOUND);
998 }
999
1000 if !self.options.rights.contains(
1001 fio::Operations::READ_BYTES
1002 | fio::Operations::WRITE_BYTES
1003 | fio::Operations::GET_ATTRIBUTES
1004 | fio::Operations::UPDATE_ATTRIBUTES,
1005 ) {
1006 return Err(Status::ACCESS_DENIED);
1007 }
1008
1009 let (target_parent, target_rights) = self
1010 .scope
1011 .token_registry()
1012 .get_owner_and_rights(target_parent_token.into())?
1013 .ok_or(Err(Status::NOT_FOUND))?;
1014
1015 if !target_rights.contains(fio::Rights::MODIFY_DIRECTORY) {
1016 return Err(Status::ACCESS_DENIED);
1017 }
1018
1019 self.file.clone().link_into(target_parent, target_name).await
1020 }
1021
1022 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1023 async fn handle_allocate(
1024 &mut self,
1025 offset: u64,
1026 length: u64,
1027 mode: fio::AllocateMode,
1028 ) -> Result<(), Status> {
1029 self.file.allocate(offset, length, mode).await
1030 }
1031
1032 fn should_sync_before_close(&self) -> bool {
1033 self.options
1034 .rights
1035 .intersects(fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES)
1036 }
1037}
1038
1039impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin>
1042 RequestHandler for Option<FileConnection<U>>
1043{
1044 type Request = Result<fio::FileRequest, fidl::Error>;
1045
1046 async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
1047 let option_this = self.get_mut();
1048 let this = option_this.as_mut().unwrap();
1049 let Some(_guard) = this.scope.try_active_guard() else { return ControlFlow::Break(()) };
1050 let state = match request {
1051 Ok(request) => {
1052 this.handle_request(request)
1053 .await
1054 .unwrap_or(ConnectionState::Dropped)
1057 }
1058 Err(_) => {
1059 ConnectionState::Dropped
1063 }
1064 };
1065 match state {
1066 ConnectionState::Alive => ControlFlow::Continue(()),
1067 ConnectionState::Dropped => {
1068 if this.should_sync_before_close() {
1069 let _ = this.file.sync(SyncMode::PreClose).await;
1070 }
1071 ControlFlow::Break(())
1072 }
1073 ConnectionState::Closed(responder) => {
1074 async move {
1075 let this = option_this.as_mut().unwrap();
1076 let _ = responder.send({
1077 let result = if this.should_sync_before_close() {
1078 this.file.sync(SyncMode::PreClose).await.map_err(Status::into_raw)
1079 } else {
1080 Ok(())
1081 };
1082 std::mem::drop(option_this.take());
1085 result
1086 });
1087 }
1088 .trace(trace::trace_future_args!("storage", "File::Close"))
1089 .await;
1090 ControlFlow::Break(())
1091 }
1092 }
1093 }
1094
1095 async fn stream_closed(self: Pin<&mut Self>) {
1096 let this = self.get_mut().as_mut().unwrap();
1097 if this.should_sync_before_close() {
1098 if let Some(_guard) = this.scope.try_active_guard() {
1099 let _ = this.file.sync(SyncMode::PreClose).await;
1100 }
1101 }
1102 }
1103}
1104
1105impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + IoOpHandler> Representation
1106 for FileConnection<U>
1107{
1108 type Protocol = fio::FileMarker;
1109
1110 async fn get_representation(
1111 &self,
1112 requested_attributes: fio::NodeAttributesQuery,
1113 ) -> Result<fio::Representation, Status> {
1114 Ok(fio::Representation::File(fio::FileInfo {
1116 is_append: Some(self.options.is_append),
1117 #[cfg(target_os = "fuchsia")]
1118 stream: self.file.duplicate_stream()?,
1119 #[cfg(not(target_os = "fuchsia"))]
1120 stream: None,
1121 attributes: if requested_attributes.is_empty() {
1122 None
1123 } else {
1124 Some(self.file.get_attributes(requested_attributes).await?)
1125 },
1126 ..Default::default()
1127 }))
1128 }
1129
1130 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
1131 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
1132 #[cfg(target_os = "fuchsia")]
1133 let stream = self.file.duplicate_stream()?;
1134 #[cfg(not(target_os = "fuchsia"))]
1135 let stream = None;
1136 Ok(fio::NodeInfoDeprecated::File(fio::FileObject { event: None, stream }))
1137 }
1138}
1139
1140#[cfg(test)]
1141mod tests {
1142 use super::*;
1143 use crate::ToObjectRequest;
1144 use crate::directory::entry::{EntryInfo, GetEntryInfo};
1145 use crate::node::Node;
1146 use assert_matches::assert_matches;
1147 use fuchsia_sync::Mutex;
1148 use futures::prelude::*;
1149
1150 const RIGHTS_R: fio::Operations =
1151 fio::Operations::READ_BYTES.union(fio::Operations::GET_ATTRIBUTES);
1152 const RIGHTS_W: fio::Operations =
1153 fio::Operations::WRITE_BYTES.union(fio::Operations::UPDATE_ATTRIBUTES);
1154 const RIGHTS_RW: fio::Operations = fio::Operations::READ_BYTES
1155 .union(fio::Operations::WRITE_BYTES)
1156 .union(fio::Operations::GET_ATTRIBUTES)
1157 .union(fio::Operations::UPDATE_ATTRIBUTES);
1158
1159 const FLAGS_R: fio::Flags = fio::Flags::PERM_READ_BYTES.union(fio::Flags::PERM_GET_ATTRIBUTES);
1164 const FLAGS_W: fio::Flags =
1165 fio::Flags::PERM_WRITE_BYTES.union(fio::Flags::PERM_UPDATE_ATTRIBUTES);
1166 const FLAGS_RW: fio::Flags = FLAGS_R.union(FLAGS_W);
1167
1168 #[derive(Debug, PartialEq)]
1169 enum FileOperation {
1170 Init {
1171 options: FileOptions,
1172 },
1173 ReadAt {
1174 offset: u64,
1175 count: u64,
1176 },
1177 WriteAt {
1178 offset: u64,
1179 content: Vec<u8>,
1180 },
1181 Append {
1182 content: Vec<u8>,
1183 },
1184 Truncate {
1185 length: u64,
1186 },
1187 #[cfg(target_os = "fuchsia")]
1188 GetBackingMemory {
1189 flags: fio::VmoFlags,
1190 },
1191 GetSize,
1192 GetAttributes {
1193 query: fio::NodeAttributesQuery,
1194 },
1195 UpdateAttributes {
1196 attrs: fio::MutableNodeAttributes,
1197 },
1198 Close,
1199 Sync,
1200 }
1201
1202 type MockCallbackType = Box<dyn Fn(&FileOperation) -> Status + Sync + Send>;
1203 struct MockFile {
1205 operations: Mutex<Vec<FileOperation>>,
1207 callback: MockCallbackType,
1209 file_size: u64,
1211 #[cfg(target_os = "fuchsia")]
1212 vmo: zx::Vmo,
1214 }
1215
1216 const MOCK_FILE_SIZE: u64 = 256;
1217 const MOCK_FILE_ID: u64 = 10;
1218 const MOCK_FILE_LINKS: u64 = 2;
1219 const MOCK_FILE_CREATION_TIME: u64 = 10;
1220 const MOCK_FILE_MODIFICATION_TIME: u64 = 100;
1221 impl MockFile {
1222 fn new(callback: MockCallbackType) -> Arc<Self> {
1223 Arc::new(MockFile {
1224 operations: Mutex::new(Vec::new()),
1225 callback,
1226 file_size: MOCK_FILE_SIZE,
1227 #[cfg(target_os = "fuchsia")]
1228 vmo: zx::NullableHandle::invalid().into(),
1229 })
1230 }
1231
1232 #[cfg(target_os = "fuchsia")]
1233 fn new_with_vmo(callback: MockCallbackType, vmo: zx::Vmo) -> Arc<Self> {
1234 Arc::new(MockFile {
1235 operations: Mutex::new(Vec::new()),
1236 callback,
1237 file_size: MOCK_FILE_SIZE,
1238 vmo,
1239 })
1240 }
1241
1242 fn handle_operation(&self, operation: FileOperation) -> Result<(), Status> {
1243 let result = (self.callback)(&operation);
1244 self.operations.lock().push(operation);
1245 match result {
1246 Status::OK => Ok(()),
1247 err => Err(err),
1248 }
1249 }
1250 }
1251
1252 impl GetEntryInfo for MockFile {
1253 fn entry_info(&self) -> EntryInfo {
1254 EntryInfo::new(MOCK_FILE_ID, fio::DirentType::File)
1255 }
1256 }
1257
1258 impl Node for MockFile {
1259 async fn get_attributes(
1260 &self,
1261 query: fio::NodeAttributesQuery,
1262 ) -> Result<fio::NodeAttributes2, Status> {
1263 self.handle_operation(FileOperation::GetAttributes { query })?;
1264 Ok(attributes!(
1265 query,
1266 Mutable {
1267 creation_time: MOCK_FILE_CREATION_TIME,
1268 modification_time: MOCK_FILE_MODIFICATION_TIME,
1269 },
1270 Immutable {
1271 protocols: fio::NodeProtocolKinds::FILE,
1272 abilities: fio::Operations::GET_ATTRIBUTES
1273 | fio::Operations::UPDATE_ATTRIBUTES
1274 | fio::Operations::READ_BYTES
1275 | fio::Operations::WRITE_BYTES,
1276 content_size: self.file_size,
1277 storage_size: 2 * self.file_size,
1278 link_count: MOCK_FILE_LINKS,
1279 id: MOCK_FILE_ID,
1280 }
1281 ))
1282 }
1283
1284 fn close(self: Arc<Self>) {
1285 let _ = self.handle_operation(FileOperation::Close);
1286 }
1287 }
1288
1289 impl File for MockFile {
1290 fn writable(&self) -> bool {
1291 true
1292 }
1293
1294 async fn open_file(&self, options: &FileOptions) -> Result<(), Status> {
1295 self.handle_operation(FileOperation::Init { options: *options })?;
1296 Ok(())
1297 }
1298
1299 async fn truncate(&self, length: u64) -> Result<(), Status> {
1300 self.handle_operation(FileOperation::Truncate { length })
1301 }
1302
1303 #[cfg(target_os = "fuchsia")]
1304 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
1305 self.handle_operation(FileOperation::GetBackingMemory { flags })?;
1306 Err(Status::NOT_SUPPORTED)
1307 }
1308
1309 async fn get_size(&self) -> Result<u64, Status> {
1310 self.handle_operation(FileOperation::GetSize)?;
1311 Ok(self.file_size)
1312 }
1313
1314 async fn update_attributes(&self, attrs: fio::MutableNodeAttributes) -> Result<(), Status> {
1315 self.handle_operation(FileOperation::UpdateAttributes { attrs })?;
1316 Ok(())
1317 }
1318
1319 async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
1320 self.handle_operation(FileOperation::Sync)
1321 }
1322 }
1323
1324 impl FileIo for MockFile {
1325 async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
1326 let count = buffer.len() as u64;
1327 self.handle_operation(FileOperation::ReadAt { offset, count })?;
1328
1329 let mut i = offset;
1331 buffer.fill_with(|| {
1332 let v = (i % 256) as u8;
1333 i += 1;
1334 v
1335 });
1336 Ok(count)
1337 }
1338
1339 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
1340 self.handle_operation(FileOperation::WriteAt { offset, content: content.to_vec() })?;
1341 Ok(content.len() as u64)
1342 }
1343
1344 async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
1345 self.handle_operation(FileOperation::Append { content: content.to_vec() })?;
1346 Ok((content.len() as u64, self.file_size + content.len() as u64))
1347 }
1348 }
1349
1350 #[cfg(target_os = "fuchsia")]
1351 impl GetVmo for MockFile {
1352 fn get_vmo(&self) -> &zx::Vmo {
1353 &self.vmo
1354 }
1355 }
1356
1357 fn only_allow_init(op: &FileOperation) -> Status {
1359 match op {
1360 FileOperation::Init { .. } => Status::OK,
1361 _ => Status::IO,
1362 }
1363 }
1364
1365 fn always_succeed_callback(_op: &FileOperation) -> Status {
1367 Status::OK
1368 }
1369
1370 struct TestEnv {
1371 pub file: Arc<MockFile>,
1372 pub proxy: fio::FileProxy,
1373 pub scope: ExecutionScope,
1374 }
1375
1376 fn init_mock_file(callback: MockCallbackType, flags: fio::Flags) -> TestEnv {
1377 let file = MockFile::new(callback);
1378 #[cfg(feature = "fdomain")]
1379 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
1380 #[cfg(not(feature = "fdomain"))]
1381 let scope = crate::execution_scope::ExecutionScope::new();
1382
1383 let (proxy, server_end) = scope.domain().create_proxy::<fio::FileMarker>();
1384
1385 flags.to_object_request(server_end).create_connection_sync::<FidlIoConnection<_>, _>(
1386 scope.clone(),
1387 file.clone(),
1388 flags,
1389 );
1390
1391 TestEnv { file, proxy, scope }
1392 }
1393
1394 #[fuchsia::test]
1395 async fn test_open_flag_truncate() {
1396 let env = init_mock_file(
1397 Box::new(always_succeed_callback),
1398 fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1399 );
1400 let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1402 let events = env.file.operations.lock();
1403 assert_eq!(
1404 *events,
1405 vec![
1406 FileOperation::Init {
1407 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1408 },
1409 FileOperation::Truncate { length: 0 },
1410 FileOperation::Sync,
1411 ]
1412 );
1413 }
1414
1415 #[fuchsia::test]
1416 async fn test_close_succeeds() {
1417 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1418 let () = env.proxy.close().await.unwrap().map_err(Status::from_raw).unwrap();
1419
1420 let events = env.file.operations.lock();
1421 assert_eq!(
1422 *events,
1423 vec![
1424 FileOperation::Init {
1425 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1426 },
1427 FileOperation::Close {},
1428 ]
1429 );
1430 }
1431
1432 #[fuchsia::test]
1433 async fn test_close_fails() {
1434 let env =
1435 init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE | fio::PERM_WRITABLE);
1436 let status = env.proxy.close().await.unwrap().map_err(Status::from_raw);
1437 assert_eq!(status, Err(Status::IO));
1438
1439 let events = env.file.operations.lock();
1440 assert_eq!(
1441 *events,
1442 vec![
1443 FileOperation::Init {
1444 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1445 },
1446 FileOperation::Sync,
1447 FileOperation::Close,
1448 ]
1449 );
1450 }
1451
1452 #[fuchsia::test]
1453 async fn test_close_called_when_dropped() {
1454 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1455 let _ = env.proxy.sync().await;
1456 std::mem::drop(env.proxy);
1457 env.scope.shutdown();
1458 env.scope.wait().await;
1459 let events = env.file.operations.lock();
1460 assert_eq!(
1461 *events,
1462 vec![
1463 FileOperation::Init {
1464 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1465 },
1466 FileOperation::Sync,
1467 FileOperation::Close,
1468 ]
1469 );
1470 }
1471
1472 #[fuchsia::test]
1473 async fn test_query() {
1474 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1475 let protocol = env.proxy.query().await.unwrap();
1476 assert_eq!(protocol, fio::FileMarker::PROTOCOL_NAME.as_bytes());
1477 }
1478
1479 #[fuchsia::test]
1480 async fn test_get_attributes() {
1481 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1482 let (mutable_attributes, immutable_attributes) = env
1483 .proxy
1484 .get_attributes(fio::NodeAttributesQuery::all())
1485 .await
1486 .unwrap()
1487 .map_err(Status::from_raw)
1488 .unwrap();
1489 let expected = attributes!(
1490 fio::NodeAttributesQuery::all(),
1491 Mutable {
1492 creation_time: MOCK_FILE_CREATION_TIME,
1493 modification_time: MOCK_FILE_MODIFICATION_TIME,
1494 },
1495 Immutable {
1496 protocols: fio::NodeProtocolKinds::FILE,
1497 abilities: fio::Operations::GET_ATTRIBUTES
1498 | fio::Operations::UPDATE_ATTRIBUTES
1499 | fio::Operations::READ_BYTES
1500 | fio::Operations::WRITE_BYTES,
1501 content_size: MOCK_FILE_SIZE,
1502 storage_size: 2 * MOCK_FILE_SIZE,
1503 link_count: MOCK_FILE_LINKS,
1504 id: MOCK_FILE_ID,
1505 }
1506 );
1507 assert_eq!(mutable_attributes, expected.mutable_attributes);
1508 assert_eq!(immutable_attributes, expected.immutable_attributes);
1509
1510 let events = env.file.operations.lock();
1511 assert_eq!(
1512 *events,
1513 vec![
1514 FileOperation::Init {
1515 options: FileOptions {
1516 rights: fio::Operations::empty(),
1517 is_append: false,
1518 is_linkable: true
1519 }
1520 },
1521 FileOperation::GetAttributes { query: fio::NodeAttributesQuery::all() }
1522 ]
1523 );
1524 }
1525
1526 #[fuchsia::test]
1527 async fn test_getbuffer() {
1528 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1529 let result = env
1530 .proxy
1531 .get_backing_memory(fio::VmoFlags::READ)
1532 .await
1533 .unwrap()
1534 .map_err(Status::from_raw);
1535 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1536 let events = env.file.operations.lock();
1537 assert_eq!(
1538 *events,
1539 vec![
1540 FileOperation::Init {
1541 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1542 },
1543 #[cfg(target_os = "fuchsia")]
1544 FileOperation::GetBackingMemory { flags: fio::VmoFlags::READ },
1545 ]
1546 );
1547 }
1548
1549 #[fuchsia::test]
1550 async fn test_getbuffer_no_perms() {
1551 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1552 let result = env
1553 .proxy
1554 .get_backing_memory(fio::VmoFlags::READ)
1555 .await
1556 .unwrap()
1557 .map_err(Status::from_raw);
1558 #[cfg(target_os = "fuchsia")]
1560 assert_eq!(result, Err(Status::ACCESS_DENIED));
1561 #[cfg(not(target_os = "fuchsia"))]
1562 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1563 let events = env.file.operations.lock();
1564 assert_eq!(
1565 *events,
1566 vec![FileOperation::Init {
1567 options: FileOptions {
1568 rights: fio::Operations::empty(),
1569 is_append: false,
1570 is_linkable: true
1571 }
1572 },]
1573 );
1574 }
1575
1576 #[fuchsia::test]
1577 async fn test_getbuffer_vmo_exec_requires_right_executable() {
1578 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1579 let result = env
1580 .proxy
1581 .get_backing_memory(fio::VmoFlags::EXECUTE)
1582 .await
1583 .unwrap()
1584 .map_err(Status::from_raw);
1585 #[cfg(target_os = "fuchsia")]
1587 assert_eq!(result, Err(Status::ACCESS_DENIED));
1588 #[cfg(not(target_os = "fuchsia"))]
1589 assert_eq!(result, Err(Status::NOT_SUPPORTED));
1590 let events = env.file.operations.lock();
1591 assert_eq!(
1592 *events,
1593 vec![FileOperation::Init {
1594 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1595 },]
1596 );
1597 }
1598
1599 #[fuchsia::test]
1600 async fn test_get_flags() {
1601 let env = init_mock_file(
1602 Box::new(always_succeed_callback),
1603 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
1604 );
1605 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::from_raw).unwrap();
1606 assert_eq!(flags, FLAGS_RW | fio::Flags::PROTOCOL_FILE);
1608 let events = env.file.operations.lock();
1609 assert_eq!(
1610 *events,
1611 vec![
1612 FileOperation::Init {
1613 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1614 },
1615 FileOperation::Truncate { length: 0 }
1616 ]
1617 );
1618 }
1619
1620 #[fuchsia::test]
1621 async fn test_open_flag_send_representation() {
1622 let env = init_mock_file(
1623 Box::new(always_succeed_callback),
1624 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
1625 );
1626 let event = env.proxy.take_event_stream().try_next().await.unwrap();
1627 match event {
1628 Some(fio::FileEvent::OnRepresentation { payload }) => {
1629 assert_eq!(
1630 payload,
1631 fio::Representation::File(fio::FileInfo {
1632 is_append: Some(false),
1633 ..Default::default()
1634 })
1635 );
1636 }
1637 e => panic!(
1638 "Expected OnRepresentation event with fio::Representation::File, got {:?}",
1639 e
1640 ),
1641 }
1642 let events = env.file.operations.lock();
1643 assert_eq!(
1644 *events,
1645 vec![FileOperation::Init {
1646 options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true },
1647 }]
1648 );
1649 }
1650
1651 #[fuchsia::test]
1652 async fn test_read_succeeds() {
1653 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1654 let data = env.proxy.read(10).await.unwrap().map_err(Status::from_raw).unwrap();
1655 assert_eq!(data, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1656
1657 let events = env.file.operations.lock();
1658 assert_eq!(
1659 *events,
1660 vec![
1661 FileOperation::Init {
1662 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1663 },
1664 FileOperation::ReadAt { offset: 0, count: 10 },
1665 ]
1666 );
1667 }
1668
1669 #[fuchsia::test]
1670 async fn test_read_not_readable() {
1671 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_WRITABLE);
1672 let result = env.proxy.read(10).await.unwrap().map_err(Status::from_raw);
1673 assert_eq!(result, Err(Status::BAD_HANDLE));
1674 }
1675
1676 #[fuchsia::test]
1677 async fn test_read_validates_count() {
1678 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1679 let result =
1680 env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
1681 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1682 }
1683
1684 #[fuchsia::test]
1685 async fn test_read_at_succeeds() {
1686 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1687 let data = env.proxy.read_at(5, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1688 assert_eq!(data, vec![10, 11, 12, 13, 14]);
1689
1690 let events = env.file.operations.lock();
1691 assert_eq!(
1692 *events,
1693 vec![
1694 FileOperation::Init {
1695 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1696 },
1697 FileOperation::ReadAt { offset: 10, count: 5 },
1698 ]
1699 );
1700 }
1701
1702 #[fuchsia::test]
1703 async fn test_read_at_validates_count() {
1704 let env = init_mock_file(Box::new(only_allow_init), fio::PERM_READABLE);
1705 let result = env
1706 .proxy
1707 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
1708 .await
1709 .unwrap()
1710 .map_err(Status::from_raw);
1711 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1712 }
1713
1714 #[fuchsia::test]
1715 async fn test_seek_start() {
1716 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1717 let offset = env
1718 .proxy
1719 .seek(fio::SeekOrigin::Start, 10)
1720 .await
1721 .unwrap()
1722 .map_err(Status::from_raw)
1723 .unwrap();
1724 assert_eq!(offset, 10);
1725
1726 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1727 assert_eq!(data, vec![10]);
1728 let events = env.file.operations.lock();
1729 assert_eq!(
1730 *events,
1731 vec![
1732 FileOperation::Init {
1733 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1734 },
1735 FileOperation::ReadAt { offset: 10, count: 1 },
1736 ]
1737 );
1738 }
1739
1740 #[fuchsia::test]
1741 async fn test_seek_cur() {
1742 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1743 let offset = env
1744 .proxy
1745 .seek(fio::SeekOrigin::Start, 10)
1746 .await
1747 .unwrap()
1748 .map_err(Status::from_raw)
1749 .unwrap();
1750 assert_eq!(offset, 10);
1751
1752 let offset = env
1753 .proxy
1754 .seek(fio::SeekOrigin::Current, -2)
1755 .await
1756 .unwrap()
1757 .map_err(Status::from_raw)
1758 .unwrap();
1759 assert_eq!(offset, 8);
1760
1761 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1762 assert_eq!(data, vec![8]);
1763 let events = env.file.operations.lock();
1764 assert_eq!(
1765 *events,
1766 vec![
1767 FileOperation::Init {
1768 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1769 },
1770 FileOperation::ReadAt { offset: 8, count: 1 },
1771 ]
1772 );
1773 }
1774
1775 #[fuchsia::test]
1776 async fn test_seek_before_start() {
1777 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1778 let result =
1779 env.proxy.seek(fio::SeekOrigin::Current, -4).await.unwrap().map_err(Status::from_raw);
1780 assert_eq!(result, Err(Status::OUT_OF_RANGE));
1781 }
1782
1783 #[fuchsia::test]
1784 async fn test_seek_end() {
1785 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1786 let offset = env
1787 .proxy
1788 .seek(fio::SeekOrigin::End, -4)
1789 .await
1790 .unwrap()
1791 .map_err(Status::from_raw)
1792 .unwrap();
1793 assert_eq!(offset, MOCK_FILE_SIZE - 4);
1794
1795 let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1796 assert_eq!(data, vec![(offset % 256) as u8]);
1797 let events = env.file.operations.lock();
1798 assert_eq!(
1799 *events,
1800 vec![
1801 FileOperation::Init {
1802 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1803 },
1804 FileOperation::GetSize, FileOperation::ReadAt { offset, count: 1 },
1806 ]
1807 );
1808 }
1809
1810 #[fuchsia::test]
1811 async fn test_update_attributes() {
1812 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1813 let attributes = fio::MutableNodeAttributes {
1814 creation_time: Some(40000),
1815 modification_time: Some(100000),
1816 mode: Some(1),
1817 ..Default::default()
1818 };
1819 let () = env
1820 .proxy
1821 .update_attributes(&attributes)
1822 .await
1823 .unwrap()
1824 .map_err(Status::from_raw)
1825 .unwrap();
1826
1827 let events = env.file.operations.lock();
1828 assert_eq!(
1829 *events,
1830 vec![
1831 FileOperation::Init {
1832 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1833 },
1834 FileOperation::UpdateAttributes { attrs: attributes },
1835 ]
1836 );
1837 }
1838
1839 #[fuchsia::test]
1840 async fn test_set_flags() {
1841 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1842 env.proxy
1843 .set_flags(fio::Flags::FILE_APPEND)
1844 .await
1845 .unwrap()
1846 .map_err(Status::from_raw)
1847 .unwrap();
1848 let flags = env.proxy.get_flags().await.unwrap().map_err(Status::from_raw).unwrap();
1849 assert_eq!(flags, FLAGS_W | fio::Flags::FILE_APPEND | fio::Flags::PROTOCOL_FILE);
1850 }
1851
1852 #[fuchsia::test]
1853 async fn test_sync() {
1854 let env = init_mock_file(Box::new(always_succeed_callback), fio::Flags::empty());
1855 let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1856 let events = env.file.operations.lock();
1857 assert_eq!(
1858 *events,
1859 vec![
1860 FileOperation::Init {
1861 options: FileOptions {
1862 rights: fio::Operations::empty(),
1863 is_append: false,
1864 is_linkable: true
1865 }
1866 },
1867 FileOperation::Sync
1868 ]
1869 );
1870 }
1871
1872 #[fuchsia::test]
1873 async fn test_resize() {
1874 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1875 let () = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw).unwrap();
1876 let events = env.file.operations.lock();
1877 assert_matches!(
1878 &events[..],
1879 [
1880 FileOperation::Init {
1881 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1882 },
1883 FileOperation::Truncate { length: 10 },
1884 ]
1885 );
1886 }
1887
1888 #[fuchsia::test]
1889 async fn test_resize_no_perms() {
1890 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1891 let result = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw);
1892 assert_eq!(result, Err(Status::BAD_HANDLE));
1893 let events = env.file.operations.lock();
1894 assert_eq!(
1895 *events,
1896 vec![FileOperation::Init {
1897 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1898 },]
1899 );
1900 }
1901
1902 #[fuchsia::test]
1903 async fn test_write() {
1904 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1905 let data = "Hello, world!".as_bytes();
1906 let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
1907 assert_eq!(count, data.len() as u64);
1908 let events = env.file.operations.lock();
1909 assert_matches!(
1910 &events[..],
1911 [
1912 FileOperation::Init {
1913 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1914 },
1915 FileOperation::WriteAt { offset: 0, .. },
1916 ]
1917 );
1918 if let FileOperation::WriteAt { content, .. } = &events[1] {
1919 assert_eq!(content.as_slice(), data);
1920 } else {
1921 unreachable!();
1922 }
1923 }
1924
1925 #[fuchsia::test]
1926 async fn test_write_no_perms() {
1927 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_READABLE);
1928 let data = "Hello, world!".as_bytes();
1929 let result = env.proxy.write(data).await.unwrap().map_err(Status::from_raw);
1930 assert_eq!(result, Err(Status::BAD_HANDLE));
1931 let events = env.file.operations.lock();
1932 assert_eq!(
1933 *events,
1934 vec![FileOperation::Init {
1935 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1936 },]
1937 );
1938 }
1939
1940 #[fuchsia::test]
1941 async fn test_write_at() {
1942 let env = init_mock_file(Box::new(always_succeed_callback), fio::PERM_WRITABLE);
1943 let data = "Hello, world!".as_bytes();
1944 let count = env.proxy.write_at(data, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1945 assert_eq!(count, data.len() as u64);
1946 let events = env.file.operations.lock();
1947 assert_matches!(
1948 &events[..],
1949 [
1950 FileOperation::Init {
1951 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1952 },
1953 FileOperation::WriteAt { offset: 10, .. },
1954 ]
1955 );
1956 if let FileOperation::WriteAt { content, .. } = &events[1] {
1957 assert_eq!(content.as_slice(), data);
1958 } else {
1959 unreachable!();
1960 }
1961 }
1962
1963 #[fuchsia::test]
1964 async fn test_append() {
1965 let env = init_mock_file(
1966 Box::new(always_succeed_callback),
1967 fio::PERM_WRITABLE | fio::Flags::FILE_APPEND,
1968 );
1969 let data = "Hello, world!".as_bytes();
1970 let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
1971 assert_eq!(count, data.len() as u64);
1972 let offset = env
1973 .proxy
1974 .seek(fio::SeekOrigin::Current, 0)
1975 .await
1976 .unwrap()
1977 .map_err(Status::from_raw)
1978 .unwrap();
1979 assert_eq!(offset, MOCK_FILE_SIZE + data.len() as u64);
1980 let events = env.file.operations.lock();
1981 assert_matches!(
1982 &events[..],
1983 [
1984 FileOperation::Init {
1985 options: FileOptions { rights: RIGHTS_W, is_append: true, .. }
1986 },
1987 FileOperation::Append { .. }
1988 ]
1989 );
1990 if let FileOperation::Append { content } = &events[1] {
1991 assert_eq!(content.as_slice(), data);
1992 } else {
1993 unreachable!();
1994 }
1995 }
1996
1997 #[cfg(target_os = "fuchsia")]
1998 mod stream_tests {
1999 use super::*;
2000
2001 fn init_mock_stream_file(vmo: zx::Vmo, flags: fio::Flags) -> TestEnv {
2002 let file = MockFile::new_with_vmo(Box::new(always_succeed_callback), vmo);
2003 #[cfg(feature = "fdomain")]
2004 let scope =
2005 crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
2006 #[cfg(not(feature = "fdomain"))]
2007 let scope = crate::execution_scope::ExecutionScope::new();
2008
2009 let (proxy, server_end) = scope.domain().create_proxy::<fio::FileMarker>();
2010
2011 let cloned_file = file.clone();
2012 let cloned_scope = scope.clone();
2013
2014 flags.to_object_request(server_end).create_connection_sync::<StreamIoConnection<_>, _>(
2015 cloned_scope,
2016 cloned_file,
2017 flags,
2018 );
2019
2020 TestEnv { file, proxy, scope }
2021 }
2022
2023 #[fuchsia::test]
2024 async fn test_stream_describe() {
2025 const VMO_CONTENTS: &[u8] = b"hello there";
2026 let vmo = zx::Vmo::create(VMO_CONTENTS.len() as u64).unwrap();
2027 vmo.write(VMO_CONTENTS, 0).unwrap();
2028 let flags = fio::PERM_READABLE | fio::PERM_WRITABLE;
2029 let env = init_mock_stream_file(vmo, flags);
2030
2031 let fio::FileInfo { stream: Some(stream), .. } = env.proxy.describe().await.unwrap()
2032 else {
2033 panic!("Missing stream")
2034 };
2035 let contents =
2036 stream.read_to_vec(zx::StreamReadOptions::empty(), 20).expect("read failed");
2037 assert_eq!(contents, VMO_CONTENTS);
2038 }
2039
2040 #[fuchsia::test]
2041 async fn test_stream_read() {
2042 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2043 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2044 vmo.write(&vmo_contents, 0).unwrap();
2045 let flags = fio::PERM_READABLE;
2046 let env = init_mock_stream_file(vmo, flags);
2047
2048 let data = env
2049 .proxy
2050 .read(vmo_contents.len() as u64)
2051 .await
2052 .unwrap()
2053 .map_err(Status::from_raw)
2054 .unwrap();
2055 assert_eq!(data, vmo_contents);
2056
2057 let events = env.file.operations.lock();
2058 assert_eq!(
2059 *events,
2060 [FileOperation::Init {
2061 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2062 },]
2063 );
2064 }
2065
2066 #[fuchsia::test]
2067 async fn test_stream_read_at() {
2068 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2069 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2070 vmo.write(&vmo_contents, 0).unwrap();
2071 let flags = fio::PERM_READABLE;
2072 let env = init_mock_stream_file(vmo, flags);
2073
2074 const OFFSET: u64 = 4;
2075 let data = env
2076 .proxy
2077 .read_at((vmo_contents.len() as u64) - OFFSET, OFFSET)
2078 .await
2079 .unwrap()
2080 .map_err(Status::from_raw)
2081 .unwrap();
2082 assert_eq!(data, vmo_contents[OFFSET as usize..]);
2083
2084 let events = env.file.operations.lock();
2085 assert_eq!(
2086 *events,
2087 [FileOperation::Init {
2088 options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2089 },]
2090 );
2091 }
2092
2093 #[fuchsia::test]
2094 async fn test_stream_write() {
2095 const DATA_SIZE: u64 = 10;
2096 let vmo = zx::Vmo::create(DATA_SIZE).unwrap();
2097 let flags = fio::PERM_WRITABLE;
2098 let env = init_mock_stream_file(
2099 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2100 flags,
2101 );
2102
2103 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2104 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2105 assert_eq!(written, DATA_SIZE);
2106 let mut vmo_contents = [0; DATA_SIZE as usize];
2107 vmo.read(&mut vmo_contents, 0).unwrap();
2108 assert_eq!(vmo_contents, data);
2109
2110 let events = env.file.operations.lock();
2111 assert_eq!(
2112 *events,
2113 [FileOperation::Init {
2114 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2115 },]
2116 );
2117 }
2118
2119 #[fuchsia::test]
2120 async fn test_stream_write_at() {
2121 const OFFSET: u64 = 4;
2122 const DATA_SIZE: u64 = 10;
2123 let vmo = zx::Vmo::create(DATA_SIZE + OFFSET).unwrap();
2124 let flags = fio::PERM_WRITABLE;
2125 let env = init_mock_stream_file(
2126 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2127 flags,
2128 );
2129
2130 let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2131 let written =
2132 env.proxy.write_at(&data, OFFSET).await.unwrap().map_err(Status::from_raw).unwrap();
2133 assert_eq!(written, DATA_SIZE);
2134 let mut vmo_contents = [0; DATA_SIZE as usize];
2135 vmo.read(&mut vmo_contents, OFFSET).unwrap();
2136 assert_eq!(vmo_contents, data);
2137
2138 let events = env.file.operations.lock();
2139 assert_eq!(
2140 *events,
2141 [FileOperation::Init {
2142 options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2143 }]
2144 );
2145 }
2146
2147 #[fuchsia::test]
2148 async fn test_stream_seek() {
2149 let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2150 let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2151 vmo.write(&vmo_contents, 0).unwrap();
2152 let flags = fio::PERM_READABLE;
2153 let env = init_mock_stream_file(vmo, flags);
2154
2155 let position = env
2156 .proxy
2157 .seek(fio::SeekOrigin::Start, 8)
2158 .await
2159 .unwrap()
2160 .map_err(Status::from_raw)
2161 .unwrap();
2162 assert_eq!(position, 8);
2163 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2164 assert_eq!(data, [1, 0]);
2165
2166 let position = env
2167 .proxy
2168 .seek(fio::SeekOrigin::Current, -4)
2169 .await
2170 .unwrap()
2171 .map_err(Status::from_raw)
2172 .unwrap();
2173 assert_eq!(position, 6);
2175 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2176 assert_eq!(data, [3, 2]);
2177
2178 let position = env
2179 .proxy
2180 .seek(fio::SeekOrigin::End, -6)
2181 .await
2182 .unwrap()
2183 .map_err(Status::from_raw)
2184 .unwrap();
2185 assert_eq!(position, 4);
2186 let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2187 assert_eq!(data, [5, 4]);
2188
2189 let e = env
2190 .proxy
2191 .seek(fio::SeekOrigin::Start, -1)
2192 .await
2193 .unwrap()
2194 .map_err(Status::from_raw)
2195 .expect_err("Seeking before the start of a file should be an error");
2196 assert_eq!(e, Status::INVALID_ARGS);
2197 }
2198
2199 #[fuchsia::test]
2200 async fn test_stream_set_flags() {
2201 let data = [0, 1, 2, 3, 4];
2202 let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 100).unwrap();
2203 let flags = fio::PERM_WRITABLE;
2204 let env = init_mock_stream_file(
2205 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2206 flags,
2207 );
2208
2209 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2210 assert_eq!(written, data.len() as u64);
2211 assert_eq!(vmo.get_content_size().unwrap(), 100);
2213
2214 env.proxy
2216 .set_flags(fio::Flags::FILE_APPEND)
2217 .await
2218 .unwrap()
2219 .map_err(Status::from_raw)
2220 .unwrap();
2221 env.proxy
2222 .seek(fio::SeekOrigin::Start, 0)
2223 .await
2224 .unwrap()
2225 .map_err(Status::from_raw)
2226 .unwrap();
2227 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2228 assert_eq!(written, data.len() as u64);
2229 assert_eq!(vmo.get_content_size().unwrap(), 105);
2231
2232 env.proxy
2234 .set_flags(fio::Flags::empty())
2235 .await
2236 .unwrap()
2237 .map_err(Status::from_raw)
2238 .unwrap();
2239 env.proxy
2240 .seek(fio::SeekOrigin::Start, 0)
2241 .await
2242 .unwrap()
2243 .map_err(Status::from_raw)
2244 .unwrap();
2245 let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2246 assert_eq!(written, data.len() as u64);
2247 assert_eq!(vmo.get_content_size().unwrap(), 105);
2249 }
2250
2251 #[fuchsia::test]
2252 async fn test_stream_read_validates_count() {
2253 let vmo = zx::Vmo::create(10).unwrap();
2254 let flags = fio::PERM_READABLE;
2255 let env = init_mock_stream_file(vmo, flags);
2256 let result =
2257 env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
2258 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2259 }
2260
2261 #[fuchsia::test]
2262 async fn test_stream_read_at_validates_count() {
2263 let vmo = zx::Vmo::create(10).unwrap();
2264 let flags = fio::PERM_READABLE;
2265 let env = init_mock_stream_file(vmo, flags);
2266 let result = env
2267 .proxy
2268 .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
2269 .await
2270 .unwrap()
2271 .map_err(Status::from_raw);
2272 assert_eq!(result, Err(Status::OUT_OF_RANGE));
2273 }
2274 }
2275}