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