Skip to main content

vfs/file/
connection.rs

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