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