vfs/file/
connection.rs

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