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