Skip to main content

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::{Persistable, persist, unpersist};
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::{Kind, take_on_open_event};
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    /// Writes `contents` into `stream`.
249    #[cfg(fuchsia_api_level_at_least = "HEAD")]
250    pub(super) fn write_to_stream(stream: zx::Stream, contents: &[u8]) -> Result<(), WriteError> {
251        let mut offset: usize = 0;
252        while offset < contents.len() {
253            let actual = stream
254                .write(zx::StreamWriteOptions::empty(), &contents[offset..])
255                .map_err(WriteError::WriteError)?;
256            // Per the syscall docs, 0 will never be returned (an error is returned instead), but
257            // make sure we don't loop forever.
258            debug_assert!(actual > 0);
259            offset += actual;
260        }
261        Ok(())
262    }
263
264    /// Reads the contents of `file` into a Vec. `file` must have been opened with
265    /// `SEND_REPRESENTATION` and the event must not have been read yet.
266    pub(crate) async fn read_file_with_on_open_event(
267        file: fio::FileProxy,
268    ) -> Result<Vec<u8>, ReadError> {
269        let event = take_on_open_event(&file).await.map_err(ReadError::Open)?;
270        let stream = extract_stream_from_on_open_event(event).map_err(ReadError::Open)?;
271
272        if let Some(stream) = stream {
273            read_contents_of_stream(stream)
274        } else {
275            // Fall back to FIDL reads if the file doesn't support streams.
276            read(&file).await
277        }
278    }
279
280    /// Writes the contents of `buf` into `file`. `file` must have been opened with
281    /// `SEND_REPRESENTATION` and the event must not have been read yet.
282    #[cfg(fuchsia_api_level_at_least = "HEAD")]
283    pub(crate) async fn write_file_with_on_open_event(
284        file: &fio::FileProxy,
285        contents: &[u8],
286    ) -> Result<(), WriteError> {
287        let event = take_on_open_event(file).await.map_err(WriteError::Open)?;
288        let stream = extract_stream_from_on_open_event(event).map_err(WriteError::Open)?;
289
290        if let Some(stream) = stream {
291            write_to_stream(stream, contents)
292        } else {
293            // Fall back to FIDL writes if the file doesn't support streams.
294            write(file, contents).await
295        }
296    }
297}
298
299/// An error encountered while reading a file
300#[derive(Debug, Error)]
301#[allow(missing_docs)]
302pub enum ReadError {
303    #[error("while opening the file: {0:?}")]
304    Open(#[from] OpenError),
305
306    #[error("read call failed: {0:?}")]
307    Fidl(#[from] fidl::Error),
308
309    #[error("read failed with status: {0}")]
310    ReadError(#[source] zx_status::Status),
311
312    #[error("file was not a utf-8 encoded string: {0}")]
313    InvalidUtf8(#[from] std::string::FromUtf8Error),
314}
315
316impl ReadError {
317    /// Returns true if the read failed because the file was no found.
318    pub fn is_not_found_error(&self) -> bool {
319        matches!(self, ReadError::Open(e) if e.is_not_found_error())
320    }
321}
322
323/// An error encountered while writing a file
324#[derive(Debug, Error)]
325#[allow(missing_docs)]
326pub enum WriteError {
327    #[error("while opening the file: {0}")]
328    Open(#[from] OpenError),
329
330    #[error("while linking the file: {0}")]
331    Link(#[from] zx_status::Status),
332
333    #[error("while renaming the file: {0}")]
334    #[cfg(fuchsia_api_level_at_least = "HEAD")]
335    Rename(#[from] crate::node::RenameError),
336
337    #[error("write call failed: {0}")]
338    Fidl(#[from] fidl::Error),
339
340    #[error("write failed with status: {0}")]
341    WriteError(#[source] zx_status::Status),
342
343    #[error("file endpoint reported more bytes written than were provided")]
344    Overwrite,
345}
346
347/// Gracefully closes the file proxy from the remote end.
348pub async fn close(file: fio::FileProxy) -> Result<(), CloseError> {
349    let result = file.close().await.map_err(CloseError::SendCloseRequest)?;
350    result.map_err(|s| CloseError::CloseError(zx_status::Status::from_raw(s)))
351}
352
353/// Writes the given data into the given file.
354pub async fn write<D>(file: &fio::FileProxy, data: D) -> Result<(), WriteError>
355where
356    D: AsRef<[u8]>,
357{
358    let mut data = data.as_ref();
359
360    while !data.is_empty() {
361        let bytes_written = file
362            .write(&data[..std::cmp::min(fio::MAX_BUF as usize, data.len())])
363            .await?
364            .map_err(|s| WriteError::WriteError(zx_status::Status::from_raw(s)))?;
365
366        if bytes_written > data.len() as u64 {
367            return Err(WriteError::Overwrite);
368        }
369
370        data = &data[bytes_written as usize..];
371    }
372    Ok(())
373}
374
375/// Write the given FIDL message in a binary form into a file open for writing.
376pub async fn write_fidl<T: Persistable>(
377    file: &fio::FileProxy,
378    data: &mut T,
379) -> Result<(), WriteError> {
380    write(file, persist(data)?).await?;
381    Ok(())
382}
383
384/// Reads all data from the given file's current offset to the end of the file.
385pub async fn read(file: &fio::FileProxy) -> Result<Vec<u8>, ReadError> {
386    let mut out = Vec::new();
387
388    loop {
389        let mut bytes = file
390            .read(fio::MAX_BUF)
391            .await?
392            .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
393        if bytes.is_empty() {
394            break;
395        }
396        out.append(&mut bytes);
397    }
398    Ok(out)
399}
400
401/// Attempts to read a number of bytes from the given file's current offset.
402/// This function may return less data than expected.
403pub async fn read_num_bytes(file: &fio::FileProxy, num_bytes: u64) -> Result<Vec<u8>, ReadError> {
404    let mut data = vec![];
405
406    // Read in chunks of |MAX_BUF| bytes.
407    // This is the maximum buffer size supported over FIDL.
408    let mut bytes_left = num_bytes;
409    while bytes_left > 0 {
410        let bytes_to_read = std::cmp::min(bytes_left, fio::MAX_BUF);
411        let mut bytes = file
412            .read(bytes_to_read)
413            .await?
414            .map_err(|s| ReadError::ReadError(zx_status::Status::from_raw(s)))?;
415
416        if bytes.is_empty() {
417            break;
418        }
419
420        bytes_left -= bytes.len() as u64;
421        data.append(&mut bytes);
422    }
423
424    // Remove excess data read in, if any.
425    let num_bytes = num_bytes as usize;
426    if data.len() > num_bytes {
427        data.drain(num_bytes..data.len());
428    }
429
430    Ok(data)
431}
432
433/// Reads a utf-8 encoded string from the given file's current offset to the end of the file.
434pub async fn read_to_string(file: &fio::FileProxy) -> Result<String, ReadError> {
435    let bytes = read(file).await?;
436    let string = String::from_utf8(bytes)?;
437    Ok(string)
438}
439
440/// Read the given FIDL message from binary form from a file open for reading.
441/// FIDL structure should be provided at a read time.
442/// Incompatible data is populated as per FIDL ABI compatibility guide:
443/// https://fuchsia.dev/fuchsia-src/development/languages/fidl/guides/abi-compat
444pub async fn read_fidl<T: Persistable>(file: &fio::FileProxy) -> Result<T, ReadError> {
445    let bytes = read(file).await?;
446    Ok(unpersist(&bytes)?)
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use crate::directory;
453    use crate::node::{Kind, take_on_open_event};
454    use assert_matches::assert_matches;
455    use fidl_fidl_test_schema::{DataTable1, DataTable2};
456    use fuchsia_async as fasync;
457    use std::path::Path;
458    use std::sync::Arc;
459    use tempfile::TempDir;
460    use vfs::ToObjectRequest;
461    use vfs::execution_scope::ExecutionScope;
462    use vfs::file::vmo::{VmoFile, read_only};
463
464    const DATA_FILE_CONTENTS: &str = "Hello World!\n";
465
466    // open_in_namespace
467
468    #[fasync::run_singlethreaded(test)]
469    async fn open_in_namespace_opens_real_file() {
470        let exists = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
471        assert_matches!(close(exists).await, Ok(()));
472    }
473
474    #[fasync::run_singlethreaded(test)]
475    async fn open_in_namespace_opens_fake_file_under_of_root_namespace_entry() {
476        let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
477        // The open error is not detected until the proxy is interacted with.
478        assert_matches!(close(notfound).await, Err(_));
479    }
480
481    #[fasync::run_singlethreaded(test)]
482    async fn open_in_namespace_rejects_fake_root_namespace_entry() {
483        assert_matches!(
484            open_in_namespace("/fake", fio::PERM_READABLE),
485            Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
486        );
487    }
488
489    // write_in_namespace
490
491    #[fasync::run_singlethreaded(test)]
492    async fn write_in_namespace_creates_file() {
493        let tempdir = TempDir::new().unwrap();
494        let path = tempdir.path().join(Path::new("new-file")).to_str().unwrap().to_owned();
495
496        // Write contents.
497        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
498        write_in_namespace(&path, data).await.unwrap();
499
500        // Verify contents.
501        let contents = std::fs::read(&path).unwrap();
502        assert_eq!(&contents, &data);
503    }
504
505    #[fasync::run_singlethreaded(test)]
506    async fn write_in_namespace_overwrites_existing_file() {
507        let tempdir = TempDir::new().unwrap();
508        let path = tempdir.path().join(Path::new("existing-file")).to_str().unwrap().to_owned();
509
510        // Write contents.
511        let original_data = b"\x80\x81"; // Non UTF-8 data: a continuation byte as the first byte.
512        write_in_namespace(&path, original_data).await.unwrap();
513
514        // Over-write contents.
515        let new_data = b"\x82"; // Non UTF-8 data: a continuation byte as the first byte.
516        write_in_namespace(&path, new_data).await.unwrap();
517
518        // Verify contents.
519        let contents = std::fs::read(&path).unwrap();
520        assert_eq!(&contents, &new_data);
521    }
522
523    #[fasync::run_singlethreaded(test)]
524    async fn write_in_namespace_fails_on_invalid_namespace_entry() {
525        assert_matches!(
526            write_in_namespace("/fake", b"").await,
527            Err(WriteNamedError { path, source: WriteError::Open(_) }) if path == "/fake"
528        );
529        let err = write_in_namespace("/fake", b"").await.unwrap_err();
530        assert_eq!(err.path(), "/fake");
531        assert_matches!(err.into_inner(), WriteError::Open(_));
532    }
533
534    // write
535
536    #[fasync::run_singlethreaded(test)]
537    async fn write_writes_to_file() {
538        let tempdir = TempDir::new().unwrap();
539        let dir = directory::open_in_namespace(
540            tempdir.path().to_str().unwrap(),
541            fio::PERM_READABLE | fio::PERM_WRITABLE,
542        )
543        .unwrap();
544
545        // Write contents.
546        let file =
547            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
548                .await
549                .unwrap();
550        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
551        write(&file, data).await.unwrap();
552
553        // Verify contents.
554        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
555        assert_eq!(&contents, &data);
556    }
557
558    #[fasync::run_singlethreaded(test)]
559    async fn write_writes_to_file_in_chunks_if_needed() {
560        let tempdir = TempDir::new().unwrap();
561        let dir = directory::open_in_namespace(
562            tempdir.path().to_str().unwrap(),
563            fio::PERM_READABLE | fio::PERM_WRITABLE,
564        )
565        .unwrap();
566
567        // Write contents.
568        let file =
569            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
570                .await
571                .unwrap();
572        let data = "abc".repeat(10000);
573        write(&file, &data).await.unwrap();
574
575        // Verify contents.
576        let contents = std::fs::read_to_string(tempdir.path().join(Path::new("file"))).unwrap();
577        assert_eq!(&contents, &data);
578    }
579
580    #[fasync::run_singlethreaded(test)]
581    async fn write_appends_to_file() {
582        let tempdir = TempDir::new().unwrap();
583        let dir = directory::open_in_namespace(
584            tempdir.path().to_str().unwrap(),
585            fio::PERM_READABLE | fio::PERM_WRITABLE,
586        )
587        .unwrap();
588
589        // Create and write to the file.
590        let file =
591            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
592                .await
593                .unwrap();
594        write(&file, "Hello ").await.unwrap();
595        write(&file, "World!\n").await.unwrap();
596        close(file).await.unwrap();
597
598        // Verify contents.
599        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
600        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
601    }
602
603    #[fasync::run_singlethreaded(test)]
604    async fn write_stream() {
605        let tempdir = TempDir::new().unwrap();
606        let dir = directory::open_in_namespace(
607            tempdir.path().to_str().unwrap(),
608            fio::PERM_READABLE | fio::PERM_WRITABLE,
609        )
610        .unwrap();
611
612        // Create and write to the file.
613        let file =
614            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
615                .await
616                .unwrap();
617        write(&file, "Hello ").await.unwrap();
618        write(&file, "World!\n").await.unwrap();
619        close(file).await.unwrap();
620
621        // Verify contents.
622        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
623        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
624    }
625
626    // read
627
628    #[fasync::run_singlethreaded(test)]
629    async fn read_reads_to_end_of_file() {
630        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
631
632        let contents = read(&file).await.unwrap();
633        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
634    }
635
636    #[fasync::run_singlethreaded(test)]
637    async fn read_reads_from_current_position() {
638        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
639
640        // Advance past the first byte.
641        let _: Vec<u8> = file.read(1).await.unwrap().unwrap();
642
643        // Verify the rest of the file is read.
644        let contents = read(&file).await.unwrap();
645        assert_eq!(&contents[..], "ello World!\n".as_bytes());
646    }
647
648    // read_in_namespace
649
650    #[fasync::run_singlethreaded(test)]
651    async fn read_in_namespace_reads_contents() {
652        let contents = read_in_namespace("/pkg/data/file").await.unwrap();
653        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
654    }
655
656    #[fasync::run_singlethreaded(test)]
657    async fn read_in_namespace_fails_on_invalid_namespace_entry() {
658        assert_matches!(
659            read_in_namespace("/fake").await,
660            Err(ReadNamedError { path, source: ReadError::Open(_) }) if path == "/fake"
661        );
662        let err = read_in_namespace("/fake").await.unwrap_err();
663        assert_eq!(err.path(), "/fake");
664        assert_matches!(err.into_inner(), ReadError::Open(_));
665    }
666
667    // read_to_string
668
669    #[fasync::run_singlethreaded(test)]
670    async fn read_to_string_reads_data_file() {
671        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
672        assert_eq!(read_to_string(&file).await.unwrap(), DATA_FILE_CONTENTS);
673    }
674
675    // read_in_namespace_to_string
676
677    #[fasync::run_singlethreaded(test)]
678    async fn read_in_namespace_to_string_reads_data_file() {
679        assert_eq!(
680            read_in_namespace_to_string("/pkg/data/file").await.unwrap(),
681            DATA_FILE_CONTENTS
682        );
683    }
684
685    // write_fidl
686
687    #[fasync::run_singlethreaded(test)]
688    async fn write_fidl_writes_to_file() {
689        let tempdir = TempDir::new().unwrap();
690        let dir = directory::open_in_namespace(
691            tempdir.path().to_str().unwrap(),
692            fio::PERM_READABLE | fio::PERM_WRITABLE,
693        )
694        .unwrap();
695
696        // Write contents.
697        let file =
698            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
699                .await
700                .unwrap();
701
702        let mut data = DataTable1 {
703            num: Some(42),
704            string: Some(DATA_FILE_CONTENTS.to_string()),
705            ..Default::default()
706        };
707
708        // Binary encoded FIDL message, with header and padding.
709        let fidl_bytes = persist(&data).unwrap();
710
711        write_fidl(&file, &mut data).await.unwrap();
712
713        // Verify contents.
714        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
715        assert_eq!(&contents, &fidl_bytes);
716    }
717
718    #[fasync::run_singlethreaded(test)]
719    async fn read_fidl_reads_from_file() {
720        let file = open_in_namespace("/pkg/data/fidl_file", fio::PERM_READABLE).unwrap();
721
722        let contents = read_fidl::<DataTable2>(&file).await.unwrap();
723
724        let data = DataTable2 {
725            num: Some(42),
726            string: Some(DATA_FILE_CONTENTS.to_string()),
727            new_field: None,
728            ..Default::default()
729        };
730        assert_eq!(&contents, &data);
731    }
732
733    #[test]
734    fn extract_stream_from_on_open_event_with_stream() {
735        let vmo = zx::Vmo::create(0).unwrap();
736        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
737        let event = fio::FileEvent::OnOpen_ {
738            s: 0,
739            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
740                stream: Some(stream),
741                event: None,
742            }))),
743        };
744        let stream = extract_stream_from_on_open_event(event)
745            .expect("Not a file")
746            .expect("Stream not present");
747        assert!(!stream.is_invalid());
748    }
749
750    #[test]
751    fn extract_stream_from_on_open_event_without_stream() {
752        let event = fio::FileEvent::OnOpen_ {
753            s: 0,
754            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
755                stream: None,
756                event: None,
757            }))),
758        };
759        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
760        assert!(stream.is_none());
761    }
762
763    #[test]
764    fn extract_stream_from_on_open_event_with_open_error() {
765        let event = fio::FileEvent::OnOpen_ { s: zx::Status::NOT_FOUND.into_raw(), info: None };
766        let result = extract_stream_from_on_open_event(event);
767        assert_matches!(result, Err(OpenError::OpenError(zx::Status::NOT_FOUND)));
768    }
769
770    #[test]
771    fn extract_stream_from_on_open_event_not_a_file() {
772        let event = fio::FileEvent::OnOpen_ {
773            s: 0,
774            info: Some(Box::new(fio::NodeInfoDeprecated::Service(fio::Service))),
775        };
776        let result = extract_stream_from_on_open_event(event);
777        assert_matches!(
778            result,
779            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Service })
780        );
781    }
782
783    #[test]
784    fn extract_stream_from_on_representation_event_with_stream() {
785        let vmo = zx::Vmo::create(0).unwrap();
786        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
787        let event = fio::FileEvent::OnRepresentation {
788            payload: fio::Representation::File(fio::FileInfo {
789                stream: Some(stream),
790                ..Default::default()
791            }),
792        };
793        let stream = extract_stream_from_on_open_event(event)
794            .expect("Not a file")
795            .expect("Stream not present");
796        assert!(!stream.is_invalid());
797    }
798
799    #[test]
800    fn extract_stream_from_on_representation_event_without_stream() {
801        let event = fio::FileEvent::OnRepresentation {
802            payload: fio::Representation::File(fio::FileInfo::default()),
803        };
804        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
805        assert!(stream.is_none());
806    }
807
808    #[test]
809    fn extract_stream_from_on_representation_event_not_a_file() {
810        let event = fio::FileEvent::OnRepresentation {
811            payload: fio::Representation::Directory(Default::default()),
812        };
813        let result = extract_stream_from_on_open_event(event);
814        assert_matches!(
815            result,
816            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Directory })
817        );
818    }
819
820    #[test]
821    fn read_contents_of_stream_with_contents() {
822        let data = b"file-contents".repeat(1000);
823        let vmo = zx::Vmo::create(data.len() as u64).unwrap();
824        vmo.write(&data, 0).unwrap();
825        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
826        let contents = read_contents_of_stream(stream).unwrap();
827        assert_eq!(contents, data);
828    }
829
830    #[test]
831    fn read_contents_of_stream_with_empty_stream() {
832        let vmo = zx::Vmo::create(0).unwrap();
833        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
834        let contents = read_contents_of_stream(stream).unwrap();
835        assert!(contents.is_empty());
836    }
837
838    fn serve_file(file: Arc<VmoFile>, flags: fio::Flags) -> fio::FileProxy {
839        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
840        flags.to_object_request(server_end).handle(|object_request| {
841            vfs::file::serve(file, ExecutionScope::new(), &flags, object_request)
842        });
843        proxy
844    }
845
846    #[fasync::run_singlethreaded(test)]
847    async fn read_file_with_on_open_event_with_stream() {
848        let data = b"file-contents".repeat(1000);
849        let vmo_file = read_only(&data);
850        const FLAGS: fio::Flags = fio::PERM_READABLE.union(fio::Flags::FLAG_SEND_REPRESENTATION);
851
852        {
853            // Ensure that the file supports streams.
854            let file = serve_file(vmo_file.clone(), FLAGS);
855            let event = take_on_open_event(&file).await.unwrap();
856            extract_stream_from_on_open_event(event).unwrap().expect("Stream not present");
857        }
858
859        let file = serve_file(vmo_file.clone(), FLAGS);
860        let contents = read_file_with_on_open_event(file).await.unwrap();
861        assert_eq!(contents, data);
862    }
863
864    #[fasync::run_singlethreaded(test)]
865    async fn read_missing_file_in_namespace() {
866        assert_matches!(
867            read_in_namespace("/pkg/data/missing").await,
868            Err(e) if e.is_not_found_error()
869        );
870    }
871
872    #[test]
873    fn write_to_stream_with_contents() {
874        let data = b"file-contents".repeat(1000);
875        let vmo = zx::Vmo::create(data.len() as u64).unwrap();
876        let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
877        write_to_stream(stream, &data).unwrap();
878
879        let mut read_back = vec![0; data.len()];
880        vmo.read(&mut read_back, 0).unwrap();
881        assert_eq!(read_back, data);
882    }
883
884    #[test]
885    fn write_to_stream_empty() {
886        let data = b"";
887        let vmo = zx::Vmo::create(0).unwrap();
888        let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
889        write_to_stream(stream, data).unwrap();
890        assert_eq!(vmo.get_size().unwrap(), 0);
891    }
892}