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