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