Skip to main content

vfs/file/
connection.rs

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