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