fuchsia_fs/
file.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
5//! Utility functions for fuchsia.io files.
6
7use crate::node::{CloseError, OpenError};
8use fidl::{persist, unpersist, Persistable};
9use flex_fuchsia_io as fio;
10use thiserror::Error;
11
12mod async_reader;
13pub use async_reader::AsyncReader;
14
15mod async_read_at;
16pub use async_read_at::{Adapter, AsyncFile, AsyncGetSize, AsyncGetSizeExt, AsyncReadAt};
17mod async_read_at_ext;
18pub use async_read_at_ext::AsyncReadAtExt;
19mod buffered_async_read_at;
20pub use buffered_async_read_at::BufferedAsyncReadAt;
21
22#[cfg(target_os = "fuchsia")]
23#[cfg(not(feature = "fdomain"))]
24pub use fuchsia::*;
25
26#[cfg(target_os = "fuchsia")]
27#[cfg(not(feature = "fdomain"))]
28mod fuchsia {
29    use super::*;
30    use crate::node::{take_on_open_event, Kind};
31
32    /// An error encountered while reading a named file
33    #[derive(Debug, Error)]
34    #[error("error reading '{path}': {source}")]
35    pub struct ReadNamedError {
36        pub(super) path: String,
37
38        #[source]
39        pub(super) source: ReadError,
40    }
41
42    impl ReadNamedError {
43        /// Returns the path associated with this error.
44        pub fn path(&self) -> &str {
45            &self.path
46        }
47
48        /// Unwraps the inner read error, discarding the associated path.
49        pub fn into_inner(self) -> ReadError {
50            self.source
51        }
52
53        /// Returns true if the read failed because the file was no found.
54        pub fn is_not_found_error(&self) -> bool {
55            self.source.is_not_found_error()
56        }
57    }
58
59    /// An error encountered while writing a named file
60    #[derive(Debug, Error)]
61    #[error("error writing '{path}': {source}")]
62    pub struct WriteNamedError {
63        pub(super) path: String,
64
65        #[source]
66        pub(super) source: WriteError,
67    }
68
69    impl WriteNamedError {
70        /// Returns the path associated with this error.
71        pub fn path(&self) -> &str {
72            &self.path
73        }
74
75        /// Unwraps the inner write error, discarding the associated path.
76        pub fn into_inner(self) -> WriteError {
77            self.source
78        }
79    }
80
81    /// Opens the given `path` from the current namespace as a [`FileProxy`].
82    ///
83    /// To connect to a filesystem node which doesn't implement fuchsia.io.File, use the functions
84    /// in [`fuchsia_component::client`] instead.
85    ///
86    /// If the namespace path doesn't exist, or we fail to make the channel pair, this returns an
87    /// error. However, if incorrect flags are sent, or if the rest of the path sent to the
88    /// filesystem server doesn't exist, this will still return success. Instead, the returned
89    /// FileProxy channel pair will be closed with an epitaph.
90    pub fn open_in_namespace(path: &str, flags: fio::Flags) -> Result<fio::FileProxy, OpenError> {
91        let (node, request) = fidl::endpoints::create_proxy();
92        open_channel_in_namespace(path, flags, request)?;
93        Ok(node)
94    }
95
96    /// Asynchronously opens the given [`path`] in the current namespace, serving the connection
97    /// over [`request`]. Once the channel is connected, any calls made prior are serviced.
98    ///
99    /// To connect to a filesystem node which doesn't implement fuchsia.io.File, use the functions
100    /// in [`fuchsia_component::client`] instead.
101    ///
102    /// If the namespace path doesn't exist, this returns an error. However, if incorrect flags are
103    /// sent, or if the rest of the path sent to the filesystem server doesn't exist, this will
104    /// still return success. Instead, the [`request`] channel will be closed with an epitaph.
105    pub fn open_channel_in_namespace(
106        path: &str,
107        flags: fio::Flags,
108        request: fidl::endpoints::ServerEnd<fio::FileMarker>,
109    ) -> Result<(), OpenError> {
110        let flags = flags | fio::Flags::PROTOCOL_FILE;
111        let namespace = fdio::Namespace::installed().map_err(OpenError::Namespace)?;
112        namespace.open(path, flags, request.into_channel()).map_err(OpenError::Namespace)
113    }
114
115    /// Write the given data into a file at `path` in the current namespace. The path must be an
116    /// absolute path.
117    /// * If the file already exists, replaces existing contents.
118    /// * If the file does not exist, creates the file.
119    pub async fn write_in_namespace<D>(path: &str, data: D) -> Result<(), WriteNamedError>
120    where
121        D: AsRef<[u8]>,
122    {
123        async {
124            let flags =
125                fio::Flags::FLAG_MAYBE_CREATE | fio::Flags::FILE_TRUNCATE | fio::PERM_WRITABLE;
126            let file = open_in_namespace(path, flags)?;
127
128            write(&file, data).await?;
129
130            let _ = close(file).await;
131            Ok(())
132        }
133        .await
134        .map_err(|source| WriteNamedError { path: path.to_owned(), source })
135    }
136
137    /// Write the given FIDL encoded message into a file at `path`. The path must be an absolute
138    /// path.
139    /// * If the file already exists, replaces existing contents.
140    /// * If the file does not exist, creates the file.
141    pub async fn write_fidl_in_namespace<T: Persistable>(
142        path: &str,
143        data: &mut T,
144    ) -> Result<(), WriteNamedError> {
145        let data = persist(data)
146            .map_err(|source| WriteNamedError { path: path.to_owned(), source: source.into() })?;
147        write_in_namespace(path, data).await?;
148        Ok(())
149    }
150
151    /// Reads all data from the file at `path` in the current namespace. The path must be an
152    /// absolute path.
153    pub async fn read_in_namespace(path: &str) -> Result<Vec<u8>, ReadNamedError> {
154        async {
155            let file = open_in_namespace(
156                path,
157                fio::Flags::FLAG_SEND_REPRESENTATION
158                    | fio::PERM_READABLE
159                    | fio::Flags::PROTOCOL_FILE,
160            )?;
161            read_file_with_on_open_event(file).await
162        }
163        .await
164        .map_err(|source| ReadNamedError { path: path.to_owned(), source })
165    }
166
167    /// Reads a utf-8 encoded string from the file at `path` in the current namespace. The path must
168    /// be an absolute path.
169    pub async fn read_in_namespace_to_string(path: &str) -> Result<String, ReadNamedError> {
170        let bytes = read_in_namespace(path).await?;
171        let string = String::from_utf8(bytes)
172            .map_err(|source| ReadNamedError { path: path.to_owned(), source: source.into() })?;
173        Ok(string)
174    }
175
176    /// Read the given FIDL message from binary file at `path` in the current namespace. The path
177    /// must be an absolute path.
178    /// FIDL structure should be provided at a read time.
179    /// Incompatible data is populated as per FIDL ABI compatibility guide:
180    /// https://fuchsia.dev/fuchsia-src/development/languages/fidl/guides/abi-compat
181    pub async fn read_in_namespace_to_fidl<T: Persistable>(
182        path: &str,
183    ) -> Result<T, ReadNamedError> {
184        let bytes = read_in_namespace(path).await?;
185        unpersist(&bytes)
186            .map_err(|source| ReadNamedError { path: path.to_owned(), source: source.into() })
187    }
188
189    /// Extracts the stream from an OnOpen or OnRepresentation FileEvent.
190    pub(super) fn extract_stream_from_on_open_event(
191        event: fio::FileEvent,
192    ) -> Result<Option<zx::Stream>, OpenError> {
193        match event {
194            fio::FileEvent::OnOpen_ { s: status, info } => {
195                zx::Status::ok(status).map_err(OpenError::OpenError)?;
196                let node_info = info.ok_or(OpenError::MissingOnOpenInfo)?;
197                match *node_info {
198                    fio::NodeInfoDeprecated::File(file_info) => Ok(file_info.stream),
199                    node_info @ _ => Err(OpenError::UnexpectedNodeKind {
200                        expected: Kind::File,
201                        actual: Kind::kind_of(&node_info),
202                    }),
203                }
204            }
205            fio::FileEvent::OnRepresentation { payload } => match payload {
206                fio::Representation::File(file_info) => Ok(file_info.stream),
207                representation @ _ => Err(OpenError::UnexpectedNodeKind {
208                    expected: Kind::File,
209                    actual: Kind::kind_of2(&representation),
210                }),
211            },
212            fio::FileEvent::_UnknownEvent { ordinal, .. } => {
213                Err(OpenError::UnknownEvent { ordinal })
214            }
215        }
216    }
217
218    /// Reads the contents of a stream into a Vec.
219    pub(super) fn read_contents_of_stream(stream: zx::Stream) -> Result<Vec<u8>, ReadError> {
220        // TODO(https://fxbug.dev/324239375): Get the file size from the OnRepresentation event.
221        let file_size =
222            stream.seek(std::io::SeekFrom::End(0)).map_err(ReadError::ReadError)? as usize;
223        let mut data = Vec::with_capacity(file_size);
224        let mut remaining = file_size;
225        while remaining > 0 {
226            // read_at is used instead of read because the seek offset was moved to the end of the
227            // file to determine the file size. Moving the seek offset back to the start of the file
228            // would require another syscall.
229            let actual = stream
230                .read_at_uninit(
231                    zx::StreamReadOptions::empty(),
232                    data.len() as u64,
233                    &mut data.spare_capacity_mut()[0..remaining],
234                )
235                .map_err(ReadError::ReadError)?;
236            // A read of 0 bytes indicates the end of the file was reached. The file may have
237            // changed size since the seek.
238            if actual == 0 {
239                break;
240            }
241            // SAFETY: read_at_uninit returns the number of bytes that were read and initialized.
242            unsafe { data.set_len(data.len() + actual) };
243            remaining -= actual;
244        }
245        Ok(data)
246    }
247
248    /// Reads the contents of `file` into a Vec. `file` must have been opened with either `DESCRIBE`
249    /// or `SEND_REPRESENTATION` and the event must not have been read yet.
250    pub(crate) async fn read_file_with_on_open_event(
251        file: fio::FileProxy,
252    ) -> Result<Vec<u8>, ReadError> {
253        let event = take_on_open_event(&file).await.map_err(ReadError::Open)?;
254        let stream = extract_stream_from_on_open_event(event).map_err(ReadError::Open)?;
255
256        if let Some(stream) = stream {
257            read_contents_of_stream(stream)
258        } else {
259            // Fall back to FIDL reads if the file doesn't support streams.
260            read(&file).await
261        }
262    }
263}
264
265/// An error encountered while reading a file
266#[derive(Debug, Error)]
267#[allow(missing_docs)]
268pub enum ReadError {
269    #[error("while opening the file: {0:?}")]
270    Open(#[from] OpenError),
271
272    #[error("read call failed: {0:?}")]
273    Fidl(#[from] fidl::Error),
274
275    #[error("read failed with status: {0}")]
276    ReadError(#[source] zx_status::Status),
277
278    #[error("file was not a utf-8 encoded string: {0}")]
279    InvalidUtf8(#[from] std::string::FromUtf8Error),
280}
281
282impl ReadError {
283    /// Returns true if the read failed because the file was no found.
284    pub fn is_not_found_error(&self) -> bool {
285        matches!(self, ReadError::Open(e) if e.is_not_found_error())
286    }
287}
288
289/// An error encountered while writing a file
290#[derive(Debug, Error)]
291#[allow(missing_docs)]
292pub enum WriteError {
293    #[error("while creating the file: {0}")]
294    Create(#[from] OpenError),
295
296    #[error("write call failed: {0}")]
297    Fidl(#[from] fidl::Error),
298
299    #[error("write failed with status: {0}")]
300    WriteError(#[source] zx_status::Status),
301
302    #[error("file endpoint reported more bytes written than were provided")]
303    Overwrite,
304}
305
306/// Gracefully closes the file proxy from the remote end.
307pub async fn close(file: fio::FileProxy) -> Result<(), CloseError> {
308    let result = file.close().await.map_err(CloseError::SendCloseRequest)?;
309    result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
310}
311
312/// Writes the given data into the given file.
313pub async fn write<D>(file: &fio::FileProxy, data: D) -> Result<(), WriteError>
314where
315    D: AsRef<[u8]>,
316{
317    let mut data = data.as_ref();
318
319    while !data.is_empty() {
320        let bytes_written = file
321            .write(&data[..std::cmp::min(fio::MAX_BUF as usize, data.len())])
322            .await?
323            .map_err(|s| WriteError::WriteError(zx_status::Status::from_raw(s)))?;
324
325        if bytes_written > data.len() as u64 {
326            return Err(WriteError::Overwrite);
327        }
328
329        data = &data[bytes_written as usize..];
330    }
331    Ok(())
332}
333
334/// Write the given FIDL message in a binary form into a file open for writing.
335pub async fn write_fidl<T: Persistable>(
336    file: &fio::FileProxy,
337    data: &mut T,
338) -> Result<(), WriteError> {
339    write(file, persist(data)?).await?;
340    Ok(())
341}
342
343/// Reads all data from the given file's current offset to the end of the file.
344pub async fn read(file: &fio::FileProxy) -> Result<Vec<u8>, ReadError> {
345    let mut out = Vec::new();
346
347    loop {
348        let mut bytes = file
349            .read(fio::MAX_BUF)
350            .await?
351            .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
352        if bytes.is_empty() {
353            break;
354        }
355        out.append(&mut bytes);
356    }
357    Ok(out)
358}
359
360/// Attempts to read a number of bytes from the given file's current offset.
361/// This function may return less data than expected.
362pub async fn read_num_bytes(file: &fio::FileProxy, num_bytes: u64) -> Result<Vec<u8>, ReadError> {
363    let mut data = vec![];
364
365    // Read in chunks of |MAX_BUF| bytes.
366    // This is the maximum buffer size supported over FIDL.
367    let mut bytes_left = num_bytes;
368    while bytes_left > 0 {
369        let bytes_to_read = std::cmp::min(bytes_left, fio::MAX_BUF);
370        let mut bytes = file
371            .read(bytes_to_read)
372            .await?
373            .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
374
375        if bytes.is_empty() {
376            break;
377        }
378
379        bytes_left -= bytes.len() as u64;
380        data.append(&mut bytes);
381    }
382
383    // Remove excess data read in, if any.
384    let num_bytes = num_bytes as usize;
385    if data.len() > num_bytes {
386        data.drain(num_bytes..data.len());
387    }
388
389    Ok(data)
390}
391
392/// Reads a utf-8 encoded string from the given file's current offset to the end of the file.
393pub async fn read_to_string(file: &fio::FileProxy) -> Result<String, ReadError> {
394    let bytes = read(file).await?;
395    let string = String::from_utf8(bytes)?;
396    Ok(string)
397}
398
399/// Read the given FIDL message from binary form from a file open for reading.
400/// FIDL structure should be provided at a read time.
401/// Incompatible data is populated as per FIDL ABI compatibility guide:
402/// https://fuchsia.dev/fuchsia-src/development/languages/fidl/guides/abi-compat
403pub async fn read_fidl<T: Persistable>(file: &fio::FileProxy) -> Result<T, ReadError> {
404    let bytes = read(file).await?;
405    Ok(unpersist(&bytes)?)
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411    use crate::directory;
412    use crate::node::{take_on_open_event, Kind};
413    use assert_matches::assert_matches;
414    use fidl_fidl_test_schema::{DataTable1, DataTable2};
415    use fuchsia_async as fasync;
416    use std::path::Path;
417    use std::sync::Arc;
418    use tempfile::TempDir;
419    use vfs::execution_scope::ExecutionScope;
420    use vfs::file::vmo::{read_only, VmoFile};
421    use vfs::ToObjectRequest;
422    use zx::{self as zx, HandleBased as _};
423
424    const DATA_FILE_CONTENTS: &str = "Hello World!\n";
425
426    // open_in_namespace
427
428    #[fasync::run_singlethreaded(test)]
429    async fn open_in_namespace_opens_real_file() {
430        let exists = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
431        assert_matches!(close(exists).await, Ok(()));
432    }
433
434    #[fasync::run_singlethreaded(test)]
435    async fn open_in_namespace_opens_fake_file_under_of_root_namespace_entry() {
436        let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
437        // The open error is not detected until the proxy is interacted with.
438        assert_matches!(close(notfound).await, Err(_));
439    }
440
441    #[fasync::run_singlethreaded(test)]
442    async fn open_in_namespace_rejects_fake_root_namespace_entry() {
443        assert_matches!(
444            open_in_namespace("/fake", fio::PERM_READABLE),
445            Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
446        );
447    }
448
449    // write_in_namespace
450
451    #[fasync::run_singlethreaded(test)]
452    async fn write_in_namespace_creates_file() {
453        let tempdir = TempDir::new().unwrap();
454        let path = tempdir.path().join(Path::new("new-file")).to_str().unwrap().to_owned();
455
456        // Write contents.
457        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
458        write_in_namespace(&path, data).await.unwrap();
459
460        // Verify contents.
461        let contents = std::fs::read(&path).unwrap();
462        assert_eq!(&contents, &data);
463    }
464
465    #[fasync::run_singlethreaded(test)]
466    async fn write_in_namespace_overwrites_existing_file() {
467        let tempdir = TempDir::new().unwrap();
468        let path = tempdir.path().join(Path::new("existing-file")).to_str().unwrap().to_owned();
469
470        // Write contents.
471        let original_data = b"\x80\x81"; // Non UTF-8 data: a continuation byte as the first byte.
472        write_in_namespace(&path, original_data).await.unwrap();
473
474        // Over-write contents.
475        let new_data = b"\x82"; // Non UTF-8 data: a continuation byte as the first byte.
476        write_in_namespace(&path, new_data).await.unwrap();
477
478        // Verify contents.
479        let contents = std::fs::read(&path).unwrap();
480        assert_eq!(&contents, &new_data);
481    }
482
483    #[fasync::run_singlethreaded(test)]
484    async fn write_in_namespace_fails_on_invalid_namespace_entry() {
485        assert_matches!(
486            write_in_namespace("/fake", b"").await,
487            Err(WriteNamedError { path, source: WriteError::Create(_) }) if path == "/fake"
488        );
489        let err = write_in_namespace("/fake", b"").await.unwrap_err();
490        assert_eq!(err.path(), "/fake");
491        assert_matches!(err.into_inner(), WriteError::Create(_));
492    }
493
494    // write
495
496    #[fasync::run_singlethreaded(test)]
497    async fn write_writes_to_file() {
498        let tempdir = TempDir::new().unwrap();
499        let dir = directory::open_in_namespace(
500            tempdir.path().to_str().unwrap(),
501            fio::PERM_READABLE | fio::PERM_WRITABLE,
502        )
503        .unwrap();
504
505        // Write contents.
506        let file =
507            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
508                .await
509                .unwrap();
510        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
511        write(&file, data).await.unwrap();
512
513        // Verify contents.
514        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
515        assert_eq!(&contents, &data);
516    }
517
518    #[fasync::run_singlethreaded(test)]
519    async fn write_writes_to_file_in_chunks_if_needed() {
520        let tempdir = TempDir::new().unwrap();
521        let dir = directory::open_in_namespace(
522            tempdir.path().to_str().unwrap(),
523            fio::PERM_READABLE | fio::PERM_WRITABLE,
524        )
525        .unwrap();
526
527        // Write contents.
528        let file =
529            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
530                .await
531                .unwrap();
532        let data = "abc".repeat(10000);
533        write(&file, &data).await.unwrap();
534
535        // Verify contents.
536        let contents = std::fs::read_to_string(tempdir.path().join(Path::new("file"))).unwrap();
537        assert_eq!(&contents, &data);
538    }
539
540    #[fasync::run_singlethreaded(test)]
541    async fn write_appends_to_file() {
542        let tempdir = TempDir::new().unwrap();
543        let dir = directory::open_in_namespace(
544            tempdir.path().to_str().unwrap(),
545            fio::PERM_READABLE | fio::PERM_WRITABLE,
546        )
547        .unwrap();
548
549        // Create and write to the file.
550        let file =
551            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
552                .await
553                .unwrap();
554        write(&file, "Hello ").await.unwrap();
555        write(&file, "World!\n").await.unwrap();
556        close(file).await.unwrap();
557
558        // Verify contents.
559        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
560        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
561    }
562
563    // read
564
565    #[fasync::run_singlethreaded(test)]
566    async fn read_reads_to_end_of_file() {
567        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
568
569        let contents = read(&file).await.unwrap();
570        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
571    }
572
573    #[fasync::run_singlethreaded(test)]
574    async fn read_reads_from_current_position() {
575        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
576
577        // Advance past the first byte.
578        let _: Vec<u8> = file.read(1).await.unwrap().unwrap();
579
580        // Verify the rest of the file is read.
581        let contents = read(&file).await.unwrap();
582        assert_eq!(&contents[..], "ello World!\n".as_bytes());
583    }
584
585    // read_in_namespace
586
587    #[fasync::run_singlethreaded(test)]
588    async fn read_in_namespace_reads_contents() {
589        let contents = read_in_namespace("/pkg/data/file").await.unwrap();
590        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
591    }
592
593    #[fasync::run_singlethreaded(test)]
594    async fn read_in_namespace_fails_on_invalid_namespace_entry() {
595        assert_matches!(
596            read_in_namespace("/fake").await,
597            Err(ReadNamedError { path, source: ReadError::Open(_) }) if path == "/fake"
598        );
599        let err = read_in_namespace("/fake").await.unwrap_err();
600        assert_eq!(err.path(), "/fake");
601        assert_matches!(err.into_inner(), ReadError::Open(_));
602    }
603
604    // read_to_string
605
606    #[fasync::run_singlethreaded(test)]
607    async fn read_to_string_reads_data_file() {
608        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
609        assert_eq!(read_to_string(&file).await.unwrap(), DATA_FILE_CONTENTS);
610    }
611
612    // read_in_namespace_to_string
613
614    #[fasync::run_singlethreaded(test)]
615    async fn read_in_namespace_to_string_reads_data_file() {
616        assert_eq!(
617            read_in_namespace_to_string("/pkg/data/file").await.unwrap(),
618            DATA_FILE_CONTENTS
619        );
620    }
621
622    // write_fidl
623
624    #[fasync::run_singlethreaded(test)]
625    async fn write_fidl_writes_to_file() {
626        let tempdir = TempDir::new().unwrap();
627        let dir = directory::open_in_namespace(
628            tempdir.path().to_str().unwrap(),
629            fio::PERM_READABLE | fio::PERM_WRITABLE,
630        )
631        .unwrap();
632
633        // Write contents.
634        let file =
635            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
636                .await
637                .unwrap();
638
639        let mut data = DataTable1 {
640            num: Some(42),
641            string: Some(DATA_FILE_CONTENTS.to_string()),
642            ..Default::default()
643        };
644
645        // Binary encoded FIDL message, with header and padding.
646        let fidl_bytes = persist(&data).unwrap();
647
648        write_fidl(&file, &mut data).await.unwrap();
649
650        // Verify contents.
651        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
652        assert_eq!(&contents, &fidl_bytes);
653    }
654
655    #[fasync::run_singlethreaded(test)]
656    async fn read_fidl_reads_from_file() {
657        let file = open_in_namespace("/pkg/data/fidl_file", fio::PERM_READABLE).unwrap();
658
659        let contents = read_fidl::<DataTable2>(&file).await.unwrap();
660
661        let data = DataTable2 {
662            num: Some(42),
663            string: Some(DATA_FILE_CONTENTS.to_string()),
664            new_field: None,
665            ..Default::default()
666        };
667        assert_eq!(&contents, &data);
668    }
669
670    #[test]
671    fn extract_stream_from_on_open_event_with_stream() {
672        let vmo = zx::Vmo::create(0).unwrap();
673        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
674        let event = fio::FileEvent::OnOpen_ {
675            s: 0,
676            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
677                stream: Some(stream),
678                event: None,
679            }))),
680        };
681        let stream = extract_stream_from_on_open_event(event)
682            .expect("Not a file")
683            .expect("Stream not present");
684        assert!(!stream.is_invalid_handle());
685    }
686
687    #[test]
688    fn extract_stream_from_on_open_event_without_stream() {
689        let event = fio::FileEvent::OnOpen_ {
690            s: 0,
691            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
692                stream: None,
693                event: None,
694            }))),
695        };
696        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
697        assert!(stream.is_none());
698    }
699
700    #[test]
701    fn extract_stream_from_on_open_event_with_open_error() {
702        let event = fio::FileEvent::OnOpen_ { s: zx::Status::NOT_FOUND.into_raw(), info: None };
703        let result = extract_stream_from_on_open_event(event);
704        assert_matches!(result, Err(OpenError::OpenError(zx::Status::NOT_FOUND)));
705    }
706
707    #[test]
708    fn extract_stream_from_on_open_event_not_a_file() {
709        let event = fio::FileEvent::OnOpen_ {
710            s: 0,
711            info: Some(Box::new(fio::NodeInfoDeprecated::Service(fio::Service))),
712        };
713        let result = extract_stream_from_on_open_event(event);
714        assert_matches!(
715            result,
716            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Service })
717        );
718    }
719
720    #[test]
721    fn extract_stream_from_on_representation_event_with_stream() {
722        let vmo = zx::Vmo::create(0).unwrap();
723        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
724        let event = fio::FileEvent::OnRepresentation {
725            payload: fio::Representation::File(fio::FileInfo {
726                stream: Some(stream),
727                ..Default::default()
728            }),
729        };
730        let stream = extract_stream_from_on_open_event(event)
731            .expect("Not a file")
732            .expect("Stream not present");
733        assert!(!stream.is_invalid_handle());
734    }
735
736    #[test]
737    fn extract_stream_from_on_representation_event_without_stream() {
738        let event = fio::FileEvent::OnRepresentation {
739            payload: fio::Representation::File(fio::FileInfo::default()),
740        };
741        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
742        assert!(stream.is_none());
743    }
744
745    #[test]
746    fn extract_stream_from_on_representation_event_not_a_file() {
747        let event = fio::FileEvent::OnRepresentation {
748            payload: fio::Representation::Directory(Default::default()),
749        };
750        let result = extract_stream_from_on_open_event(event);
751        assert_matches!(
752            result,
753            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Directory })
754        );
755    }
756
757    #[test]
758    fn read_contents_of_stream_with_contents() {
759        let data = b"file-contents".repeat(1000);
760        let vmo = zx::Vmo::create(data.len() as u64).unwrap();
761        vmo.write(&data, 0).unwrap();
762        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
763        let contents = read_contents_of_stream(stream).unwrap();
764        assert_eq!(contents, data);
765    }
766
767    #[test]
768    fn read_contents_of_stream_with_empty_stream() {
769        let vmo = zx::Vmo::create(0).unwrap();
770        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
771        let contents = read_contents_of_stream(stream).unwrap();
772        assert!(contents.is_empty());
773    }
774
775    fn serve_file(file: Arc<VmoFile>, flags: fio::Flags) -> fio::FileProxy {
776        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
777        flags.to_object_request(server_end).handle(|object_request| {
778            vfs::file::serve(file, ExecutionScope::new(), &flags, object_request)
779        });
780        proxy
781    }
782
783    #[fasync::run_singlethreaded(test)]
784    async fn read_file_with_on_open_event_with_stream() {
785        let data = b"file-contents".repeat(1000);
786        let vmo_file = read_only(&data);
787        const FLAGS: fio::Flags = fio::PERM_READABLE.union(fio::Flags::FLAG_SEND_REPRESENTATION);
788
789        {
790            // Ensure that the file supports streams.
791            let file = serve_file(vmo_file.clone(), FLAGS);
792            let event = take_on_open_event(&file).await.unwrap();
793            extract_stream_from_on_open_event(event).unwrap().expect("Stream not present");
794        }
795
796        let file = serve_file(vmo_file.clone(), FLAGS);
797        let contents = read_file_with_on_open_event(file).await.unwrap();
798        assert_eq!(contents, data);
799    }
800
801    #[fasync::run_singlethreaded(test)]
802    async fn read_missing_file_in_namespace() {
803        assert_matches!(
804            read_in_namespace("/pkg/data/missing").await,
805            Err(e) if e.is_not_found_error()
806        );
807    }
808}