Skip to main content

vfs/file/
connection.rs

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