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