Skip to main content

vfs/file/
connection.rs

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