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 {
581                    stream: None,
582                    observer: self.file.event()?,
583                    ..Default::default()
584                })?;
585            }
586            #[cfg(target_os = "fuchsia")]
587            fio::FileRequest::Describe { responder } => {
588                trace::duration!(c"storage", c"File::Describe");
589                let stream = self.file.duplicate_stream()?;
590                responder.send(fio::FileInfo {
591                    stream,
592                    observer: self.file.event()?,
593                    ..Default::default()
594                })?;
595            }
596            fio::FileRequest::LinkInto { dst_parent_token, dst, responder } => {
597                async move {
598                    responder.send(
599                        self.handle_link_into(dst_parent_token, dst)
600                            .await
601                            .map_err(Status::into_raw),
602                    )
603                }
604                .trace(trace::trace_future_args!(c"storage", c"File::LinkInto"))
605                .await?;
606            }
607            fio::FileRequest::GetConnectionInfo { responder } => {
608                trace::duration!(c"storage", c"File::GetConnectionInfo");
609                // TODO(https://fxbug.dev/293947862): Restrict GET_ATTRIBUTES.
610                responder.send(fio::ConnectionInfo {
611                    rights: Some(self.options.rights),
612                    ..Default::default()
613                })?;
614            }
615            fio::FileRequest::Sync { responder } => {
616                async move {
617                    responder.send(self.file.sync(SyncMode::Normal).await.map_err(Status::into_raw))
618                }
619                .trace(trace::trace_future_args!(c"storage", c"File::Sync"))
620                .await?;
621            }
622            fio::FileRequest::GetAttr { responder } => {
623                async move {
624                    let (status, attrs) =
625                        crate::common::io2_to_io1_attrs(self.file.as_ref(), self.options.rights)
626                            .await;
627                    responder.send(status.into_raw(), &attrs)
628                }
629                .trace(trace::trace_future_args!(c"storage", c"File::GetAttr"))
630                .await?;
631            }
632            fio::FileRequest::SetAttr { flags, attributes, responder } => {
633                async move {
634                    let result =
635                        self.handle_update_attributes(io1_to_io2_attrs(flags, attributes)).await;
636                    responder.send(Status::from_result(result).into_raw())
637                }
638                .trace(trace::trace_future_args!(c"storage", c"File::SetAttr"))
639                .await?;
640            }
641            fio::FileRequest::GetAttributes { query, responder } => {
642                async move {
643                    // TODO(https://fxbug.dev/293947862): Restrict GET_ATTRIBUTES.
644                    let attrs = self.file.get_attributes(query).await;
645                    responder.send(
646                        attrs
647                            .as_ref()
648                            .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
649                            .map_err(|status| status.into_raw()),
650                    )
651                }
652                .trace(trace::trace_future_args!(c"storage", c"File::GetAttributes"))
653                .await?;
654            }
655            fio::FileRequest::UpdateAttributes { payload, responder } => {
656                async move {
657                    let result =
658                        self.handle_update_attributes(payload).await.map_err(Status::into_raw);
659                    responder.send(result)
660                }
661                .trace(trace::trace_future_args!(c"storage", c"File::UpdateAttributes"))
662                .await?;
663            }
664            fio::FileRequest::ListExtendedAttributes { iterator, control_handle: _ } => {
665                self.handle_list_extended_attribute(iterator)
666                    .trace(trace::trace_future_args!(c"storage", c"File::ListExtendedAttributes"))
667                    .await;
668            }
669            fio::FileRequest::GetExtendedAttribute { name, responder } => {
670                async move {
671                    let res =
672                        self.handle_get_extended_attribute(name).await.map_err(Status::into_raw);
673                    responder.send(res)
674                }
675                .trace(trace::trace_future_args!(c"storage", c"File::GetExtendedAttribute"))
676                .await?;
677            }
678            fio::FileRequest::SetExtendedAttribute { name, value, mode, responder } => {
679                async move {
680                    let res = self
681                        .handle_set_extended_attribute(name, value, mode)
682                        .await
683                        .map_err(Status::into_raw);
684                    responder.send(res)
685                }
686                .trace(trace::trace_future_args!(c"storage", c"File::SetExtendedAttribute"))
687                .await?;
688            }
689            fio::FileRequest::RemoveExtendedAttribute { name, responder } => {
690                async move {
691                    let res =
692                        self.handle_remove_extended_attribute(name).await.map_err(Status::into_raw);
693                    responder.send(res)
694                }
695                .trace(trace::trace_future_args!(c"storage", c"File::RemoveExtendedAttribute"))
696                .await?;
697            }
698            #[cfg(fuchsia_api_level_at_least = "HEAD")]
699            fio::FileRequest::EnableVerity { options, responder } => {
700                async move {
701                    let res = self.handle_enable_verity(options).await.map_err(Status::into_raw);
702                    responder.send(res)
703                }
704                .trace(trace::trace_future_args!(c"storage", c"File::EnableVerity"))
705                .await?;
706            }
707            fio::FileRequest::Read { count, responder } => {
708                let trace_args =
709                    trace::trace_future_args!(c"storage", c"File::Read", "bytes" => count);
710                async move {
711                    let result = self.handle_read(count).await;
712                    responder.send(result.as_deref().map_err(|s| s.into_raw()))
713                }
714                .trace(trace_args)
715                .await?;
716            }
717            fio::FileRequest::ReadAt { offset, count, responder } => {
718                let trace_args = trace::trace_future_args!(
719                    c"storage",
720                    c"File::ReadAt",
721                    "offset" => offset,
722                    "bytes" => count
723                );
724                async move {
725                    let result = self.handle_read_at(offset, count).await;
726                    responder.send(result.as_deref().map_err(|s| s.into_raw()))
727                }
728                .trace(trace_args)
729                .await?;
730            }
731            fio::FileRequest::Write { data, responder } => {
732                let trace_args =
733                    trace::trace_future_args!(c"storage", c"File::Write", "bytes" => data.len());
734                async move {
735                    let result = self.handle_write(data).await;
736                    responder.send(result.map_err(Status::into_raw))
737                }
738                .trace(trace_args)
739                .await?;
740            }
741            fio::FileRequest::WriteAt { offset, data, responder } => {
742                let trace_args = trace::trace_future_args!(
743                    c"storage",
744                    c"File::WriteAt",
745                    "offset" => offset,
746                    "bytes" => data.len()
747                );
748                async move {
749                    let result = self.handle_write_at(offset, data).await;
750                    responder.send(result.map_err(Status::into_raw))
751                }
752                .trace(trace_args)
753                .await?;
754            }
755            fio::FileRequest::Seek { origin, offset, responder } => {
756                async move {
757                    let result = self.handle_seek(offset, origin).await;
758                    responder.send(result.map_err(Status::into_raw))
759                }
760                .trace(trace::trace_future_args!(c"storage", c"File::Seek"))
761                .await?;
762            }
763            fio::FileRequest::Resize { length, responder } => {
764                async move {
765                    let result = self.handle_truncate(length).await;
766                    responder.send(result.map_err(Status::into_raw))
767                }
768                .trace(trace::trace_future_args!(c"storage", c"File::Resize"))
769                .await?;
770            }
771            #[cfg(fuchsia_api_level_at_least = "NEXT")]
772            fio::FileRequest::GetFlags { responder } => {
773                trace::duration!(c"storage", c"File::GetFlags");
774                responder.send(Ok(fio::Flags::from(&self.options)))?;
775            }
776            #[cfg(fuchsia_api_level_at_least = "NEXT")]
777            fio::FileRequest::SetFlags { flags, responder } => {
778                trace::duration!(c"storage", c"File::SetFlags");
779                // The only supported flag is APPEND.
780                if flags.is_empty() || flags == fio::Flags::FILE_APPEND {
781                    self.options.is_append = flags.contains(fio::Flags::FILE_APPEND);
782                    responder.send(self.file.set_flags(flags).map_err(Status::into_raw))?;
783                } else {
784                    responder.send(Err(Status::INVALID_ARGS.into_raw()))?;
785                }
786            }
787            #[cfg(fuchsia_api_level_at_least = "NEXT")]
788            fio::FileRequest::DeprecatedGetFlags { responder } => {
789                trace::duration!(c"storage", c"File::DeprecatedGetFlags");
790                responder.send(Status::OK.into_raw(), self.options.to_io1())?;
791            }
792            #[cfg(fuchsia_api_level_at_least = "NEXT")]
793            fio::FileRequest::DeprecatedSetFlags { flags, responder } => {
794                trace::duration!(c"storage", c"File::DeprecatedSetFlags");
795                // The only supported flag is APPEND.
796                let is_append = flags.contains(fio::OpenFlags::APPEND);
797                self.options.is_append = is_append;
798                let flags = if is_append { fio::Flags::FILE_APPEND } else { fio::Flags::empty() };
799                responder.send(Status::from_result(self.file.set_flags(flags)).into_raw())?;
800            }
801            #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
802            fio::FileRequest::GetFlags { responder } => {
803                trace::duration!(c"storage", c"File::GetFlags");
804                responder.send(Status::OK.into_raw(), self.options.to_io1())?;
805            }
806            #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
807            fio::FileRequest::SetFlags { flags, responder } => {
808                trace::duration!(c"storage", c"File::SetFlags");
809                // The only supported flag is APPEND.
810                let is_append = flags.contains(fio::OpenFlags::APPEND);
811                self.options.is_append = is_append;
812                let flags = if is_append { fio::Flags::FILE_APPEND } else { fio::Flags::empty() };
813                responder.send(Status::from_result(self.file.set_flags(flags)).into_raw())?;
814            }
815            #[cfg(target_os = "fuchsia")]
816            fio::FileRequest::GetBackingMemory { flags, responder } => {
817                async move {
818                    let result = self.handle_get_backing_memory(flags).await;
819                    responder.send(result.map_err(Status::into_raw))
820                }
821                .trace(trace::trace_future_args!(c"storage", c"File::GetBackingMemory"))
822                .await?;
823            }
824
825            #[cfg(not(target_os = "fuchsia"))]
826            fio::FileRequest::GetBackingMemory { flags: _, responder } => {
827                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
828            }
829            fio::FileRequest::AdvisoryLock { request: _, responder } => {
830                trace::duration!(c"storage", c"File::AdvisoryLock");
831                responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
832            }
833            fio::FileRequest::Query { responder } => {
834                trace::duration!(c"storage", c"File::Query");
835                responder.send(fio::FILE_PROTOCOL_NAME.as_bytes())?;
836            }
837            fio::FileRequest::QueryFilesystem { responder } => {
838                trace::duration!(c"storage", c"File::QueryFilesystem");
839                match self.file.query_filesystem() {
840                    Err(status) => responder.send(status.into_raw(), None)?,
841                    Ok(info) => responder.send(0, Some(&info))?,
842                }
843            }
844            #[cfg(fuchsia_api_level_at_least = "HEAD")]
845            fio::FileRequest::Allocate { offset, length, mode, responder } => {
846                async move {
847                    let result = self.handle_allocate(offset, length, mode).await;
848                    responder.send(result.map_err(Status::into_raw))
849                }
850                .trace(trace::trace_future_args!(c"storage", c"File::Allocate"))
851                .await?;
852            }
853            fio::FileRequest::_UnknownMethod { .. } => (),
854        }
855        Ok(ConnectionState::Alive)
856    }
857
858    async fn handle_deprecated_clone(
859        &mut self,
860        flags: fio::OpenFlags,
861        server_end: ServerEnd<fio::NodeMarker>,
862    ) {
863        flags
864            .to_object_request(server_end)
865            .handle_async(async |object_request| {
866                let options =
867                    inherit_rights_for_clone(self.options.to_io1(), flags)?.to_file_options()?;
868
869                let connection = Self {
870                    scope: self.scope.clone(),
871                    file: self.file.clone_connection(options)?,
872                    options,
873                };
874
875                let requests = object_request.take().into_request_stream(&connection).await?;
876                self.scope.spawn(RequestListener::new(requests, Some(connection)));
877                Ok(())
878            })
879            .await;
880    }
881
882    fn handle_clone(&mut self, server_end: ServerEnd<fio::FileMarker>) {
883        let connection = match self.file.clone_connection(self.options) {
884            Ok(file) => Self { scope: self.scope.clone(), file, options: self.options },
885            Err(status) => {
886                let _ = server_end.close_with_epitaph(status);
887                return;
888            }
889        };
890        self.scope.spawn(RequestListener::new(server_end.into_stream(), Some(connection)));
891    }
892
893    async fn handle_read(&mut self, count: u64) -> Result<Vec<u8>, Status> {
894        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
895            return Err(Status::BAD_HANDLE);
896        }
897
898        if count > fio::MAX_TRANSFER_SIZE {
899            return Err(Status::OUT_OF_RANGE);
900        }
901        self.file.read(count).await
902    }
903
904    async fn handle_read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, Status> {
905        if !self.options.rights.intersects(fio::Operations::READ_BYTES) {
906            return Err(Status::BAD_HANDLE);
907        }
908        if count > fio::MAX_TRANSFER_SIZE {
909            return Err(Status::OUT_OF_RANGE);
910        }
911        self.file.read_at(offset, count).await
912    }
913
914    async fn handle_write(&mut self, content: Vec<u8>) -> Result<u64, Status> {
915        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
916            return Err(Status::BAD_HANDLE);
917        }
918        self.file.write(content).await
919    }
920
921    async fn handle_write_at(&self, offset: u64, content: Vec<u8>) -> Result<u64, Status> {
922        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
923            return Err(Status::BAD_HANDLE);
924        }
925
926        self.file.write_at(offset, content).await
927    }
928
929    /// Move seek position to byte `offset` relative to the origin specified by `start`.
930    async fn handle_seek(&mut self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, Status> {
931        self.file.seek(offset, origin).await
932    }
933
934    async fn handle_update_attributes(
935        &mut self,
936        attributes: fio::MutableNodeAttributes,
937    ) -> Result<(), Status> {
938        if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
939            return Err(Status::BAD_HANDLE);
940        }
941
942        self.file.update_attributes(attributes).await
943    }
944
945    #[cfg(fuchsia_api_level_at_least = "HEAD")]
946    async fn handle_enable_verity(
947        &mut self,
948        options: fio::VerificationOptions,
949    ) -> Result<(), Status> {
950        if !self.options.rights.intersects(fio::Operations::UPDATE_ATTRIBUTES) {
951            return Err(Status::BAD_HANDLE);
952        }
953        self.file.enable_verity(options).await
954    }
955
956    async fn handle_truncate(&mut self, length: u64) -> Result<(), Status> {
957        if !self.options.rights.intersects(fio::Operations::WRITE_BYTES) {
958            return Err(Status::BAD_HANDLE);
959        }
960
961        self.file.truncate(length).await
962    }
963
964    #[cfg(target_os = "fuchsia")]
965    async fn handle_get_backing_memory(&mut self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
966        get_backing_memory_validate_flags(flags, self.options.to_io1())?;
967        self.file.get_backing_memory(flags).await
968    }
969
970    async fn handle_list_extended_attribute(
971        &mut self,
972        iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
973    ) {
974        let attributes = match self.file.list_extended_attributes().await {
975            Ok(attributes) => attributes,
976            Err(status) => {
977                log::error!(status:?; "list extended attributes failed");
978                iterator
979                    .close_with_epitaph(status)
980                    .unwrap_or_else(|error| log::error!(error:?; "failed to send epitaph"));
981                return;
982            }
983        };
984        self.scope.spawn(extended_attributes_sender(iterator, attributes));
985    }
986
987    async fn handle_get_extended_attribute(
988        &mut self,
989        name: Vec<u8>,
990    ) -> Result<fio::ExtendedAttributeValue, Status> {
991        let value = self.file.get_extended_attribute(name).await?;
992        encode_extended_attribute_value(value)
993    }
994
995    async fn handle_set_extended_attribute(
996        &mut self,
997        name: Vec<u8>,
998        value: fio::ExtendedAttributeValue,
999        mode: fio::SetExtendedAttributeMode,
1000    ) -> Result<(), Status> {
1001        if name.contains(&0) {
1002            return Err(Status::INVALID_ARGS);
1003        }
1004        let val = decode_extended_attribute_value(value)?;
1005        self.file.set_extended_attribute(name, val, mode).await
1006    }
1007
1008    async fn handle_remove_extended_attribute(&mut self, name: Vec<u8>) -> Result<(), Status> {
1009        self.file.remove_extended_attribute(name).await
1010    }
1011
1012    async fn handle_link_into(
1013        &mut self,
1014        target_parent_token: fidl::Event,
1015        target_name: String,
1016    ) -> Result<(), Status> {
1017        let target_name = parse_name(target_name).map_err(|_| Status::INVALID_ARGS)?;
1018
1019        #[cfg(fuchsia_api_level_at_least = "HEAD")]
1020        if !self.options.is_linkable {
1021            return Err(Status::NOT_FOUND);
1022        }
1023
1024        if !self.options.rights.contains(
1025            fio::Operations::READ_BYTES
1026                | fio::Operations::WRITE_BYTES
1027                | fio::Operations::GET_ATTRIBUTES
1028                | fio::Operations::UPDATE_ATTRIBUTES,
1029        ) {
1030            return Err(Status::ACCESS_DENIED);
1031        }
1032
1033        let target_parent = self
1034            .scope
1035            .token_registry()
1036            .get_owner(target_parent_token.into())?
1037            .ok_or(Err(Status::NOT_FOUND))?;
1038
1039        self.file.clone().link_into(target_parent, target_name).await
1040    }
1041
1042    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1043    async fn handle_allocate(
1044        &mut self,
1045        offset: u64,
1046        length: u64,
1047        mode: fio::AllocateMode,
1048    ) -> Result<(), Status> {
1049        self.file.allocate(offset, length, mode).await
1050    }
1051
1052    fn should_sync_before_close(&self) -> bool {
1053        self.options
1054            .rights
1055            .intersects(fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES)
1056    }
1057}
1058
1059// The `FileConnection` is wrapped in an `Option` so it can be dropped before responding to a Close
1060// request.
1061impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + DerefMut + IoOpHandler + Unpin>
1062    RequestHandler for Option<FileConnection<U>>
1063{
1064    type Request = Result<fio::FileRequest, fidl::Error>;
1065
1066    async fn handle_request(self: Pin<&mut Self>, request: Self::Request) -> ControlFlow<()> {
1067        let option_this = self.get_mut();
1068        let this = option_this.as_mut().unwrap();
1069        let _guard = this.scope.active_guard();
1070        let state = match request {
1071            Ok(request) => {
1072                this.handle_request(request)
1073                    .await
1074                    // Protocol level error.  Close the connection on any unexpected error.
1075                    // TODO: Send an epitaph.
1076                    .unwrap_or(ConnectionState::Dropped)
1077            }
1078            Err(_) => {
1079                // FIDL level error, such as invalid message format and alike.  Close the
1080                // connection on any unexpected error.
1081                // TODO: Send an epitaph.
1082                ConnectionState::Dropped
1083            }
1084        };
1085        match state {
1086            ConnectionState::Alive => ControlFlow::Continue(()),
1087            ConnectionState::Dropped => {
1088                if this.should_sync_before_close() {
1089                    let _ = this.file.sync(SyncMode::PreClose).await;
1090                }
1091                ControlFlow::Break(())
1092            }
1093            ConnectionState::Closed(responder) => {
1094                async move {
1095                    let this = option_this.as_mut().unwrap();
1096                    let _ = responder.send({
1097                        let result = if this.should_sync_before_close() {
1098                            this.file.sync(SyncMode::PreClose).await.map_err(Status::into_raw)
1099                        } else {
1100                            Ok(())
1101                        };
1102                        // The file gets closed when we drop the connection, so we should do that
1103                        // before sending the response.
1104                        std::mem::drop(option_this.take());
1105                        result
1106                    });
1107                }
1108                .trace(trace::trace_future_args!(c"storage", c"File::Close"))
1109                .await;
1110                ControlFlow::Break(())
1111            }
1112        }
1113    }
1114
1115    async fn stream_closed(self: Pin<&mut Self>) {
1116        let this = self.get_mut().as_mut().unwrap();
1117        if this.should_sync_before_close() {
1118            let _guard = this.scope.active_guard();
1119            let _ = this.file.sync(SyncMode::PreClose).await;
1120        }
1121    }
1122}
1123
1124impl<T: 'static + File, U: Deref<Target = OpenNode<T>> + IoOpHandler> Representation
1125    for FileConnection<U>
1126{
1127    type Protocol = fio::FileMarker;
1128
1129    async fn get_representation(
1130        &self,
1131        requested_attributes: fio::NodeAttributesQuery,
1132    ) -> Result<fio::Representation, Status> {
1133        // TODO(https://fxbug.dev/324112547): Add support for connecting as Node.
1134        Ok(fio::Representation::File(fio::FileInfo {
1135            is_append: Some(self.options.is_append),
1136            observer: self.file.event()?,
1137            #[cfg(target_os = "fuchsia")]
1138            stream: self.file.duplicate_stream()?,
1139            #[cfg(not(target_os = "fuchsia"))]
1140            stream: None,
1141            attributes: if requested_attributes.is_empty() {
1142                None
1143            } else {
1144                Some(self.file.get_attributes(requested_attributes).await?)
1145            },
1146            ..Default::default()
1147        }))
1148    }
1149
1150    async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
1151        #[cfg(target_os = "fuchsia")]
1152        let stream = self.file.duplicate_stream()?;
1153        #[cfg(not(target_os = "fuchsia"))]
1154        let stream = None;
1155        Ok(fio::NodeInfoDeprecated::File(fio::FileObject { event: self.file.event()?, stream }))
1156    }
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::*;
1162    use crate::directory::entry::{EntryInfo, GetEntryInfo};
1163    use crate::node::Node;
1164    use assert_matches::assert_matches;
1165    use futures::prelude::*;
1166    use std::sync::Mutex;
1167
1168    const RIGHTS_R: fio::Operations =
1169        fio::Operations::READ_BYTES.union(fio::Operations::GET_ATTRIBUTES);
1170    const RIGHTS_W: fio::Operations = fio::Operations::WRITE_BYTES
1171        .union(fio::Operations::GET_ATTRIBUTES)
1172        .union(fio::Operations::UPDATE_ATTRIBUTES);
1173    const RIGHTS_RW: fio::Operations = fio::Operations::READ_BYTES
1174        .union(fio::Operations::WRITE_BYTES)
1175        .union(fio::Operations::GET_ATTRIBUTES)
1176        .union(fio::Operations::UPDATE_ATTRIBUTES);
1177
1178    #[derive(Debug, PartialEq)]
1179    enum FileOperation {
1180        Init {
1181            options: FileOptions,
1182        },
1183        ReadAt {
1184            offset: u64,
1185            count: u64,
1186        },
1187        WriteAt {
1188            offset: u64,
1189            content: Vec<u8>,
1190        },
1191        Append {
1192            content: Vec<u8>,
1193        },
1194        Truncate {
1195            length: u64,
1196        },
1197        #[cfg(target_os = "fuchsia")]
1198        GetBackingMemory {
1199            flags: fio::VmoFlags,
1200        },
1201        GetSize,
1202        GetAttributes {
1203            query: fio::NodeAttributesQuery,
1204        },
1205        UpdateAttributes {
1206            attrs: fio::MutableNodeAttributes,
1207        },
1208        Close,
1209        Sync,
1210    }
1211
1212    type MockCallbackType = Box<dyn Fn(&FileOperation) -> Status + Sync + Send>;
1213    /// A fake file that just tracks what calls `FileConnection` makes on it.
1214    struct MockFile {
1215        /// The list of operations that have been called.
1216        operations: Mutex<Vec<FileOperation>>,
1217        /// Callback used to determine how to respond to given operation.
1218        callback: MockCallbackType,
1219        /// Only used for get_size/get_attributes
1220        file_size: u64,
1221        #[cfg(target_os = "fuchsia")]
1222        /// VMO if using streams.
1223        vmo: zx::Vmo,
1224    }
1225
1226    const MOCK_FILE_SIZE: u64 = 256;
1227    const MOCK_FILE_ID: u64 = 10;
1228    const MOCK_FILE_LINKS: u64 = 2;
1229    const MOCK_FILE_CREATION_TIME: u64 = 10;
1230    const MOCK_FILE_MODIFICATION_TIME: u64 = 100;
1231    impl MockFile {
1232        fn new(callback: MockCallbackType) -> Arc<Self> {
1233            Arc::new(MockFile {
1234                operations: Mutex::new(Vec::new()),
1235                callback,
1236                file_size: MOCK_FILE_SIZE,
1237                #[cfg(target_os = "fuchsia")]
1238                vmo: zx::Handle::invalid().into(),
1239            })
1240        }
1241
1242        #[cfg(target_os = "fuchsia")]
1243        fn new_with_vmo(callback: MockCallbackType, vmo: zx::Vmo) -> Arc<Self> {
1244            Arc::new(MockFile {
1245                operations: Mutex::new(Vec::new()),
1246                callback,
1247                file_size: MOCK_FILE_SIZE,
1248                vmo,
1249            })
1250        }
1251
1252        fn handle_operation(&self, operation: FileOperation) -> Result<(), Status> {
1253            let result = (self.callback)(&operation);
1254            self.operations.lock().unwrap().push(operation);
1255            match result {
1256                Status::OK => Ok(()),
1257                err => Err(err),
1258            }
1259        }
1260    }
1261
1262    impl GetEntryInfo for MockFile {
1263        fn entry_info(&self) -> EntryInfo {
1264            EntryInfo::new(MOCK_FILE_ID, fio::DirentType::File)
1265        }
1266    }
1267
1268    impl Node for MockFile {
1269        async fn get_attributes(
1270            &self,
1271            query: fio::NodeAttributesQuery,
1272        ) -> Result<fio::NodeAttributes2, Status> {
1273            self.handle_operation(FileOperation::GetAttributes { query })?;
1274            Ok(attributes!(
1275                query,
1276                Mutable {
1277                    creation_time: MOCK_FILE_CREATION_TIME,
1278                    modification_time: MOCK_FILE_MODIFICATION_TIME,
1279                },
1280                Immutable {
1281                    protocols: fio::NodeProtocolKinds::FILE,
1282                    abilities: fio::Operations::GET_ATTRIBUTES
1283                        | fio::Operations::UPDATE_ATTRIBUTES
1284                        | fio::Operations::READ_BYTES
1285                        | fio::Operations::WRITE_BYTES,
1286                    content_size: self.file_size,
1287                    storage_size: 2 * self.file_size,
1288                    link_count: MOCK_FILE_LINKS,
1289                    id: MOCK_FILE_ID,
1290                }
1291            ))
1292        }
1293
1294        fn close(self: Arc<Self>) {
1295            let _ = self.handle_operation(FileOperation::Close);
1296        }
1297    }
1298
1299    impl File for MockFile {
1300        fn writable(&self) -> bool {
1301            true
1302        }
1303
1304        async fn open_file(&self, options: &FileOptions) -> Result<(), Status> {
1305            self.handle_operation(FileOperation::Init { options: *options })?;
1306            Ok(())
1307        }
1308
1309        async fn truncate(&self, length: u64) -> Result<(), Status> {
1310            self.handle_operation(FileOperation::Truncate { length })
1311        }
1312
1313        #[cfg(target_os = "fuchsia")]
1314        async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
1315            self.handle_operation(FileOperation::GetBackingMemory { flags })?;
1316            Err(Status::NOT_SUPPORTED)
1317        }
1318
1319        async fn get_size(&self) -> Result<u64, Status> {
1320            self.handle_operation(FileOperation::GetSize)?;
1321            Ok(self.file_size)
1322        }
1323
1324        async fn update_attributes(&self, attrs: fio::MutableNodeAttributes) -> Result<(), Status> {
1325            self.handle_operation(FileOperation::UpdateAttributes { attrs })?;
1326            Ok(())
1327        }
1328
1329        async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
1330            self.handle_operation(FileOperation::Sync)
1331        }
1332    }
1333
1334    impl FileIo for MockFile {
1335        async fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<u64, Status> {
1336            let count = buffer.len() as u64;
1337            self.handle_operation(FileOperation::ReadAt { offset, count })?;
1338
1339            // Return data as if we were a file with 0..255 repeated endlessly.
1340            let mut i = offset;
1341            buffer.fill_with(|| {
1342                let v = (i % 256) as u8;
1343                i += 1;
1344                v
1345            });
1346            Ok(count)
1347        }
1348
1349        async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, Status> {
1350            self.handle_operation(FileOperation::WriteAt { offset, content: content.to_vec() })?;
1351            Ok(content.len() as u64)
1352        }
1353
1354        async fn append(&self, content: &[u8]) -> Result<(u64, u64), Status> {
1355            self.handle_operation(FileOperation::Append { content: content.to_vec() })?;
1356            Ok((content.len() as u64, self.file_size + content.len() as u64))
1357        }
1358    }
1359
1360    #[cfg(target_os = "fuchsia")]
1361    impl GetVmo for MockFile {
1362        fn get_vmo(&self) -> &zx::Vmo {
1363            &self.vmo
1364        }
1365    }
1366
1367    /// Only the init operation will succeed, all others fail.
1368    fn only_allow_init(op: &FileOperation) -> Status {
1369        match op {
1370            FileOperation::Init { .. } => Status::OK,
1371            _ => Status::IO,
1372        }
1373    }
1374
1375    /// All operations succeed.
1376    fn always_succeed_callback(_op: &FileOperation) -> Status {
1377        Status::OK
1378    }
1379
1380    struct TestEnv {
1381        pub file: Arc<MockFile>,
1382        pub proxy: fio::FileProxy,
1383        pub scope: ExecutionScope,
1384    }
1385
1386    fn init_mock_file(callback: MockCallbackType, flags: fio::OpenFlags) -> TestEnv {
1387        let file = MockFile::new(callback);
1388        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
1389
1390        let scope = ExecutionScope::new();
1391
1392        flags.to_object_request(server_end).create_connection_sync::<FidlIoConnection<_>, _>(
1393            scope.clone(),
1394            file.clone(),
1395            flags,
1396        );
1397
1398        TestEnv { file, proxy, scope }
1399    }
1400
1401    #[fuchsia::test]
1402    async fn test_open_flag_truncate() {
1403        let env = init_mock_file(
1404            Box::new(always_succeed_callback),
1405            fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE,
1406        );
1407        // Do a no-op sync() to make sure that the open has finished.
1408        let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1409        let events = env.file.operations.lock().unwrap();
1410        assert_eq!(
1411            *events,
1412            vec![
1413                FileOperation::Init {
1414                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1415                },
1416                FileOperation::Truncate { length: 0 },
1417                FileOperation::Sync,
1418            ]
1419        );
1420    }
1421
1422    #[fuchsia::test]
1423    async fn test_clone_same_rights() {
1424        let env = init_mock_file(
1425            Box::new(always_succeed_callback),
1426            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
1427        );
1428        // Read from original proxy.
1429        let _: Vec<u8> = env.proxy.read(6).await.unwrap().map_err(Status::from_raw).unwrap();
1430        let (clone_proxy, remote) = fidl::endpoints::create_proxy::<fio::FileMarker>();
1431        env.proxy
1432            .deprecated_clone(fio::OpenFlags::CLONE_SAME_RIGHTS, remote.into_channel().into())
1433            .unwrap();
1434        // Seek and read from clone_proxy.
1435        let _: u64 = clone_proxy
1436            .seek(fio::SeekOrigin::Start, 100)
1437            .await
1438            .unwrap()
1439            .map_err(Status::from_raw)
1440            .unwrap();
1441        let _: Vec<u8> = clone_proxy.read(5).await.unwrap().map_err(Status::from_raw).unwrap();
1442
1443        // Read from original proxy.
1444        let _: Vec<u8> = env.proxy.read(5).await.unwrap().map_err(Status::from_raw).unwrap();
1445
1446        let events = env.file.operations.lock().unwrap();
1447        // Each connection should have an independent seek.
1448        assert_eq!(
1449            *events,
1450            vec![
1451                FileOperation::Init {
1452                    options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1453                },
1454                FileOperation::ReadAt { offset: 0, count: 6 },
1455                FileOperation::ReadAt { offset: 100, count: 5 },
1456                FileOperation::ReadAt { offset: 6, count: 5 },
1457            ]
1458        );
1459    }
1460
1461    #[fuchsia::test]
1462    async fn test_close_succeeds() {
1463        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1464        let () = env.proxy.close().await.unwrap().map_err(Status::from_raw).unwrap();
1465
1466        let events = env.file.operations.lock().unwrap();
1467        assert_eq!(
1468            *events,
1469            vec![
1470                FileOperation::Init {
1471                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1472                },
1473                FileOperation::Close {},
1474            ]
1475        );
1476    }
1477
1478    #[fuchsia::test]
1479    async fn test_close_fails() {
1480        let env = init_mock_file(
1481            Box::new(only_allow_init),
1482            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
1483        );
1484        let status = env.proxy.close().await.unwrap().map_err(Status::from_raw);
1485        assert_eq!(status, Err(Status::IO));
1486
1487        let events = env.file.operations.lock().unwrap();
1488        assert_eq!(
1489            *events,
1490            vec![
1491                FileOperation::Init {
1492                    options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1493                },
1494                FileOperation::Sync,
1495                FileOperation::Close,
1496            ]
1497        );
1498    }
1499
1500    #[fuchsia::test]
1501    async fn test_close_called_when_dropped() {
1502        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1503        let _ = env.proxy.sync().await;
1504        std::mem::drop(env.proxy);
1505        env.scope.shutdown();
1506        env.scope.wait().await;
1507        let events = env.file.operations.lock().unwrap();
1508        assert_eq!(
1509            *events,
1510            vec![
1511                FileOperation::Init {
1512                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1513                },
1514                FileOperation::Sync,
1515                FileOperation::Close,
1516            ]
1517        );
1518    }
1519
1520    #[fuchsia::test]
1521    async fn test_describe() {
1522        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1523        let protocol = env.proxy.query().await.unwrap();
1524        assert_eq!(protocol, fio::FILE_PROTOCOL_NAME.as_bytes());
1525    }
1526
1527    #[fuchsia::test]
1528    async fn test_get_attributes() {
1529        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::empty());
1530        let (mutable_attributes, immutable_attributes) = env
1531            .proxy
1532            .get_attributes(fio::NodeAttributesQuery::all())
1533            .await
1534            .unwrap()
1535            .map_err(Status::from_raw)
1536            .unwrap();
1537        let expected = attributes!(
1538            fio::NodeAttributesQuery::all(),
1539            Mutable {
1540                creation_time: MOCK_FILE_CREATION_TIME,
1541                modification_time: MOCK_FILE_MODIFICATION_TIME,
1542            },
1543            Immutable {
1544                protocols: fio::NodeProtocolKinds::FILE,
1545                abilities: fio::Operations::GET_ATTRIBUTES
1546                    | fio::Operations::UPDATE_ATTRIBUTES
1547                    | fio::Operations::READ_BYTES
1548                    | fio::Operations::WRITE_BYTES,
1549                content_size: MOCK_FILE_SIZE,
1550                storage_size: 2 * MOCK_FILE_SIZE,
1551                link_count: MOCK_FILE_LINKS,
1552                id: MOCK_FILE_ID,
1553            }
1554        );
1555        assert_eq!(mutable_attributes, expected.mutable_attributes);
1556        assert_eq!(immutable_attributes, expected.immutable_attributes);
1557
1558        let events = env.file.operations.lock().unwrap();
1559        assert_eq!(
1560            *events,
1561            vec![
1562                FileOperation::Init {
1563                    options: FileOptions {
1564                        rights: fio::Operations::GET_ATTRIBUTES,
1565                        is_append: false,
1566                        is_linkable: true
1567                    }
1568                },
1569                FileOperation::GetAttributes { query: fio::NodeAttributesQuery::all() }
1570            ]
1571        );
1572    }
1573
1574    #[fuchsia::test]
1575    async fn test_getbuffer() {
1576        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1577        let result = env
1578            .proxy
1579            .get_backing_memory(fio::VmoFlags::READ)
1580            .await
1581            .unwrap()
1582            .map_err(Status::from_raw);
1583        assert_eq!(result, Err(Status::NOT_SUPPORTED));
1584        let events = env.file.operations.lock().unwrap();
1585        assert_eq!(
1586            *events,
1587            vec![
1588                FileOperation::Init {
1589                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1590                },
1591                #[cfg(target_os = "fuchsia")]
1592                FileOperation::GetBackingMemory { flags: fio::VmoFlags::READ },
1593            ]
1594        );
1595    }
1596
1597    #[fuchsia::test]
1598    async fn test_getbuffer_no_perms() {
1599        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::empty());
1600        let result = env
1601            .proxy
1602            .get_backing_memory(fio::VmoFlags::READ)
1603            .await
1604            .unwrap()
1605            .map_err(Status::from_raw);
1606        // On Target this is ACCESS_DENIED, on host this is NOT_SUPPORTED
1607        #[cfg(target_os = "fuchsia")]
1608        assert_eq!(result, Err(Status::ACCESS_DENIED));
1609        #[cfg(not(target_os = "fuchsia"))]
1610        assert_eq!(result, Err(Status::NOT_SUPPORTED));
1611        let events = env.file.operations.lock().unwrap();
1612        assert_eq!(
1613            *events,
1614            vec![FileOperation::Init {
1615                options: FileOptions {
1616                    rights: fio::Operations::GET_ATTRIBUTES,
1617                    is_append: false,
1618                    is_linkable: true
1619                }
1620            },]
1621        );
1622    }
1623
1624    #[fuchsia::test]
1625    async fn test_getbuffer_vmo_exec_requires_right_executable() {
1626        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1627        let result = env
1628            .proxy
1629            .get_backing_memory(fio::VmoFlags::EXECUTE)
1630            .await
1631            .unwrap()
1632            .map_err(Status::from_raw);
1633        // On Target this is ACCESS_DENIED, on host this is NOT_SUPPORTED
1634        #[cfg(target_os = "fuchsia")]
1635        assert_eq!(result, Err(Status::ACCESS_DENIED));
1636        #[cfg(not(target_os = "fuchsia"))]
1637        assert_eq!(result, Err(Status::NOT_SUPPORTED));
1638        let events = env.file.operations.lock().unwrap();
1639        assert_eq!(
1640            *events,
1641            vec![FileOperation::Init {
1642                options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1643            },]
1644        );
1645    }
1646
1647    #[fuchsia::test]
1648    async fn test_deprecated_get_flags() {
1649        let env = init_mock_file(
1650            Box::new(always_succeed_callback),
1651            fio::OpenFlags::RIGHT_READABLE
1652                | fio::OpenFlags::RIGHT_WRITABLE
1653                | fio::OpenFlags::TRUNCATE,
1654        );
1655        let (status, flags) = env.proxy.deprecated_get_flags().await.unwrap();
1656        assert_eq!(Status::from_raw(status), Status::OK);
1657        // OPEN_FLAG_TRUNCATE should get stripped because it only applies at open time.
1658        assert_eq!(flags, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE);
1659        let events = env.file.operations.lock().unwrap();
1660        assert_eq!(
1661            *events,
1662            vec![
1663                FileOperation::Init {
1664                    options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true }
1665                },
1666                FileOperation::Truncate { length: 0 }
1667            ]
1668        );
1669    }
1670
1671    #[fuchsia::test]
1672    async fn test_open_flag_describe() {
1673        let env = init_mock_file(
1674            Box::new(always_succeed_callback),
1675            fio::OpenFlags::RIGHT_READABLE
1676                | fio::OpenFlags::RIGHT_WRITABLE
1677                | fio::OpenFlags::DESCRIBE,
1678        );
1679        let event = env.proxy.take_event_stream().try_next().await.unwrap();
1680        match event {
1681            Some(fio::FileEvent::OnOpen_ { s, info: Some(boxed) }) => {
1682                assert_eq!(Status::from_raw(s), Status::OK);
1683                assert_eq!(
1684                    *boxed,
1685                    fio::NodeInfoDeprecated::File(fio::FileObject { event: None, stream: None })
1686                );
1687            }
1688            Some(fio::FileEvent::OnRepresentation { payload }) => {
1689                assert_eq!(payload, fio::Representation::File(fio::FileInfo::default()));
1690            }
1691            e => panic!("Expected OnOpen event with fio::NodeInfoDeprecated::File, got {:?}", e),
1692        }
1693        let events = env.file.operations.lock().unwrap();
1694        assert_eq!(
1695            *events,
1696            vec![FileOperation::Init {
1697                options: FileOptions { rights: RIGHTS_RW, is_append: false, is_linkable: true },
1698            }]
1699        );
1700    }
1701
1702    #[fuchsia::test]
1703    async fn test_read_succeeds() {
1704        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1705        let data = env.proxy.read(10).await.unwrap().map_err(Status::from_raw).unwrap();
1706        assert_eq!(data, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1707
1708        let events = env.file.operations.lock().unwrap();
1709        assert_eq!(
1710            *events,
1711            vec![
1712                FileOperation::Init {
1713                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1714                },
1715                FileOperation::ReadAt { offset: 0, count: 10 },
1716            ]
1717        );
1718    }
1719
1720    #[fuchsia::test]
1721    async fn test_read_not_readable() {
1722        let env = init_mock_file(Box::new(only_allow_init), fio::OpenFlags::RIGHT_WRITABLE);
1723        let result = env.proxy.read(10).await.unwrap().map_err(Status::from_raw);
1724        assert_eq!(result, Err(Status::BAD_HANDLE));
1725    }
1726
1727    #[fuchsia::test]
1728    async fn test_read_validates_count() {
1729        let env = init_mock_file(Box::new(only_allow_init), fio::OpenFlags::RIGHT_READABLE);
1730        let result =
1731            env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
1732        assert_eq!(result, Err(Status::OUT_OF_RANGE));
1733    }
1734
1735    #[fuchsia::test]
1736    async fn test_read_at_succeeds() {
1737        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1738        let data = env.proxy.read_at(5, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1739        assert_eq!(data, vec![10, 11, 12, 13, 14]);
1740
1741        let events = env.file.operations.lock().unwrap();
1742        assert_eq!(
1743            *events,
1744            vec![
1745                FileOperation::Init {
1746                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1747                },
1748                FileOperation::ReadAt { offset: 10, count: 5 },
1749            ]
1750        );
1751    }
1752
1753    #[fuchsia::test]
1754    async fn test_read_at_validates_count() {
1755        let env = init_mock_file(Box::new(only_allow_init), fio::OpenFlags::RIGHT_READABLE);
1756        let result = env
1757            .proxy
1758            .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
1759            .await
1760            .unwrap()
1761            .map_err(Status::from_raw);
1762        assert_eq!(result, Err(Status::OUT_OF_RANGE));
1763    }
1764
1765    #[fuchsia::test]
1766    async fn test_seek_start() {
1767        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_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 data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1778        assert_eq!(data, vec![10]);
1779        let events = env.file.operations.lock().unwrap();
1780        assert_eq!(
1781            *events,
1782            vec![
1783                FileOperation::Init {
1784                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1785                },
1786                FileOperation::ReadAt { offset: 10, count: 1 },
1787            ]
1788        );
1789    }
1790
1791    #[fuchsia::test]
1792    async fn test_seek_cur() {
1793        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1794        let offset = env
1795            .proxy
1796            .seek(fio::SeekOrigin::Start, 10)
1797            .await
1798            .unwrap()
1799            .map_err(Status::from_raw)
1800            .unwrap();
1801        assert_eq!(offset, 10);
1802
1803        let offset = env
1804            .proxy
1805            .seek(fio::SeekOrigin::Current, -2)
1806            .await
1807            .unwrap()
1808            .map_err(Status::from_raw)
1809            .unwrap();
1810        assert_eq!(offset, 8);
1811
1812        let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1813        assert_eq!(data, vec![8]);
1814        let events = env.file.operations.lock().unwrap();
1815        assert_eq!(
1816            *events,
1817            vec![
1818                FileOperation::Init {
1819                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1820                },
1821                FileOperation::ReadAt { offset: 8, count: 1 },
1822            ]
1823        );
1824    }
1825
1826    #[fuchsia::test]
1827    async fn test_seek_before_start() {
1828        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1829        let result =
1830            env.proxy.seek(fio::SeekOrigin::Current, -4).await.unwrap().map_err(Status::from_raw);
1831        assert_eq!(result, Err(Status::OUT_OF_RANGE));
1832    }
1833
1834    #[fuchsia::test]
1835    async fn test_seek_end() {
1836        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1837        let offset = env
1838            .proxy
1839            .seek(fio::SeekOrigin::End, -4)
1840            .await
1841            .unwrap()
1842            .map_err(Status::from_raw)
1843            .unwrap();
1844        assert_eq!(offset, MOCK_FILE_SIZE - 4);
1845
1846        let data = env.proxy.read(1).await.unwrap().map_err(Status::from_raw).unwrap();
1847        assert_eq!(data, vec![(offset % 256) as u8]);
1848        let events = env.file.operations.lock().unwrap();
1849        assert_eq!(
1850            *events,
1851            vec![
1852                FileOperation::Init {
1853                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1854                },
1855                FileOperation::GetSize, // for the seek
1856                FileOperation::ReadAt { offset, count: 1 },
1857            ]
1858        );
1859    }
1860
1861    #[fuchsia::test]
1862    async fn test_update_attributes() {
1863        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_WRITABLE);
1864        let attributes = fio::MutableNodeAttributes {
1865            creation_time: Some(40000),
1866            modification_time: Some(100000),
1867            mode: Some(1),
1868            ..Default::default()
1869        };
1870        let () = env
1871            .proxy
1872            .update_attributes(&attributes)
1873            .await
1874            .unwrap()
1875            .map_err(Status::from_raw)
1876            .unwrap();
1877
1878        let events = env.file.operations.lock().unwrap();
1879        assert_eq!(
1880            *events,
1881            vec![
1882                FileOperation::Init {
1883                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1884                },
1885                FileOperation::UpdateAttributes { attrs: attributes },
1886            ]
1887        );
1888    }
1889
1890    #[fuchsia::test]
1891    async fn test_deprecated_set_flags() {
1892        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_WRITABLE);
1893        let status = env.proxy.deprecated_set_flags(fio::OpenFlags::APPEND).await.unwrap();
1894        assert_eq!(Status::from_raw(status), Status::OK);
1895        let (status, flags) = env.proxy.deprecated_get_flags().await.unwrap();
1896        assert_eq!(Status::from_raw(status), Status::OK);
1897        assert_eq!(flags, fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::APPEND);
1898    }
1899
1900    #[fuchsia::test]
1901    async fn test_sync() {
1902        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::empty());
1903        let () = env.proxy.sync().await.unwrap().map_err(Status::from_raw).unwrap();
1904        let events = env.file.operations.lock().unwrap();
1905        assert_eq!(
1906            *events,
1907            vec![
1908                FileOperation::Init {
1909                    options: FileOptions {
1910                        rights: fio::Operations::GET_ATTRIBUTES,
1911                        is_append: false,
1912                        is_linkable: true
1913                    }
1914                },
1915                FileOperation::Sync
1916            ]
1917        );
1918    }
1919
1920    #[fuchsia::test]
1921    async fn test_resize() {
1922        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_WRITABLE);
1923        let () = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw).unwrap();
1924        let events = env.file.operations.lock().unwrap();
1925        assert_matches!(
1926            &events[..],
1927            [
1928                FileOperation::Init {
1929                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1930                },
1931                FileOperation::Truncate { length: 10 },
1932            ]
1933        );
1934    }
1935
1936    #[fuchsia::test]
1937    async fn test_resize_no_perms() {
1938        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1939        let result = env.proxy.resize(10).await.unwrap().map_err(Status::from_raw);
1940        assert_eq!(result, Err(Status::BAD_HANDLE));
1941        let events = env.file.operations.lock().unwrap();
1942        assert_eq!(
1943            *events,
1944            vec![FileOperation::Init {
1945                options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1946            },]
1947        );
1948    }
1949
1950    #[fuchsia::test]
1951    async fn test_write() {
1952        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_WRITABLE);
1953        let data = "Hello, world!".as_bytes();
1954        let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
1955        assert_eq!(count, data.len() as u64);
1956        let events = env.file.operations.lock().unwrap();
1957        assert_matches!(
1958            &events[..],
1959            [
1960                FileOperation::Init {
1961                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
1962                },
1963                FileOperation::WriteAt { offset: 0, .. },
1964            ]
1965        );
1966        if let FileOperation::WriteAt { content, .. } = &events[1] {
1967            assert_eq!(content.as_slice(), data);
1968        } else {
1969            unreachable!();
1970        }
1971    }
1972
1973    #[fuchsia::test]
1974    async fn test_write_no_perms() {
1975        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_READABLE);
1976        let data = "Hello, world!".as_bytes();
1977        let result = env.proxy.write(data).await.unwrap().map_err(Status::from_raw);
1978        assert_eq!(result, Err(Status::BAD_HANDLE));
1979        let events = env.file.operations.lock().unwrap();
1980        assert_eq!(
1981            *events,
1982            vec![FileOperation::Init {
1983                options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
1984            },]
1985        );
1986    }
1987
1988    #[fuchsia::test]
1989    async fn test_write_at() {
1990        let env = init_mock_file(Box::new(always_succeed_callback), fio::OpenFlags::RIGHT_WRITABLE);
1991        let data = "Hello, world!".as_bytes();
1992        let count = env.proxy.write_at(data, 10).await.unwrap().map_err(Status::from_raw).unwrap();
1993        assert_eq!(count, data.len() as u64);
1994        let events = env.file.operations.lock().unwrap();
1995        assert_matches!(
1996            &events[..],
1997            [
1998                FileOperation::Init {
1999                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2000                },
2001                FileOperation::WriteAt { offset: 10, .. },
2002            ]
2003        );
2004        if let FileOperation::WriteAt { content, .. } = &events[1] {
2005            assert_eq!(content.as_slice(), data);
2006        } else {
2007            unreachable!();
2008        }
2009    }
2010
2011    #[fuchsia::test]
2012    async fn test_append() {
2013        let env = init_mock_file(
2014            Box::new(always_succeed_callback),
2015            fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::APPEND,
2016        );
2017        let data = "Hello, world!".as_bytes();
2018        let count = env.proxy.write(data).await.unwrap().map_err(Status::from_raw).unwrap();
2019        assert_eq!(count, data.len() as u64);
2020        let offset = env
2021            .proxy
2022            .seek(fio::SeekOrigin::Current, 0)
2023            .await
2024            .unwrap()
2025            .map_err(Status::from_raw)
2026            .unwrap();
2027        assert_eq!(offset, MOCK_FILE_SIZE + data.len() as u64);
2028        let events = env.file.operations.lock().unwrap();
2029        assert_matches!(
2030            &events[..],
2031            [
2032                FileOperation::Init {
2033                    options: FileOptions { rights: RIGHTS_W, is_append: true, .. }
2034                },
2035                FileOperation::Append { .. }
2036            ]
2037        );
2038        if let FileOperation::Append { content } = &events[1] {
2039            assert_eq!(content.as_slice(), data);
2040        } else {
2041            unreachable!();
2042        }
2043    }
2044
2045    #[cfg(target_os = "fuchsia")]
2046    mod stream_tests {
2047        use super::*;
2048
2049        fn init_mock_stream_file(vmo: zx::Vmo, flags: fio::OpenFlags) -> TestEnv {
2050            let file = MockFile::new_with_vmo(Box::new(always_succeed_callback), vmo);
2051            let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
2052
2053            let scope = ExecutionScope::new();
2054
2055            let cloned_file = file.clone();
2056            let cloned_scope = scope.clone();
2057
2058            flags.to_object_request(server_end).create_connection_sync::<StreamIoConnection<_>, _>(
2059                cloned_scope,
2060                cloned_file,
2061                flags,
2062            );
2063
2064            TestEnv { file, proxy, scope }
2065        }
2066
2067        #[fuchsia::test]
2068        async fn test_stream_describe() {
2069            const VMO_CONTENTS: &[u8] = b"hello there";
2070            let vmo = zx::Vmo::create(VMO_CONTENTS.len() as u64).unwrap();
2071            vmo.write(VMO_CONTENTS, 0).unwrap();
2072            let flags = fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE;
2073            let env = init_mock_stream_file(vmo, flags);
2074
2075            let fio::FileInfo { stream: Some(stream), .. } = env.proxy.describe().await.unwrap()
2076            else {
2077                panic!("Missing stream")
2078            };
2079            let contents =
2080                stream.read_to_vec(zx::StreamReadOptions::empty(), 20).expect("read failed");
2081            assert_eq!(contents, VMO_CONTENTS);
2082        }
2083
2084        #[fuchsia::test]
2085        async fn test_stream_read() {
2086            let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2087            let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2088            vmo.write(&vmo_contents, 0).unwrap();
2089            let flags = fio::OpenFlags::RIGHT_READABLE;
2090            let env = init_mock_stream_file(vmo, flags);
2091
2092            let data = env
2093                .proxy
2094                .read(vmo_contents.len() as u64)
2095                .await
2096                .unwrap()
2097                .map_err(Status::from_raw)
2098                .unwrap();
2099            assert_eq!(data, vmo_contents);
2100
2101            let events = env.file.operations.lock().unwrap();
2102            assert_eq!(
2103                *events,
2104                [FileOperation::Init {
2105                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2106                },]
2107            );
2108        }
2109
2110        #[fuchsia::test]
2111        async fn test_stream_read_at() {
2112            let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2113            let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2114            vmo.write(&vmo_contents, 0).unwrap();
2115            let flags = fio::OpenFlags::RIGHT_READABLE;
2116            let env = init_mock_stream_file(vmo, flags);
2117
2118            const OFFSET: u64 = 4;
2119            let data = env
2120                .proxy
2121                .read_at((vmo_contents.len() as u64) - OFFSET, OFFSET)
2122                .await
2123                .unwrap()
2124                .map_err(Status::from_raw)
2125                .unwrap();
2126            assert_eq!(data, vmo_contents[OFFSET as usize..]);
2127
2128            let events = env.file.operations.lock().unwrap();
2129            assert_eq!(
2130                *events,
2131                [FileOperation::Init {
2132                    options: FileOptions { rights: RIGHTS_R, is_append: false, is_linkable: true }
2133                },]
2134            );
2135        }
2136
2137        #[fuchsia::test]
2138        async fn test_stream_write() {
2139            const DATA_SIZE: u64 = 10;
2140            let vmo = zx::Vmo::create(DATA_SIZE).unwrap();
2141            let flags = fio::OpenFlags::RIGHT_WRITABLE;
2142            let env = init_mock_stream_file(
2143                vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2144                flags,
2145            );
2146
2147            let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2148            let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2149            assert_eq!(written, DATA_SIZE);
2150            let mut vmo_contents = [0; DATA_SIZE as usize];
2151            vmo.read(&mut vmo_contents, 0).unwrap();
2152            assert_eq!(vmo_contents, data);
2153
2154            let events = env.file.operations.lock().unwrap();
2155            assert_eq!(
2156                *events,
2157                [FileOperation::Init {
2158                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2159                },]
2160            );
2161        }
2162
2163        #[fuchsia::test]
2164        async fn test_stream_write_at() {
2165            const OFFSET: u64 = 4;
2166            const DATA_SIZE: u64 = 10;
2167            let vmo = zx::Vmo::create(DATA_SIZE + OFFSET).unwrap();
2168            let flags = fio::OpenFlags::RIGHT_WRITABLE;
2169            let env = init_mock_stream_file(
2170                vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2171                flags,
2172            );
2173
2174            let data: [u8; DATA_SIZE as usize] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2175            let written =
2176                env.proxy.write_at(&data, OFFSET).await.unwrap().map_err(Status::from_raw).unwrap();
2177            assert_eq!(written, DATA_SIZE);
2178            let mut vmo_contents = [0; DATA_SIZE as usize];
2179            vmo.read(&mut vmo_contents, OFFSET).unwrap();
2180            assert_eq!(vmo_contents, data);
2181
2182            let events = env.file.operations.lock().unwrap();
2183            assert_eq!(
2184                *events,
2185                [FileOperation::Init {
2186                    options: FileOptions { rights: RIGHTS_W, is_append: false, is_linkable: true }
2187                }]
2188            );
2189        }
2190
2191        #[fuchsia::test]
2192        async fn test_stream_seek() {
2193            let vmo_contents = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
2194            let vmo = zx::Vmo::create(vmo_contents.len() as u64).unwrap();
2195            vmo.write(&vmo_contents, 0).unwrap();
2196            let flags = fio::OpenFlags::RIGHT_READABLE;
2197            let env = init_mock_stream_file(vmo, flags);
2198
2199            let position = env
2200                .proxy
2201                .seek(fio::SeekOrigin::Start, 8)
2202                .await
2203                .unwrap()
2204                .map_err(Status::from_raw)
2205                .unwrap();
2206            assert_eq!(position, 8);
2207            let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2208            assert_eq!(data, [1, 0]);
2209
2210            let position = env
2211                .proxy
2212                .seek(fio::SeekOrigin::Current, -4)
2213                .await
2214                .unwrap()
2215                .map_err(Status::from_raw)
2216                .unwrap();
2217            // Seeked to 8, read 2, seeked backwards 4. 8 + 2 - 4 = 6.
2218            assert_eq!(position, 6);
2219            let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2220            assert_eq!(data, [3, 2]);
2221
2222            let position = env
2223                .proxy
2224                .seek(fio::SeekOrigin::End, -6)
2225                .await
2226                .unwrap()
2227                .map_err(Status::from_raw)
2228                .unwrap();
2229            assert_eq!(position, 4);
2230            let data = env.proxy.read(2).await.unwrap().map_err(Status::from_raw).unwrap();
2231            assert_eq!(data, [5, 4]);
2232
2233            let e = env
2234                .proxy
2235                .seek(fio::SeekOrigin::Start, -1)
2236                .await
2237                .unwrap()
2238                .map_err(Status::from_raw)
2239                .expect_err("Seeking before the start of a file should be an error");
2240            assert_eq!(e, Status::INVALID_ARGS);
2241        }
2242
2243        #[fuchsia::test]
2244        async fn test_stream_deprecated_set_flags() {
2245            let data = [0, 1, 2, 3, 4];
2246            let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 100).unwrap();
2247            let flags = fio::OpenFlags::RIGHT_WRITABLE;
2248            let env = init_mock_stream_file(
2249                vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2250                flags,
2251            );
2252
2253            let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2254            assert_eq!(written, data.len() as u64);
2255            // Data was not appended.
2256            assert_eq!(vmo.get_content_size().unwrap(), 100);
2257
2258            // Switch to append mode.
2259            zx::ok(env.proxy.deprecated_set_flags(fio::OpenFlags::APPEND).await.unwrap()).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 appended.
2269            assert_eq!(vmo.get_content_size().unwrap(), 105);
2270
2271            // Switch out of append mode.
2272            zx::ok(env.proxy.deprecated_set_flags(fio::OpenFlags::empty()).await.unwrap()).unwrap();
2273            env.proxy
2274                .seek(fio::SeekOrigin::Start, 0)
2275                .await
2276                .unwrap()
2277                .map_err(Status::from_raw)
2278                .unwrap();
2279            let written = env.proxy.write(&data).await.unwrap().map_err(Status::from_raw).unwrap();
2280            assert_eq!(written, data.len() as u64);
2281            // Data was not appended.
2282            assert_eq!(vmo.get_content_size().unwrap(), 105);
2283        }
2284
2285        #[fuchsia::test]
2286        async fn test_stream_read_validates_count() {
2287            let vmo = zx::Vmo::create(10).unwrap();
2288            let flags = fio::OpenFlags::RIGHT_READABLE;
2289            let env = init_mock_stream_file(vmo, flags);
2290            let result =
2291                env.proxy.read(fio::MAX_TRANSFER_SIZE + 1).await.unwrap().map_err(Status::from_raw);
2292            assert_eq!(result, Err(Status::OUT_OF_RANGE));
2293        }
2294
2295        #[fuchsia::test]
2296        async fn test_stream_read_at_validates_count() {
2297            let vmo = zx::Vmo::create(10).unwrap();
2298            let flags = fio::OpenFlags::RIGHT_READABLE;
2299            let env = init_mock_stream_file(vmo, flags);
2300            let result = env
2301                .proxy
2302                .read_at(fio::MAX_TRANSFER_SIZE + 1, 0)
2303                .await
2304                .unwrap()
2305                .map_err(Status::from_raw);
2306            assert_eq!(result, Err(Status::OUT_OF_RANGE));
2307        }
2308    }
2309}