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