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    use zx::{self as zx, HandleBased as _};
464
465    const DATA_FILE_CONTENTS: &str = "Hello World!\n";
466
467    // open_in_namespace
468
469    #[fasync::run_singlethreaded(test)]
470    async fn open_in_namespace_opens_real_file() {
471        let exists = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
472        assert_matches!(close(exists).await, Ok(()));
473    }
474
475    #[fasync::run_singlethreaded(test)]
476    async fn open_in_namespace_opens_fake_file_under_of_root_namespace_entry() {
477        let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
478        // The open error is not detected until the proxy is interacted with.
479        assert_matches!(close(notfound).await, Err(_));
480    }
481
482    #[fasync::run_singlethreaded(test)]
483    async fn open_in_namespace_rejects_fake_root_namespace_entry() {
484        assert_matches!(
485            open_in_namespace("/fake", fio::PERM_READABLE),
486            Err(OpenError::Namespace(zx_status::Status::NOT_FOUND))
487        );
488    }
489
490    // write_in_namespace
491
492    #[fasync::run_singlethreaded(test)]
493    async fn write_in_namespace_creates_file() {
494        let tempdir = TempDir::new().unwrap();
495        let path = tempdir.path().join(Path::new("new-file")).to_str().unwrap().to_owned();
496
497        // Write contents.
498        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
499        write_in_namespace(&path, data).await.unwrap();
500
501        // Verify contents.
502        let contents = std::fs::read(&path).unwrap();
503        assert_eq!(&contents, &data);
504    }
505
506    #[fasync::run_singlethreaded(test)]
507    async fn write_in_namespace_overwrites_existing_file() {
508        let tempdir = TempDir::new().unwrap();
509        let path = tempdir.path().join(Path::new("existing-file")).to_str().unwrap().to_owned();
510
511        // Write contents.
512        let original_data = b"\x80\x81"; // Non UTF-8 data: a continuation byte as the first byte.
513        write_in_namespace(&path, original_data).await.unwrap();
514
515        // Over-write contents.
516        let new_data = b"\x82"; // Non UTF-8 data: a continuation byte as the first byte.
517        write_in_namespace(&path, new_data).await.unwrap();
518
519        // Verify contents.
520        let contents = std::fs::read(&path).unwrap();
521        assert_eq!(&contents, &new_data);
522    }
523
524    #[fasync::run_singlethreaded(test)]
525    async fn write_in_namespace_fails_on_invalid_namespace_entry() {
526        assert_matches!(
527            write_in_namespace("/fake", b"").await,
528            Err(WriteNamedError { path, source: WriteError::Open(_) }) if path == "/fake"
529        );
530        let err = write_in_namespace("/fake", b"").await.unwrap_err();
531        assert_eq!(err.path(), "/fake");
532        assert_matches!(err.into_inner(), WriteError::Open(_));
533    }
534
535    // write
536
537    #[fasync::run_singlethreaded(test)]
538    async fn write_writes_to_file() {
539        let tempdir = TempDir::new().unwrap();
540        let dir = directory::open_in_namespace(
541            tempdir.path().to_str().unwrap(),
542            fio::PERM_READABLE | fio::PERM_WRITABLE,
543        )
544        .unwrap();
545
546        // Write contents.
547        let file =
548            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
549                .await
550                .unwrap();
551        let data = b"\x80"; // Non UTF-8 data: a continuation byte as the first byte.
552        write(&file, data).await.unwrap();
553
554        // Verify contents.
555        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
556        assert_eq!(&contents, &data);
557    }
558
559    #[fasync::run_singlethreaded(test)]
560    async fn write_writes_to_file_in_chunks_if_needed() {
561        let tempdir = TempDir::new().unwrap();
562        let dir = directory::open_in_namespace(
563            tempdir.path().to_str().unwrap(),
564            fio::PERM_READABLE | fio::PERM_WRITABLE,
565        )
566        .unwrap();
567
568        // Write contents.
569        let file =
570            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
571                .await
572                .unwrap();
573        let data = "abc".repeat(10000);
574        write(&file, &data).await.unwrap();
575
576        // Verify contents.
577        let contents = std::fs::read_to_string(tempdir.path().join(Path::new("file"))).unwrap();
578        assert_eq!(&contents, &data);
579    }
580
581    #[fasync::run_singlethreaded(test)]
582    async fn write_appends_to_file() {
583        let tempdir = TempDir::new().unwrap();
584        let dir = directory::open_in_namespace(
585            tempdir.path().to_str().unwrap(),
586            fio::PERM_READABLE | fio::PERM_WRITABLE,
587        )
588        .unwrap();
589
590        // Create and write to the file.
591        let file =
592            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
593                .await
594                .unwrap();
595        write(&file, "Hello ").await.unwrap();
596        write(&file, "World!\n").await.unwrap();
597        close(file).await.unwrap();
598
599        // Verify contents.
600        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
601        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
602    }
603
604    #[fasync::run_singlethreaded(test)]
605    async fn write_stream() {
606        let tempdir = TempDir::new().unwrap();
607        let dir = directory::open_in_namespace(
608            tempdir.path().to_str().unwrap(),
609            fio::PERM_READABLE | fio::PERM_WRITABLE,
610        )
611        .unwrap();
612
613        // Create and write to the file.
614        let file =
615            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
616                .await
617                .unwrap();
618        write(&file, "Hello ").await.unwrap();
619        write(&file, "World!\n").await.unwrap();
620        close(file).await.unwrap();
621
622        // Verify contents.
623        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
624        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
625    }
626
627    // read
628
629    #[fasync::run_singlethreaded(test)]
630    async fn read_reads_to_end_of_file() {
631        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
632
633        let contents = read(&file).await.unwrap();
634        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
635    }
636
637    #[fasync::run_singlethreaded(test)]
638    async fn read_reads_from_current_position() {
639        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
640
641        // Advance past the first byte.
642        let _: Vec<u8> = file.read(1).await.unwrap().unwrap();
643
644        // Verify the rest of the file is read.
645        let contents = read(&file).await.unwrap();
646        assert_eq!(&contents[..], "ello World!\n".as_bytes());
647    }
648
649    // read_in_namespace
650
651    #[fasync::run_singlethreaded(test)]
652    async fn read_in_namespace_reads_contents() {
653        let contents = read_in_namespace("/pkg/data/file").await.unwrap();
654        assert_eq!(&contents[..], DATA_FILE_CONTENTS.as_bytes());
655    }
656
657    #[fasync::run_singlethreaded(test)]
658    async fn read_in_namespace_fails_on_invalid_namespace_entry() {
659        assert_matches!(
660            read_in_namespace("/fake").await,
661            Err(ReadNamedError { path, source: ReadError::Open(_) }) if path == "/fake"
662        );
663        let err = read_in_namespace("/fake").await.unwrap_err();
664        assert_eq!(err.path(), "/fake");
665        assert_matches!(err.into_inner(), ReadError::Open(_));
666    }
667
668    // read_to_string
669
670    #[fasync::run_singlethreaded(test)]
671    async fn read_to_string_reads_data_file() {
672        let file = open_in_namespace("/pkg/data/file", fio::PERM_READABLE).unwrap();
673        assert_eq!(read_to_string(&file).await.unwrap(), DATA_FILE_CONTENTS);
674    }
675
676    // read_in_namespace_to_string
677
678    #[fasync::run_singlethreaded(test)]
679    async fn read_in_namespace_to_string_reads_data_file() {
680        assert_eq!(
681            read_in_namespace_to_string("/pkg/data/file").await.unwrap(),
682            DATA_FILE_CONTENTS
683        );
684    }
685
686    // write_fidl
687
688    #[fasync::run_singlethreaded(test)]
689    async fn write_fidl_writes_to_file() {
690        let tempdir = TempDir::new().unwrap();
691        let dir = directory::open_in_namespace(
692            tempdir.path().to_str().unwrap(),
693            fio::PERM_READABLE | fio::PERM_WRITABLE,
694        )
695        .unwrap();
696
697        // Write contents.
698        let file =
699            directory::open_file(&dir, "file", fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE)
700                .await
701                .unwrap();
702
703        let mut data = DataTable1 {
704            num: Some(42),
705            string: Some(DATA_FILE_CONTENTS.to_string()),
706            ..Default::default()
707        };
708
709        // Binary encoded FIDL message, with header and padding.
710        let fidl_bytes = persist(&data).unwrap();
711
712        write_fidl(&file, &mut data).await.unwrap();
713
714        // Verify contents.
715        let contents = std::fs::read(tempdir.path().join(Path::new("file"))).unwrap();
716        assert_eq!(&contents, &fidl_bytes);
717    }
718
719    #[fasync::run_singlethreaded(test)]
720    async fn read_fidl_reads_from_file() {
721        let file = open_in_namespace("/pkg/data/fidl_file", fio::PERM_READABLE).unwrap();
722
723        let contents = read_fidl::<DataTable2>(&file).await.unwrap();
724
725        let data = DataTable2 {
726            num: Some(42),
727            string: Some(DATA_FILE_CONTENTS.to_string()),
728            new_field: None,
729            ..Default::default()
730        };
731        assert_eq!(&contents, &data);
732    }
733
734    #[test]
735    fn extract_stream_from_on_open_event_with_stream() {
736        let vmo = zx::Vmo::create(0).unwrap();
737        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
738        let event = fio::FileEvent::OnOpen_ {
739            s: 0,
740            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
741                stream: Some(stream),
742                event: None,
743            }))),
744        };
745        let stream = extract_stream_from_on_open_event(event)
746            .expect("Not a file")
747            .expect("Stream not present");
748        assert!(!stream.is_invalid_handle());
749    }
750
751    #[test]
752    fn extract_stream_from_on_open_event_without_stream() {
753        let event = fio::FileEvent::OnOpen_ {
754            s: 0,
755            info: Some(Box::new(fio::NodeInfoDeprecated::File(fio::FileObject {
756                stream: None,
757                event: None,
758            }))),
759        };
760        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
761        assert!(stream.is_none());
762    }
763
764    #[test]
765    fn extract_stream_from_on_open_event_with_open_error() {
766        let event = fio::FileEvent::OnOpen_ { s: zx::Status::NOT_FOUND.into_raw(), info: None };
767        let result = extract_stream_from_on_open_event(event);
768        assert_matches!(result, Err(OpenError::OpenError(zx::Status::NOT_FOUND)));
769    }
770
771    #[test]
772    fn extract_stream_from_on_open_event_not_a_file() {
773        let event = fio::FileEvent::OnOpen_ {
774            s: 0,
775            info: Some(Box::new(fio::NodeInfoDeprecated::Service(fio::Service))),
776        };
777        let result = extract_stream_from_on_open_event(event);
778        assert_matches!(
779            result,
780            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Service })
781        );
782    }
783
784    #[test]
785    fn extract_stream_from_on_representation_event_with_stream() {
786        let vmo = zx::Vmo::create(0).unwrap();
787        let stream = zx::Stream::create(zx::StreamOptions::empty(), &vmo, 0).unwrap();
788        let event = fio::FileEvent::OnRepresentation {
789            payload: fio::Representation::File(fio::FileInfo {
790                stream: Some(stream),
791                ..Default::default()
792            }),
793        };
794        let stream = extract_stream_from_on_open_event(event)
795            .expect("Not a file")
796            .expect("Stream not present");
797        assert!(!stream.is_invalid_handle());
798    }
799
800    #[test]
801    fn extract_stream_from_on_representation_event_without_stream() {
802        let event = fio::FileEvent::OnRepresentation {
803            payload: fio::Representation::File(fio::FileInfo::default()),
804        };
805        let stream = extract_stream_from_on_open_event(event).expect("Not a file");
806        assert!(stream.is_none());
807    }
808
809    #[test]
810    fn extract_stream_from_on_representation_event_not_a_file() {
811        let event = fio::FileEvent::OnRepresentation {
812            payload: fio::Representation::Directory(Default::default()),
813        };
814        let result = extract_stream_from_on_open_event(event);
815        assert_matches!(
816            result,
817            Err(OpenError::UnexpectedNodeKind { expected: Kind::File, actual: Kind::Directory })
818        );
819    }
820
821    #[test]
822    fn read_contents_of_stream_with_contents() {
823        let data = b"file-contents".repeat(1000);
824        let vmo = zx::Vmo::create(data.len() as u64).unwrap();
825        vmo.write(&data, 0).unwrap();
826        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
827        let contents = read_contents_of_stream(stream).unwrap();
828        assert_eq!(contents, data);
829    }
830
831    #[test]
832    fn read_contents_of_stream_with_empty_stream() {
833        let vmo = zx::Vmo::create(0).unwrap();
834        let stream = zx::Stream::create(zx::StreamOptions::MODE_READ, &vmo, 0).unwrap();
835        let contents = read_contents_of_stream(stream).unwrap();
836        assert!(contents.is_empty());
837    }
838
839    fn serve_file(file: Arc<VmoFile>, flags: fio::Flags) -> fio::FileProxy {
840        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
841        flags.to_object_request(server_end).handle(|object_request| {
842            vfs::file::serve(file, ExecutionScope::new(), &flags, object_request)
843        });
844        proxy
845    }
846
847    #[fasync::run_singlethreaded(test)]
848    async fn read_file_with_on_open_event_with_stream() {
849        let data = b"file-contents".repeat(1000);
850        let vmo_file = read_only(&data);
851        const FLAGS: fio::Flags = fio::PERM_READABLE.union(fio::Flags::FLAG_SEND_REPRESENTATION);
852
853        {
854            // Ensure that the file supports streams.
855            let file = serve_file(vmo_file.clone(), FLAGS);
856            let event = take_on_open_event(&file).await.unwrap();
857            extract_stream_from_on_open_event(event).unwrap().expect("Stream not present");
858        }
859
860        let file = serve_file(vmo_file.clone(), FLAGS);
861        let contents = read_file_with_on_open_event(file).await.unwrap();
862        assert_eq!(contents, data);
863    }
864
865    #[fasync::run_singlethreaded(test)]
866    async fn read_missing_file_in_namespace() {
867        assert_matches!(
868            read_in_namespace("/pkg/data/missing").await,
869            Err(e) if e.is_not_found_error()
870        );
871    }
872
873    #[test]
874    fn write_to_stream_with_contents() {
875        let data = b"file-contents".repeat(1000);
876        let vmo = zx::Vmo::create(data.len() as u64).unwrap();
877        let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
878        write_to_stream(stream, &data).unwrap();
879
880        let mut read_back = vec![0; data.len()];
881        vmo.read(&mut read_back, 0).unwrap();
882        assert_eq!(read_back, data);
883    }
884
885    #[test]
886    fn write_to_stream_empty() {
887        let data = b"";
888        let vmo = zx::Vmo::create(0).unwrap();
889        let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, &vmo, 0).unwrap();
890        write_to_stream(stream, data).unwrap();
891        assert_eq!(vmo.get_size().unwrap(), 0);
892    }
893}