vfs/file/
connection.rs

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