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