vfs/file/
connection.rs

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