Skip to main content

fuchsia_fs/
directory.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 directories.
6
7use crate::PERM_READABLE;
8use crate::node::{self, CloneError, CloseError, OpenError, RenameError};
9use flex_fuchsia_io as fio;
10use fuchsia_async::{DurationExt, MonotonicDuration, TimeoutExt};
11use futures::future::BoxFuture;
12use futures::stream::{self, BoxStream, StreamExt};
13use std::collections::VecDeque;
14use std::str::Utf8Error;
15use thiserror::Error;
16use zerocopy::{FromBytes, Immutable, KnownLayout, Ref, Unaligned};
17
18use flex_client::ProxyHasDomain;
19use flex_client::fidl::{ClientEnd, ProtocolMarker, ServerEnd};
20
21mod watcher;
22pub use watcher::{WatchEvent, WatchMessage, Watcher, WatcherCreateError, WatcherStreamError};
23
24#[cfg(target_os = "fuchsia")]
25#[cfg(not(feature = "fdomain"))]
26pub use fuchsia::*;
27
28#[cfg(not(target_os = "fuchsia"))]
29pub use host::*;
30
31#[cfg(target_os = "fuchsia")]
32#[cfg(not(feature = "fdomain"))]
33mod fuchsia {
34    use super::*;
35    use crate::file::ReadError;
36
37    /// Opens the given `path` from the current namespace as a [`DirectoryProxy`].
38    ///
39    /// To connect to a filesystem node which doesn't implement fuchsia.io.Directory, use the
40    /// functions in [`fuchsia_component::client`] instead.
41    ///
42    /// If the namespace path doesn't exist, or we fail to make the channel pair, this returns an
43    /// error. However, if incorrect flags are sent, or if the rest of the path sent to the
44    /// filesystem server doesn't exist, this will still return success. Instead, the returned
45    /// DirectoryProxy channel pair will be closed with an epitaph.
46    pub fn open_in_namespace(
47        path: &str,
48        flags: fio::Flags,
49    ) -> Result<fio::DirectoryProxy, OpenError> {
50        let (node, request) = fidl::endpoints::create_proxy();
51        open_channel_in_namespace(path, flags, request)?;
52        Ok(node)
53    }
54
55    /// Asynchronously opens the given [`path`] in the current namespace, serving the connection
56    /// over [`request`]. Once the channel is connected, any calls made prior are serviced.
57    ///
58    /// To connect to a filesystem node which doesn't implement fuchsia.io.Directory, use the
59    /// functions in [`fuchsia_component::client`] instead.
60    ///
61    /// If the namespace path doesn't exist, this returns an error. However, if incorrect flags are
62    /// sent, or if the rest of the path sent to the filesystem server doesn't exist, this will
63    /// still return success. Instead, the [`request`] channel will be closed with an epitaph.
64    pub fn open_channel_in_namespace(
65        path: &str,
66        flags: fio::Flags,
67        request: fidl::endpoints::ServerEnd<fio::DirectoryMarker>,
68    ) -> Result<(), OpenError> {
69        let mut flags = flags | fio::Flags::PROTOCOL_DIRECTORY;
70        if flags.intersection(fio::MASK_KNOWN_PERMISSIONS).is_empty() {
71            flags |= PERM_READABLE;
72        }
73        let namespace = fdio::Namespace::installed().map_err(OpenError::Namespace)?;
74        namespace.open(path, flags, request.into_channel()).map_err(OpenError::Namespace)
75    }
76
77    /// Opens `path` from the `parent` directory as a file and reads the file contents into a Vec.
78    pub async fn read_file(parent: &fio::DirectoryProxy, path: &str) -> Result<Vec<u8>, ReadError> {
79        let flags = fio::Flags::FLAG_SEND_REPRESENTATION | PERM_READABLE;
80        let file = open_file_async(parent, path, flags).map_err(ReadError::Open)?;
81        crate::file::read_file_with_on_open_event(file).await
82    }
83
84    /// Atomically creates `path` with `contents`.
85    ///
86    /// This is done by opening an unnamed temporary file, writing the contents into it, and linking
87    /// to `$(path).__tmp`, and then renaming the temporary file to `path`.
88    ///
89    /// Note that power failure after the temporary file was created, but before it was renamed,
90    /// will result in the temporary file existing in the filesystem.  This function will always try
91    /// to remove that file first, but if this function is never called, the temp file will never be
92    /// removed.
93    ///
94    /// TODO(https://fxbug.dev/444270977): It would be preferable if we could avoid creating the
95    /// named temporary file, but a limitation in Node/LinkInto prevents this.  Once that limitation
96    /// is resolved, directly LinkInto over the target file, avoiding the need to clean up on poewr
97    /// failure.
98    ///
99    /// Note that not all filesystems support this feature.
100    ///
101    /// Available at HEAD since FLAG_CREATE_AS_UNNAMED_TEMPORARY is available at HEAD
102    #[cfg(fuchsia_api_level_at_least = "HEAD")]
103    pub async fn atomic_write_file(
104        parent: &fio::DirectoryProxy,
105        path: &str,
106        contents: &[u8],
107    ) -> Result<(), crate::file::WriteError> {
108        use crate::file::WriteError;
109        let (dir, filename) = split_path(parent, path).await?;
110        let dir = dir.as_ref().unwrap_or(parent);
111        let tmp_filename = format!("{filename}.__tmp");
112        // Clean up `tmp_filename` in case it still existed from a previous attempt.  Safe to ignore
113        // errors.
114        let _ = dir.unlink(&tmp_filename, &fio::UnlinkOptions::default()).await?;
115        {
116            let flags = fio::Flags::FLAG_SEND_REPRESENTATION
117                | fio::Flags::PROTOCOL_FILE
118                | fio::PERM_READABLE
119                | fio::PERM_WRITABLE
120                | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY;
121            let tmp = open_file_async(dir, ".", flags).map_err(WriteError::Open)?;
122            crate::file::write_file_with_on_open_event(&tmp, contents).await?;
123            tmp.sync()
124                .await
125                .map_err(WriteError::Fidl)?
126                .map_err(|s| WriteError::WriteError(zx::Status::err_from_raw(s)))?;
127            let (status, token) = dir.deprecated_get_token().await.map_err(WriteError::Fidl)?;
128            zx::ok(status).map_err(WriteError::Link)?;
129            let token = zx::Event::from(token.unwrap());
130            tmp.link_into(token, &tmp_filename)
131                .await
132                .map_err(WriteError::Fidl)?
133                .map_err(|s| WriteError::Link(zx::Status::err_from_raw(s)))?;
134        }
135        rename(dir, &tmp_filename, filename).await.map_err(|err| WriteError::Rename(err))
136    }
137}
138
139#[cfg(not(target_os = "fuchsia"))]
140mod host {
141    use super::*;
142    use crate::file::ReadError;
143
144    /// Opens `path` from the `parent` directory as a file and reads the file contents into a Vec.
145    pub async fn read_file(parent: &fio::DirectoryProxy, path: &str) -> Result<Vec<u8>, ReadError> {
146        let file = open_file_async(parent, path, PERM_READABLE)?;
147        crate::file::read(&file).await
148    }
149}
150
151/// Error returned by readdir_recursive.
152#[derive(Debug, Clone, Error)]
153pub enum RecursiveEnumerateError {
154    #[error("fidl error during {}: {:?}", _0, _1)]
155    Fidl(&'static str, fidl::Error),
156
157    #[error("Failed to read directory {}: {:?}", name, err)]
158    ReadDir { name: String, err: EnumerateError },
159
160    #[error("Failed to open directory {}: {:?}", name, err)]
161    Open { name: String, err: OpenError },
162
163    #[error("timeout")]
164    Timeout,
165}
166
167/// Error returned by readdir.
168#[derive(Debug, Clone, Error)]
169pub enum EnumerateError {
170    #[error("a directory entry could not be decoded: {:?}", _0)]
171    DecodeDirent(DecodeDirentError),
172
173    #[error("fidl error during {}: {:?}", _0, _1)]
174    Fidl(&'static str, fidl::Error),
175
176    #[error("`read_dirents` failed with status {:?}", _0)]
177    ReadDirents(zx_status::Status),
178
179    #[error("timeout")]
180    Timeout,
181
182    #[error("`rewind` failed with status {:?}", _0)]
183    Rewind(zx_status::Status),
184
185    #[error("`unlink` failed with status {:?}", _0)]
186    Unlink(zx_status::Status),
187}
188
189/// An error encountered while decoding a single directory entry.
190#[derive(Debug, Clone, PartialEq, Eq, Error)]
191pub enum DecodeDirentError {
192    #[error("an entry extended past the end of the buffer")]
193    BufferOverrun,
194
195    #[error("name is not valid utf-8: {}", _0)]
196    InvalidUtf8(Utf8Error),
197}
198
199/// Opens the given `path` from the given `parent` directory as a [`DirectoryProxy`]. If open fails,
200/// the returned `DirectoryProxy` will be closed with an epitaph.
201pub fn open_directory_async(
202    parent: &fio::DirectoryProxy,
203    path: &str,
204    flags: fio::Flags,
205) -> Result<fio::DirectoryProxy, OpenError> {
206    let (dir, server_end) = parent.domain().create_proxy::<fio::DirectoryMarker>();
207
208    let mut flags = flags | fio::Flags::PROTOCOL_DIRECTORY;
209    if flags.intersection(fio::MASK_KNOWN_PERMISSIONS).is_empty() {
210        flags |= PERM_READABLE;
211    }
212
213    #[cfg(fuchsia_api_level_at_least = "27")]
214    parent
215        .open(path, flags, &fio::Options::default(), server_end.into_channel())
216        .map_err(OpenError::SendOpenRequest)?;
217    #[cfg(not(fuchsia_api_level_at_least = "27"))]
218    parent
219        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
220        .map_err(OpenError::SendOpenRequest)?;
221
222    Ok(dir)
223}
224
225/// Opens the given `path` from given `parent` directory as a [`DirectoryProxy`], verifying that
226/// the target implements the fuchsia.io.Directory protocol.
227pub async fn open_directory(
228    parent: &fio::DirectoryProxy,
229    path: &str,
230    flags: fio::Flags,
231) -> Result<fio::DirectoryProxy, OpenError> {
232    let (dir, server_end) = parent.domain().create_proxy::<fio::DirectoryMarker>();
233
234    let mut flags = flags | fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_SEND_REPRESENTATION;
235    if flags.intersection(fio::MASK_KNOWN_PERMISSIONS).is_empty() {
236        flags |= PERM_READABLE;
237    }
238
239    #[cfg(fuchsia_api_level_at_least = "27")]
240    parent
241        .open(path, flags, &fio::Options::default(), server_end.into_channel())
242        .map_err(OpenError::SendOpenRequest)?;
243    #[cfg(not(fuchsia_api_level_at_least = "27"))]
244    parent
245        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
246        .map_err(OpenError::SendOpenRequest)?;
247
248    // wait for the directory to open and report success.
249    node::verify_directory_describe_event(dir).await
250}
251
252/// Creates a directory named `path` within the `parent` directory if it doesn't exist.
253pub async fn create_directory(
254    parent: &fio::DirectoryProxy,
255    path: &str,
256    flags: fio::Flags,
257) -> Result<fio::DirectoryProxy, OpenError> {
258    let (dir, server_end) = parent.domain().create_proxy::<fio::DirectoryMarker>();
259
260    let flags = flags
261        | fio::Flags::FLAG_MAYBE_CREATE
262        | fio::Flags::PROTOCOL_DIRECTORY
263        | fio::Flags::FLAG_SEND_REPRESENTATION;
264
265    #[cfg(fuchsia_api_level_at_least = "27")]
266    parent
267        .open(path, flags, &fio::Options::default(), server_end.into_channel())
268        .map_err(OpenError::SendOpenRequest)?;
269    #[cfg(not(fuchsia_api_level_at_least = "27"))]
270    parent
271        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
272        .map_err(OpenError::SendOpenRequest)?;
273
274    // wait for the directory to open and report success.
275    node::verify_directory_describe_event(dir).await
276}
277
278/// Creates a directory named `path` (including all segments leading up to the terminal segment)
279/// within the `parent` directory.  Returns a connection to the terminal directory.
280pub async fn create_directory_recursive(
281    parent: &fio::DirectoryProxy,
282    path: &str,
283    flags: fio::Flags,
284) -> Result<fio::DirectoryProxy, OpenError> {
285    let components = path.split('/');
286    let mut dir = None;
287    for part in components {
288        dir = Some({
289            let dir_ref = match dir.as_ref() {
290                Some(r) => r,
291                None => parent,
292            };
293            create_directory(dir_ref, part, flags).await?
294        })
295    }
296    dir.ok_or(OpenError::OpenError(zx_status::Status::INVALID_ARGS))
297}
298
299/// Opens the given `path` from the given `parent` directory as a [`FileProxy`]. If open fails,
300/// the returned `FileProxy` will be closed with an epitaph.
301pub fn open_file_async(
302    parent: &fio::DirectoryProxy,
303    path: &str,
304    flags: fio::Flags,
305) -> Result<fio::FileProxy, OpenError> {
306    let (file, server_end) = parent.domain().create_proxy::<fio::FileMarker>();
307
308    let mut flags = flags | fio::Flags::PROTOCOL_FILE;
309    if flags.intersection(fio::MASK_KNOWN_PERMISSIONS).is_empty() {
310        flags |= PERM_READABLE;
311    }
312
313    #[cfg(fuchsia_api_level_at_least = "27")]
314    parent
315        .open(path, flags, &fio::Options::default(), server_end.into_channel())
316        .map_err(OpenError::SendOpenRequest)?;
317    #[cfg(not(fuchsia_api_level_at_least = "27"))]
318    parent
319        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
320        .map_err(OpenError::SendOpenRequest)?;
321
322    Ok(file)
323}
324
325/// Opens the given `path` from given `parent` directory as a [`FileProxy`], verifying that the
326/// target implements the fuchsia.io.File protocol.
327pub async fn open_file(
328    parent: &fio::DirectoryProxy,
329    path: &str,
330    flags: fio::Flags,
331) -> Result<fio::FileProxy, OpenError> {
332    let (file, server_end) = parent.domain().create_proxy::<fio::FileMarker>();
333
334    let mut flags = flags | fio::Flags::PROTOCOL_FILE | fio::Flags::FLAG_SEND_REPRESENTATION;
335    if flags.intersection(fio::MASK_KNOWN_PERMISSIONS).is_empty() {
336        flags |= PERM_READABLE;
337    }
338
339    #[cfg(fuchsia_api_level_at_least = "27")]
340    parent
341        .open(path, flags, &fio::Options::default(), server_end.into_channel())
342        .map_err(OpenError::SendOpenRequest)?;
343    #[cfg(not(fuchsia_api_level_at_least = "27"))]
344    parent
345        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
346        .map_err(OpenError::SendOpenRequest)?;
347
348    // wait for the file to open and report success.
349    node::verify_file_describe_event(file).await
350}
351
352/// Opens the given `path` from the given `parent` directory as a [`NodeProxy`], verifying that the
353/// target implements the fuchsia.io.Node protocol.
354pub async fn open_node(
355    parent: &fio::DirectoryProxy,
356    path: &str,
357    flags: fio::Flags,
358) -> Result<fio::NodeProxy, OpenError> {
359    let (file, server_end) = parent.domain().create_proxy::<fio::NodeMarker>();
360
361    let flags = flags | fio::Flags::FLAG_SEND_REPRESENTATION;
362
363    #[cfg(fuchsia_api_level_at_least = "27")]
364    parent
365        .open(path, flags, &fio::Options::default(), server_end.into_channel())
366        .map_err(OpenError::SendOpenRequest)?;
367    #[cfg(not(fuchsia_api_level_at_least = "27"))]
368    parent
369        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
370        .map_err(OpenError::SendOpenRequest)?;
371
372    // wait for the file to open and report success.
373    node::verify_node_describe_event(file).await
374}
375
376/// Opens the given `path` from the given `parent` directory as a [`P::Proxy`]. The target is not
377/// verified to be any particular type and may not implement the [`P`] protocol.
378pub fn open_async<P: ProtocolMarker>(
379    parent: &fio::DirectoryProxy,
380    path: &str,
381    flags: fio::Flags,
382) -> Result<P::Proxy, OpenError> {
383    let (client, server_end) = parent.domain().create_endpoints::<P>();
384
385    #[cfg(fuchsia_api_level_at_least = "27")]
386    let () = parent
387        .open(path, flags, &fio::Options::default(), server_end.into_channel())
388        .map_err(OpenError::SendOpenRequest)?;
389    #[cfg(not(fuchsia_api_level_at_least = "27"))]
390    let () = parent
391        .open3(path, flags, &fio::Options::default(), server_end.into_channel())
392        .map_err(OpenError::SendOpenRequest)?;
393
394    Ok(ClientEnd::<P>::new(client.into_channel()).into_proxy())
395}
396
397/// Opens a new connection to the given `directory`. The cloned connection has the same permissions.
398pub fn clone(dir: &fio::DirectoryProxy) -> Result<fio::DirectoryProxy, CloneError> {
399    let (client_end, server_end) = dir.domain().create_proxy::<fio::DirectoryMarker>();
400    dir.clone(server_end.into_channel().into()).map_err(CloneError::SendCloneRequest)?;
401    Ok(client_end)
402}
403
404/// Opens a new connection to the given `directory` using `request`. The cloned connection has the
405/// same permissions as `directory`.
406pub fn clone_onto(
407    directory: &fio::DirectoryProxy,
408    request: ServerEnd<fio::DirectoryMarker>,
409) -> Result<(), CloneError> {
410    directory.clone(request.into_channel().into()).map_err(CloneError::SendCloneRequest)
411}
412
413/// Gracefully closes the directory proxy from the remote end.
414pub async fn close(dir: fio::DirectoryProxy) -> Result<(), CloseError> {
415    let result = dir.close().await.map_err(CloseError::SendCloseRequest)?;
416    result.map_err(|s| CloseError::CloseError(zx_status::Status::err_from_raw(s)))
417}
418
419/// Create a randomly named file in the given directory with the given prefix, and return its path
420/// and `FileProxy`. `prefix` may contain "/".
421pub async fn create_randomly_named_file(
422    dir: &fio::DirectoryProxy,
423    prefix: &str,
424    flags: fio::Flags,
425) -> Result<(String, fio::FileProxy), OpenError> {
426    use rand::distr::{Alphanumeric, SampleString as _};
427    use rand::rngs::SmallRng;
428    let mut rng: SmallRng = rand::make_rng();
429
430    let flags = flags | fio::Flags::FLAG_MUST_CREATE;
431
432    loop {
433        let random_string = Alphanumeric.sample_string(&mut rng, 6);
434        let path = prefix.to_string() + &random_string;
435
436        match open_file(dir, &path, flags).await {
437            Ok(file) => return Ok((path, file)),
438            Err(OpenError::OpenError(zx_status::Status::ALREADY_EXISTS)) => {}
439            Err(err) => return Err(err),
440        }
441    }
442}
443
444// Split the given path under the directory into parent and file name, and open the parent directory
445// if the path contains "/".
446async fn split_path<'a>(
447    dir: &fio::DirectoryProxy,
448    path: &'a str,
449) -> Result<(Option<fio::DirectoryProxy>, &'a str), OpenError> {
450    match path.rsplit_once('/') {
451        Some((parent, name)) => {
452            let proxy = open_directory(
453                dir,
454                parent,
455                fio::Flags::from_bits(fio::RW_STAR_DIR.bits()).unwrap(),
456            )
457            .await?;
458            Ok((Some(proxy), name))
459        }
460        None => Ok((None, path)),
461    }
462}
463
464/// Rename `src` to `dst` under the given directory, `src` and `dst` may contain "/".
465pub async fn rename(dir: &fio::DirectoryProxy, src: &str, dst: &str) -> Result<(), RenameError> {
466    use flex_client::Event;
467    let (src_parent, src_filename) = split_path(dir, src).await?;
468    let src_parent = src_parent.as_ref().unwrap_or(dir);
469    let (dst_parent, dst_filename) = split_path(dir, dst).await?;
470    let dst_parent = dst_parent.as_ref().unwrap_or(dir);
471    #[cfg(fuchsia_api_level_at_least = "NEXT")]
472    let (status, dst_parent_dir_token) =
473        dst_parent.deprecated_get_token().await.map_err(RenameError::SendGetTokenRequest)?;
474    #[cfg(not(fuchsia_api_level_at_least = "NEXT"))]
475    let (status, dst_parent_dir_token) =
476        dst_parent.get_token().await.map_err(RenameError::SendGetTokenRequest)?;
477    zx_status::Status::ok(status).map_err(RenameError::GetTokenError)?;
478    let event = Event::from(dst_parent_dir_token.ok_or(RenameError::NoHandleError)?);
479    src_parent
480        .rename(src_filename, event, dst_filename)
481        .await
482        .map_err(RenameError::SendRenameRequest)?
483        .map_err(|s| RenameError::RenameError(zx_status::Status::err_from_raw(s)))
484}
485
486pub use fio::DirentType as DirentKind;
487
488/// A directory entry.
489#[derive(Clone, Eq, Ord, PartialOrd, PartialEq, Debug)]
490pub struct DirEntry {
491    /// The name of this node.
492    pub name: String,
493
494    /// The type of this node, or [`DirentKind::Unknown`] if not known.
495    pub kind: DirentKind,
496}
497
498impl DirEntry {
499    fn root() -> Self {
500        Self { name: "".to_string(), kind: DirentKind::Directory }
501    }
502
503    fn is_dir(&self) -> bool {
504        self.kind == DirentKind::Directory
505    }
506
507    fn is_root(&self) -> bool {
508        self.is_dir() && self.name.is_empty()
509    }
510
511    fn chain(&self, subentry: &DirEntry) -> DirEntry {
512        if self.name.is_empty() {
513            DirEntry { name: subentry.name.clone(), kind: subentry.kind }
514        } else {
515            DirEntry { name: format!("{}/{}", self.name, subentry.name), kind: subentry.kind }
516        }
517    }
518}
519
520/// Returns Stream of nodes in tree rooted at the given DirectoryProxy for which |results_filter|
521/// returns `true` plus any leaf (empty) directories. The results filter receives the directory
522/// entry for the node in question and if the node is a directory, a reference a Vec of the
523/// directory's contents. The function recurses into sub-directories for which |recurse_filter|
524/// returns true. The returned entries will not include ".". |timeout| can be provided optionally
525/// to specify the maximum time to wait for a directory to be read.
526pub fn readdir_recursive_filtered<'a, ResultFn, RecurseFn>(
527    dir: &'a fio::DirectoryProxy,
528    timeout: Option<MonotonicDuration>,
529    results_filter: ResultFn,
530    recurse_filter: RecurseFn,
531) -> BoxStream<'a, Result<DirEntry, RecursiveEnumerateError>>
532where
533    ResultFn: Fn(&DirEntry, Option<&Vec<DirEntry>>) -> bool + Send + Sync + Copy + 'a,
534    RecurseFn: Fn(&DirEntry) -> bool + Send + Sync + Copy + 'a,
535{
536    let mut pending = VecDeque::new();
537    pending.push_back(DirEntry::root());
538    let results: VecDeque<DirEntry> = VecDeque::new();
539
540    stream::unfold((results, pending), move |(mut results, mut pending)| {
541        async move {
542            loop {
543                // Pending results to stream from the last read directory.
544                if !results.is_empty() {
545                    let result = results.pop_front().unwrap();
546                    return Some((Ok(result), (results, pending)));
547                }
548
549                // No pending directories to read and per the last condition no pending results to
550                // stream so finish the stream.
551                if pending.is_empty() {
552                    return None;
553                }
554
555                // The directory that will be read now.
556                let dir_entry = pending.pop_front().unwrap();
557
558                let sub_dir;
559                let dir_ref = if dir_entry.is_root() {
560                    dir
561                } else {
562                    match open_directory_async(dir, &dir_entry.name, fio::Flags::empty()) {
563                        Ok(dir) => {
564                            sub_dir = dir;
565                            &sub_dir
566                        }
567                        Err(err) => {
568                            let error = RecursiveEnumerateError::Open { name: dir_entry.name, err };
569                            return Some((Err(error), (results, pending)));
570                        }
571                    }
572                };
573
574                let readdir_result = match timeout {
575                    Some(timeout_duration) => readdir_with_timeout(dir_ref, timeout_duration).await,
576                    None => readdir(&dir_ref).await,
577                };
578                let subentries = match readdir_result {
579                    Ok(subentries) => subentries,
580                    // Promote timeout error.
581                    Err(EnumerateError::Timeout) => {
582                        return Some((Err(RecursiveEnumerateError::Timeout), (results, pending)));
583                    }
584                    Err(err) => {
585                        let error =
586                            Err(RecursiveEnumerateError::ReadDir { name: dir_entry.name, err });
587                        return Some((error, (results, pending)));
588                    }
589                };
590
591                // If this is an empty directory and the caller is interested
592                // in empty directories, emit that result.
593                if subentries.is_empty()
594                    && results_filter(&dir_entry, Some(&subentries))
595                    && !dir_entry.name.is_empty()
596                {
597                    return Some((Ok(dir_entry), (results, pending)));
598                }
599
600                for subentry in subentries.into_iter() {
601                    let subentry = dir_entry.chain(&subentry);
602                    if subentry.is_dir() && recurse_filter(&subentry) {
603                        pending.push_back(subentry.clone());
604                    }
605                    if results_filter(&subentry, None) {
606                        results.push_back(subentry);
607                    }
608                }
609            }
610        }
611    })
612    .boxed()
613}
614
615/// Returns a Vec of all non-directory nodes and all empty directory nodes in the given directory
616/// proxy. The returned entries will not include ".".
617/// |timeout| can be provided optionally to specify the maximum time to wait for a directory to be
618/// read.
619pub fn readdir_recursive(
620    dir: &fio::DirectoryProxy,
621    timeout: Option<MonotonicDuration>,
622) -> BoxStream<'_, Result<DirEntry, RecursiveEnumerateError>> {
623    readdir_recursive_filtered(
624        dir,
625        timeout,
626        |entry: &DirEntry, contents: Option<&Vec<DirEntry>>| {
627            // We're interested in results which are not directories and any
628            // empty directories.
629            !entry.is_dir() || (contents.is_some() && contents.unwrap().is_empty())
630        },
631        |_| true,
632    )
633}
634
635async fn readdir_inner(
636    dir: &fio::DirectoryProxy,
637    include_dot: bool,
638) -> Result<Vec<DirEntry>, EnumerateError> {
639    let status = dir.rewind().await.map_err(|e| EnumerateError::Fidl("rewind", e))?;
640    zx_status::Status::ok(status).map_err(EnumerateError::Rewind)?;
641
642    let mut entries = vec![];
643
644    loop {
645        let (status, buf) = dir
646            .read_dirents(fio::MAX_BUF)
647            .await
648            .map_err(|e| EnumerateError::Fidl("read_dirents", e))?;
649        zx_status::Status::ok(status).map_err(EnumerateError::ReadDirents)?;
650
651        if buf.is_empty() {
652            break;
653        }
654
655        for entry in parse_dir_entries(&buf) {
656            let entry = entry.map_err(EnumerateError::DecodeDirent)?;
657            if include_dot || entry.name != "." {
658                entries.push(entry);
659            }
660        }
661    }
662
663    entries.sort_unstable();
664
665    Ok(entries)
666}
667
668/// Returns a sorted Vec of directory entries contained directly in the given directory proxy.
669/// (Like `readdir`, but includes the dot path as well.)
670pub async fn readdir_inclusive(dir: &fio::DirectoryProxy) -> Result<Vec<DirEntry>, EnumerateError> {
671    readdir_inner(dir, /*include_dot=*/ true).await
672}
673
674/// Returns a sorted Vec of directory entries contained directly in the given directory proxy. The
675/// returned entries will not include "." or nodes from any subdirectories.
676pub async fn readdir(dir: &fio::DirectoryProxy) -> Result<Vec<DirEntry>, EnumerateError> {
677    readdir_inner(dir, /*include_dot=*/ false).await
678}
679
680/// Returns a sorted Vec of directory entries contained directly in the given directory proxy. The
681/// returned entries will not include "." or nodes from any subdirectories. Timeouts if the read
682/// takes longer than the given `timeout` duration.
683pub async fn readdir_with_timeout(
684    dir: &fio::DirectoryProxy,
685    timeout: MonotonicDuration,
686) -> Result<Vec<DirEntry>, EnumerateError> {
687    readdir(&dir).on_timeout(timeout.after_now(), || Err(EnumerateError::Timeout)).await
688}
689
690/// Returns `true` if an entry with the specified name exists in the given directory.
691pub async fn dir_contains(dir: &fio::DirectoryProxy, name: &str) -> Result<bool, EnumerateError> {
692    Ok(readdir(&dir).await?.iter().any(|e| e.name == name))
693}
694
695/// Returns `true` if an entry with the specified name exists in the given directory.
696///
697/// Timesout if reading the directory's entries takes longer than the given `timeout`
698/// duration.
699pub async fn dir_contains_with_timeout(
700    dir: &fio::DirectoryProxy,
701    name: &str,
702    timeout: MonotonicDuration,
703) -> Result<bool, EnumerateError> {
704    Ok(readdir_with_timeout(&dir, timeout).await?.iter().any(|e| e.name == name))
705}
706
707/// Parses the buffer returned by a read_dirents FIDL call.
708///
709/// Returns either an error or a parsed entry for each entry in the supplied buffer (see
710/// read_dirents for the format of this buffer).
711pub fn parse_dir_entries(mut buf: &[u8]) -> Vec<Result<DirEntry, DecodeDirentError>> {
712    #[derive(KnownLayout, FromBytes, Immutable, Unaligned)]
713    #[repr(C, packed)]
714    struct Dirent {
715        /// The inode number of the entry.
716        _ino: u64,
717        /// The length of the filename located after this entry.
718        size: u8,
719        /// The type of the entry. One of the `fio::DIRENT_TYPE_*` constants.
720        kind: u8,
721        // The unterminated name of the entry.  Length is the `size` field above.
722        // char name[0],
723    }
724
725    let mut entries = vec![];
726
727    while !buf.is_empty() {
728        let Ok((dirent, rest)) = Ref::<_, Dirent>::from_prefix(buf) else {
729            entries.push(Err(DecodeDirentError::BufferOverrun));
730            return entries;
731        };
732
733        let entry = {
734            // Don't read past the end of the buffer.
735            let size = usize::from(dirent.size);
736            if size > rest.len() {
737                entries.push(Err(DecodeDirentError::BufferOverrun));
738                return entries;
739            }
740
741            // Advance to the next entry.
742            buf = &rest[size..];
743            match String::from_utf8(rest[..size].to_vec()) {
744                Ok(name) => Ok(DirEntry {
745                    name,
746                    kind: DirentKind::from_primitive(dirent.kind).unwrap_or(DirentKind::Unknown),
747                }),
748                Err(err) => Err(DecodeDirentError::InvalidUtf8(err.utf8_error())),
749            }
750        };
751
752        entries.push(entry);
753    }
754
755    entries
756}
757
758const DIR_FLAGS: fio::Flags = fio::Flags::empty()
759    .union(fio::Flags::PROTOCOL_DIRECTORY)
760    .union(PERM_READABLE)
761    .union(fio::Flags::PERM_INHERIT_WRITE);
762
763/// Removes a directory and all of its children. `name` must be a subdirectory of `root_dir`.
764///
765/// The async analogue of `std::fs::remove_dir_all`.
766pub async fn remove_dir_recursive(
767    root_dir: &fio::DirectoryProxy,
768    name: &str,
769) -> Result<(), EnumerateError> {
770    let (dir, dir_server) = root_dir.domain().create_proxy::<fio::DirectoryMarker>();
771
772    #[cfg(fuchsia_api_level_at_least = "27")]
773    root_dir
774        .open(name, DIR_FLAGS, &fio::Options::default(), dir_server.into_channel())
775        .map_err(|e| EnumerateError::Fidl("open", e))?;
776    #[cfg(not(fuchsia_api_level_at_least = "27"))]
777    root_dir
778        .open3(name, DIR_FLAGS, &fio::Options::default(), dir_server.into_channel())
779        .map_err(|e| EnumerateError::Fidl("open", e))?;
780    remove_dir_contents(dir).await?;
781    root_dir
782        .unlink(
783            name,
784            &fio::UnlinkOptions {
785                flags: Some(fio::UnlinkFlags::MUST_BE_DIRECTORY),
786                ..Default::default()
787            },
788        )
789        .await
790        .map_err(|e| EnumerateError::Fidl("unlink", e))?
791        .map_err(|s| EnumerateError::Unlink(zx_status::Status::err_from_raw(s)))
792}
793
794// Returns a `BoxFuture` instead of being async because async doesn't support recursion.
795fn remove_dir_contents(dir: fio::DirectoryProxy) -> BoxFuture<'static, Result<(), EnumerateError>> {
796    let fut = async move {
797        for dirent in readdir(&dir).await? {
798            match dirent.kind {
799                DirentKind::Directory => {
800                    let (subdir, subdir_server) =
801                        dir.domain().create_proxy::<fio::DirectoryMarker>();
802                    #[cfg(fuchsia_api_level_at_least = "27")]
803                    dir.open(
804                        &dirent.name,
805                        DIR_FLAGS,
806                        &fio::Options::default(),
807                        subdir_server.into_channel(),
808                    )
809                    .map_err(|e| EnumerateError::Fidl("open", e))?;
810                    #[cfg(not(fuchsia_api_level_at_least = "27"))]
811                    dir.open3(
812                        &dirent.name,
813                        DIR_FLAGS,
814                        &fio::Options::default(),
815                        subdir_server.into_channel(),
816                    )
817                    .map_err(|e| EnumerateError::Fidl("open", e))?;
818                    remove_dir_contents(subdir).await?;
819                }
820                _ => {}
821            }
822            dir.unlink(&dirent.name, &fio::UnlinkOptions::default())
823                .await
824                .map_err(|e| EnumerateError::Fidl("unlink", e))?
825                .map_err(|s| EnumerateError::Unlink(zx_status::Status::err_from_raw(s)))?;
826        }
827        Ok(())
828    };
829    Box::pin(fut)
830}
831
832/// Opens `path` from the `parent` directory as a file and reads the file contents as a utf-8
833/// encoded string.
834#[cfg(not(feature = "fdomain"))]
835pub async fn read_file_to_string(
836    parent: &fio::DirectoryProxy,
837    path: &str,
838) -> Result<String, crate::file::ReadError> {
839    let contents = read_file(parent, path).await?;
840    Ok(String::from_utf8(contents)?)
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846    use crate::directory::OpenError;
847    use crate::file::{ReadError, WriteError, write};
848    use assert_matches::assert_matches;
849    use fuchsia_async as fasync;
850    use futures::channel::oneshot;
851    use proptest::prelude::*;
852    use tempfile::TempDir;
853    use vfs::file::vmo::read_only;
854    use vfs::pseudo_directory;
855    use vfs::remote::remote_dir;
856
857    const DATA_FILE_CONTENTS: &str = "Hello World!\n";
858
859    #[cfg(target_os = "fuchsia")]
860    const LONG_DURATION: MonotonicDuration = MonotonicDuration::from_seconds(30);
861
862    #[cfg(not(target_os = "fuchsia"))]
863    const LONG_DURATION: MonotonicDuration = MonotonicDuration::from_secs(30);
864
865    proptest! {
866        #[test]
867        fn test_parse_dir_entries_does_not_crash(buf in prop::collection::vec(any::<u8>(), 0..200)) {
868            parse_dir_entries(&buf);
869        }
870    }
871
872    fn open_pkg() -> fio::DirectoryProxy {
873        open_in_namespace("/pkg", fio::PERM_READABLE).unwrap()
874    }
875
876    fn open_tmp() -> (TempDir, fio::DirectoryProxy) {
877        let tempdir = TempDir::new().expect("failed to create tmp dir");
878        let proxy = open_in_namespace(
879            tempdir.path().to_str().unwrap(),
880            fio::PERM_READABLE | fio::PERM_WRITABLE,
881        )
882        .unwrap();
883        (tempdir, proxy)
884    }
885
886    fn open_data() -> (TempDir, fio::DirectoryProxy) {
887        let tempdir = TempDir::new_in("/data").expect("failed to create tmp dir in /data");
888        let proxy = open_in_namespace(
889            tempdir.path().to_str().unwrap(),
890            fio::PERM_READABLE | fio::PERM_WRITABLE,
891        )
892        .unwrap();
893        (tempdir, proxy)
894    }
895
896    // open_in_namespace
897
898    #[fasync::run_singlethreaded(test)]
899    async fn open_in_namespace_opens_real_dir() {
900        let exists = open_in_namespace("/pkg", fio::PERM_READABLE).unwrap();
901        assert_matches!(close(exists).await, Ok(()));
902    }
903
904    #[fasync::run_singlethreaded(test)]
905    async fn open_in_namespace_opens_fake_subdir_of_root_namespace_entry() {
906        let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
907        // The open error is not detected until the proxy is interacted with.
908        assert_matches!(close(notfound).await, Err(_));
909    }
910
911    #[fasync::run_singlethreaded(test)]
912    async fn open_in_namespace_rejects_fake_root_namespace_entry() {
913        let result = open_in_namespace("/fake", fio::PERM_READABLE);
914        assert_matches!(result, Err(OpenError::Namespace(zx_status::Status::NOT_FOUND)));
915        assert_matches!(result, Err(e) if e.is_not_found_error());
916    }
917
918    // open_directory_async
919
920    #[fasync::run_singlethreaded(test)]
921    async fn open_directory_async_opens_real_dir() {
922        let pkg = open_pkg();
923        let data = open_directory_async(&pkg, "data", fio::PERM_READABLE).unwrap();
924        close(data).await.unwrap();
925    }
926
927    #[fasync::run_singlethreaded(test)]
928    async fn open_directory_async_opens_fake_dir() {
929        let pkg = open_pkg();
930        let fake = open_directory_async(&pkg, "fake", fio::PERM_READABLE).unwrap();
931        // The open error is not detected until the proxy is interacted with.
932        assert_matches!(close(fake).await, Err(_));
933    }
934
935    // open_directory
936
937    #[fasync::run_singlethreaded(test)]
938    async fn open_directory_opens_real_dir() {
939        let pkg = open_pkg();
940        let data = open_directory(&pkg, "data", fio::PERM_READABLE).await.unwrap();
941        close(data).await.unwrap();
942    }
943
944    #[fasync::run_singlethreaded(test)]
945    async fn open_directory_rejects_fake_dir() {
946        let pkg = open_pkg();
947
948        let result = open_directory(&pkg, "fake", fio::PERM_READABLE).await;
949        assert_matches!(result, Err(OpenError::OpenError(zx_status::Status::NOT_FOUND)));
950        assert_matches!(result, Err(e) if e.is_not_found_error());
951    }
952
953    #[fasync::run_singlethreaded(test)]
954    async fn open_directory_rejects_file() {
955        let pkg = open_pkg();
956
957        assert_matches!(
958            open_directory(&pkg, "data/file", fio::PERM_READABLE).await,
959            Err(OpenError::OpenError(zx_status::Status::NOT_DIR))
960        );
961    }
962
963    // create_directory
964
965    #[fasync::run_singlethreaded(test)]
966    async fn create_directory_simple() {
967        let (_tmp, proxy) = open_tmp();
968        let dir = create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
969        crate::directory::close(dir).await.unwrap();
970    }
971
972    #[fasync::run_singlethreaded(test)]
973    async fn create_directory_add_file() {
974        let (_tmp, proxy) = open_tmp();
975        let dir =
976            create_directory(&proxy, "dir", fio::PERM_READABLE | fio::PERM_WRITABLE).await.unwrap();
977        let file = open_file(&dir, "data", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
978            .await
979            .unwrap();
980        crate::file::close(file).await.unwrap();
981    }
982
983    #[fasync::run_singlethreaded(test)]
984    async fn create_directory_existing_dir_opens() {
985        let (_tmp, proxy) = open_tmp();
986        let dir = create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
987        crate::directory::close(dir).await.unwrap();
988        create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
989    }
990
991    #[fasync::run_singlethreaded(test)]
992    async fn create_directory_existing_dir_fails_if_must_create() {
993        let (_tmp, proxy) = open_tmp();
994        let dir =
995            create_directory(&proxy, "dir", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
996                .await
997                .unwrap();
998        crate::directory::close(dir).await.unwrap();
999        assert_matches!(
1000            create_directory(&proxy, "dir", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
1001                .await,
1002            Err(_)
1003        );
1004    }
1005
1006    // open_file_async
1007
1008    #[fasync::run_singlethreaded(test)]
1009    async fn open_file_no_describe_opens_real_file() {
1010        let pkg = open_pkg();
1011        let file = open_file_async(&pkg, "data/file", fio::PERM_READABLE).unwrap();
1012        crate::file::close(file).await.unwrap();
1013    }
1014
1015    #[fasync::run_singlethreaded(test)]
1016    async fn open_file_no_describe_opens_fake_file() {
1017        let pkg = open_pkg();
1018        let fake = open_file_async(&pkg, "data/fake", fio::PERM_READABLE).unwrap();
1019        // The open error is not detected until the proxy is interacted with.
1020        assert_matches!(crate::file::close(fake).await, Err(_));
1021    }
1022
1023    // open_file
1024
1025    #[fasync::run_singlethreaded(test)]
1026    async fn open_file_opens_real_file() {
1027        let pkg = open_pkg();
1028        let file = open_file(&pkg, "data/file", fio::PERM_READABLE).await.unwrap();
1029        assert_eq!(
1030            file.seek(fio::SeekOrigin::End, 0).await.unwrap(),
1031            Ok(DATA_FILE_CONTENTS.len() as u64),
1032        );
1033        crate::file::close(file).await.unwrap();
1034    }
1035
1036    #[fasync::run_singlethreaded(test)]
1037    async fn open_file_rejects_fake_file() {
1038        let pkg = open_pkg();
1039
1040        let result = open_file(&pkg, "data/fake", fio::PERM_READABLE).await;
1041        assert_matches!(result, Err(OpenError::OpenError(zx_status::Status::NOT_FOUND)));
1042        assert_matches!(result, Err(e) if e.is_not_found_error());
1043    }
1044
1045    #[fasync::run_singlethreaded(test)]
1046    async fn open_file_rejects_dir() {
1047        let pkg = open_pkg();
1048
1049        assert_matches!(
1050            open_file(&pkg, "data", fio::PERM_READABLE).await,
1051            Err(OpenError::UnexpectedNodeKind {
1052                expected: node::Kind::File,
1053                actual: node::Kind::Directory,
1054            } | node::OpenError::OpenError(zx_status::Status::NOT_FILE))
1055        );
1056    }
1057
1058    #[fasync::run_singlethreaded(test)]
1059    async fn open_file_flags() {
1060        let tempdir = TempDir::new().expect("failed to create tmp dir");
1061        std::fs::write(tempdir.path().join("read_write"), "rw/read_write")
1062            .expect("failed to write file");
1063        let dir = crate::directory::open_in_namespace(
1064            tempdir.path().to_str().unwrap(),
1065            fio::PERM_READABLE | fio::PERM_WRITABLE,
1066        )
1067        .expect("could not open tmp dir");
1068        let example_dir = pseudo_directory! {
1069            "ro" => pseudo_directory! {
1070                "read_only" => read_only("ro/read_only"),
1071            },
1072            "rw" => remote_dir(dir)
1073        };
1074        let example_dir_proxy = vfs::directory::serve(
1075            example_dir,
1076            vfs::execution_scope::ExecutionScope::new(),
1077            fio::PERM_READABLE | fio::PERM_WRITABLE,
1078        );
1079
1080        for (file_name, flags, should_succeed) in vec![
1081            ("ro/read_only", fio::PERM_READABLE, true),
1082            ("ro/read_only", fio::PERM_READABLE | fio::PERM_WRITABLE, false),
1083            ("ro/read_only", fio::PERM_WRITABLE, false),
1084            ("rw/read_write", fio::PERM_READABLE, true),
1085            ("rw/read_write", fio::PERM_READABLE | fio::PERM_WRITABLE, true),
1086            ("rw/read_write", fio::PERM_WRITABLE, true),
1087        ] {
1088            // open_file_async
1089
1090            let file = open_file_async(&example_dir_proxy, file_name, flags).unwrap();
1091            match (should_succeed, file.query().await) {
1092                (true, Ok(_)) => (),
1093                (false, Err(_)) => continue,
1094                (true, Err(e)) => {
1095                    panic!("failed to open when expected success, couldn't describe: {:?}", e)
1096                }
1097                (false, Ok(d)) => {
1098                    panic!("successfully opened when expected failure, could describe: {:?}", d)
1099                }
1100            }
1101            if flags.intersects(fio::Flags::PERM_READ_BYTES) {
1102                assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
1103            }
1104            if flags.intersects(fio::Flags::PERM_WRITE_BYTES) {
1105                let _ = file.seek(fio::SeekOrigin::Start, 0).await.expect("Seek failed!");
1106                let _: u64 = file
1107                    .write(file_name.as_bytes())
1108                    .await
1109                    .unwrap()
1110                    .map_err(zx_status::Status::err_from_raw)
1111                    .unwrap();
1112            }
1113            crate::file::close(file).await.unwrap();
1114
1115            // open_file
1116
1117            match open_file(&example_dir_proxy, file_name, flags).await {
1118                Ok(file) if should_succeed => {
1119                    if flags.intersects(fio::Flags::PERM_READ_BYTES) {
1120                        assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
1121                    }
1122                    if flags.intersects(fio::Flags::PERM_WRITE_BYTES) {
1123                        let _ = file.seek(fio::SeekOrigin::Start, 0).await.expect("Seek failed!");
1124                        let _: u64 = file
1125                            .write(file_name.as_bytes())
1126                            .await
1127                            .unwrap()
1128                            .map_err(zx_status::Status::err_from_raw)
1129                            .unwrap();
1130                    }
1131                    crate::file::close(file).await.unwrap();
1132                }
1133                Ok(_) => {
1134                    panic!("successfully opened when expected failure: {:?}", (file_name, flags))
1135                }
1136                Err(e) if should_succeed => {
1137                    panic!("failed to open when expected success: {:?}", (e, file_name, flags))
1138                }
1139                Err(_) => {}
1140            }
1141        }
1142    }
1143
1144    // open_node
1145
1146    #[fasync::run_singlethreaded(test)]
1147    async fn open_node_opens_real_node() {
1148        let pkg = open_pkg();
1149        let node = open_node(&pkg, "data", fio::PERM_READABLE).await.unwrap();
1150        crate::node::close(node).await.unwrap();
1151    }
1152
1153    #[fasync::run_singlethreaded(test)]
1154    async fn open_node_opens_fake_node() {
1155        let pkg = open_pkg();
1156        // The open error should be detected immediately.
1157        assert_matches!(open_node(&pkg, "fake", fio::PERM_READABLE).await, Err(_));
1158    }
1159
1160    // create_randomly_named_file
1161
1162    #[fasync::run_singlethreaded(test)]
1163    async fn create_randomly_named_file_simple() {
1164        let (_tmp, proxy) = open_tmp();
1165        let (path, file) =
1166            create_randomly_named_file(&proxy, "prefix", fio::PERM_WRITABLE).await.unwrap();
1167        assert!(path.starts_with("prefix"));
1168        crate::file::close(file).await.unwrap();
1169    }
1170
1171    #[fasync::run_singlethreaded(test)]
1172    async fn create_randomly_named_file_subdir() {
1173        let (_tmp, proxy) = open_tmp();
1174        let _subdir = create_directory(&proxy, "subdir", fio::PERM_WRITABLE).await.unwrap();
1175        let (path, file) =
1176            create_randomly_named_file(&proxy, "subdir/file", fio::PERM_WRITABLE).await.unwrap();
1177        assert!(path.starts_with("subdir/file"));
1178        crate::file::close(file).await.unwrap();
1179    }
1180
1181    #[fasync::run_singlethreaded(test)]
1182    async fn create_randomly_named_file_no_prefix() {
1183        let (_tmp, proxy) = open_tmp();
1184        let (_path, file) =
1185            create_randomly_named_file(&proxy, "", fio::PERM_READABLE | fio::PERM_WRITABLE)
1186                .await
1187                .unwrap();
1188        crate::file::close(file).await.unwrap();
1189    }
1190
1191    #[fasync::run_singlethreaded(test)]
1192    async fn create_randomly_named_file_error() {
1193        let pkg = open_pkg();
1194        assert_matches!(create_randomly_named_file(&pkg, "", fio::Flags::empty()).await, Err(_));
1195    }
1196
1197    // rename
1198
1199    #[fasync::run_singlethreaded(test)]
1200    async fn rename_simple() {
1201        let (tmp, proxy) = open_tmp();
1202        let (path, file) =
1203            create_randomly_named_file(&proxy, "", fio::PERM_WRITABLE).await.unwrap();
1204        crate::file::close(file).await.unwrap();
1205        rename(&proxy, &path, "new_path").await.unwrap();
1206        assert!(!tmp.path().join(path).exists());
1207        assert!(tmp.path().join("new_path").exists());
1208    }
1209
1210    #[fasync::run_singlethreaded(test)]
1211    async fn rename_with_subdir() {
1212        let (tmp, proxy) = open_tmp();
1213        let _subdir1 = create_directory(&proxy, "subdir1", fio::PERM_WRITABLE).await.unwrap();
1214        let _subdir2 = create_directory(&proxy, "subdir2", fio::PERM_WRITABLE).await.unwrap();
1215        let (path, file) =
1216            create_randomly_named_file(&proxy, "subdir1/file", fio::PERM_WRITABLE).await.unwrap();
1217        crate::file::close(file).await.unwrap();
1218        rename(&proxy, &path, "subdir2/file").await.unwrap();
1219        assert!(!tmp.path().join(path).exists());
1220        assert!(tmp.path().join("subdir2/file").exists());
1221    }
1222
1223    #[fasync::run_singlethreaded(test)]
1224    async fn rename_directory() {
1225        let (tmp, proxy) = open_tmp();
1226        let dir = create_directory(&proxy, "dir", fio::PERM_WRITABLE).await.unwrap();
1227        close(dir).await.unwrap();
1228        rename(&proxy, "dir", "dir2").await.unwrap();
1229        assert!(!tmp.path().join("dir").exists());
1230        assert!(tmp.path().join("dir2").exists());
1231    }
1232
1233    #[fasync::run_singlethreaded(test)]
1234    async fn rename_overwrite_existing_file() {
1235        let (tmp, proxy) = open_tmp();
1236        std::fs::write(tmp.path().join("foo"), b"foo").unwrap();
1237        std::fs::write(tmp.path().join("bar"), b"bar").unwrap();
1238        rename(&proxy, "foo", "bar").await.unwrap();
1239        assert!(!tmp.path().join("foo").exists());
1240        assert_eq!(std::fs::read_to_string(tmp.path().join("bar")).unwrap(), "foo");
1241    }
1242
1243    #[fasync::run_singlethreaded(test)]
1244    async fn rename_non_existing_src_fails() {
1245        let (tmp, proxy) = open_tmp();
1246        assert_matches!(
1247            rename(&proxy, "foo", "bar").await,
1248            Err(RenameError::RenameError(zx_status::Status::NOT_FOUND))
1249        );
1250        assert!(!tmp.path().join("foo").exists());
1251        assert!(!tmp.path().join("bar").exists());
1252    }
1253
1254    #[fasync::run_singlethreaded(test)]
1255    async fn rename_to_non_existing_subdir_fails() {
1256        let (tmp, proxy) = open_tmp();
1257        std::fs::write(tmp.path().join("foo"), b"foo").unwrap();
1258        assert_matches!(
1259            rename(&proxy, "foo", "bar/foo").await,
1260            Err(RenameError::OpenError(OpenError::OpenError(zx_status::Status::NOT_FOUND)))
1261        );
1262        assert!(tmp.path().join("foo").exists());
1263        assert!(!tmp.path().join("bar/foo").exists());
1264    }
1265
1266    #[fasync::run_singlethreaded(test)]
1267    async fn rename_root_path_fails() {
1268        let (tmp, proxy) = open_tmp();
1269        assert_matches!(
1270            rename(&proxy, "/foo", "bar").await,
1271            Err(RenameError::OpenError(OpenError::OpenError(zx_status::Status::INVALID_ARGS)))
1272        );
1273        assert!(!tmp.path().join("bar").exists());
1274    }
1275
1276    // parse_dir_entries
1277
1278    #[test]
1279    fn test_parse_dir_entries_rejects_invalid_utf8() {
1280        #[rustfmt::skip]
1281        let buf = &[
1282            // entry 0
1283            // ino
1284            1, 0, 0, 0, 0, 0, 0, 0,
1285            // name length
1286            1,
1287            // type
1288            fio::DirentType::File.into_primitive(),
1289            // name (a lonely continuation byte)
1290            0x80,
1291            // entry 1
1292            // ino
1293            2, 0, 0, 0, 0, 0, 0, 0,
1294            // name length
1295            4,
1296            // type
1297            fio::DirentType::File.into_primitive(),
1298            // name
1299            'o' as u8, 'k' as u8, 'a' as u8, 'y' as u8,
1300        ];
1301
1302        #[allow(unknown_lints, invalid_from_utf8)]
1303        let expected_err = std::str::from_utf8(&[0x80]).unwrap_err();
1304
1305        assert_eq!(
1306            parse_dir_entries(buf),
1307            vec![
1308                Err(DecodeDirentError::InvalidUtf8(expected_err)),
1309                Ok(DirEntry { name: "okay".to_string(), kind: DirentKind::File })
1310            ]
1311        );
1312    }
1313
1314    #[test]
1315    fn test_parse_dir_entries_overrun() {
1316        #[rustfmt::skip]
1317        let buf = &[
1318            // ino
1319            0, 0, 0, 0, 0, 0, 0, 0,
1320            // name length
1321            5,
1322            // type
1323            fio::DirentType::File.into_primitive(),
1324            // name
1325            't' as u8, 'e' as u8, 's' as u8, 't' as u8,
1326        ];
1327
1328        assert_eq!(parse_dir_entries(buf), vec![Err(DecodeDirentError::BufferOverrun)]);
1329    }
1330
1331    // readdir
1332
1333    #[fasync::run_singlethreaded(test)]
1334    async fn test_readdir() {
1335        let dir = pseudo_directory! {
1336            "afile" => read_only(""),
1337            "zzz" => read_only(""),
1338            "subdir" => pseudo_directory! {
1339                "ignored" => read_only(""),
1340            },
1341        };
1342        let dir_proxy =
1343            vfs::directory::serve_read_only(dir, vfs::execution_scope::ExecutionScope::new());
1344
1345        // run twice to check that seek offset is properly reset before reading the directory
1346        for _ in 0..2 {
1347            let entries = readdir(&dir_proxy).await.expect("readdir failed");
1348            assert_eq!(
1349                entries,
1350                vec![
1351                    build_direntry("afile", DirentKind::File),
1352                    build_direntry("subdir", DirentKind::Directory),
1353                    build_direntry("zzz", DirentKind::File),
1354                ]
1355            );
1356        }
1357    }
1358
1359    // dir_contains
1360
1361    #[fasync::run_singlethreaded(test)]
1362    async fn test_dir_contains() {
1363        let dir = pseudo_directory! {
1364            "afile" => read_only(""),
1365            "zzz" => read_only(""),
1366            "subdir" => pseudo_directory! {
1367                "ignored" => read_only(""),
1368            },
1369        };
1370        let dir_proxy =
1371            vfs::directory::serve_read_only(dir, vfs::execution_scope::ExecutionScope::new());
1372
1373        for file in &["afile", "zzz", "subdir"] {
1374            assert!(dir_contains(&dir_proxy, file).await.unwrap());
1375        }
1376
1377        assert!(
1378            !dir_contains(&dir_proxy, "notin").await.expect("error checking if dir contains notin")
1379        );
1380    }
1381
1382    #[fasync::run_singlethreaded(test)]
1383    async fn test_dir_contains_with_timeout() {
1384        let tempdir = TempDir::new().expect("failed to create tmp dir");
1385        let dir = create_nested_dir(&tempdir).await;
1386        let first = dir_contains_with_timeout(&dir, "notin", LONG_DURATION)
1387            .await
1388            .expect("error checking dir contains notin");
1389        assert!(!first);
1390        let second = dir_contains_with_timeout(&dir, "a", LONG_DURATION)
1391            .await
1392            .expect("error checking dir contains a");
1393        assert!(second);
1394    }
1395
1396    // readdir_recursive
1397
1398    #[fasync::run_singlethreaded(test)]
1399    async fn test_readdir_recursive() {
1400        let tempdir = TempDir::new().expect("failed to create tmp dir");
1401        let dir = create_nested_dir(&tempdir).await;
1402        // run twice to check that seek offset is properly reset before reading the directory
1403        for _ in 0..2 {
1404            let (tx, rx) = oneshot::channel();
1405            let clone_dir = clone(&dir).expect("clone dir");
1406            fasync::Task::spawn(async move {
1407                let entries = readdir_recursive(&clone_dir, None)
1408                    .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1409                    .await
1410                    .into_iter()
1411                    .collect::<Result<Vec<_>, _>>()
1412                    .expect("readdir_recursive failed");
1413                tx.send(entries).expect("sending entries failed");
1414            })
1415            .detach();
1416            let entries = rx.await.expect("receiving entries failed");
1417            assert_eq!(
1418                entries,
1419                vec![
1420                    build_direntry("a", DirentKind::File),
1421                    build_direntry("b", DirentKind::File),
1422                    build_direntry("emptydir", DirentKind::Directory),
1423                    build_direntry("subdir/a", DirentKind::File),
1424                    build_direntry("subdir/subsubdir/a", DirentKind::File),
1425                    build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1426                ]
1427            );
1428        }
1429    }
1430
1431    #[fasync::run_singlethreaded(test)]
1432    async fn test_readdir_recursive_timeout_expired() {
1433        // This test must use a forever-pending server in order to ensure that the timeout
1434        // triggers before the function under test finishes, even if the timeout is
1435        // in the past.
1436        let (dir, _server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
1437        let result = readdir_recursive(&dir, Some(zx::MonotonicDuration::from_nanos(0)))
1438            .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1439            .await
1440            .into_iter()
1441            .collect::<Result<Vec<_>, _>>();
1442        assert!(result.is_err());
1443    }
1444
1445    #[fasync::run_singlethreaded(test)]
1446    async fn test_readdir_recursive_timeout() {
1447        let tempdir = TempDir::new().expect("failed to create tmp dir");
1448        let dir = create_nested_dir(&tempdir).await;
1449        let entries = readdir_recursive(&dir, Some(LONG_DURATION))
1450            .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1451            .await
1452            .into_iter()
1453            .collect::<Result<Vec<_>, _>>()
1454            .expect("readdir_recursive failed");
1455        assert_eq!(
1456            entries,
1457            vec![
1458                build_direntry("a", DirentKind::File),
1459                build_direntry("b", DirentKind::File),
1460                build_direntry("emptydir", DirentKind::Directory),
1461                build_direntry("subdir/a", DirentKind::File),
1462                build_direntry("subdir/subsubdir/a", DirentKind::File),
1463                build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1464            ]
1465        );
1466    }
1467
1468    // remove_dir
1469
1470    #[fasync::run_singlethreaded(test)]
1471    async fn test_remove_dir_recursive() {
1472        {
1473            let tempdir = TempDir::new().expect("failed to create tmp dir");
1474            let dir = create_nested_dir(&tempdir).await;
1475            remove_dir_recursive(&dir, "emptydir").await.expect("remove_dir_recursive failed");
1476            let entries = readdir_recursive(&dir, None)
1477                .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1478                .await
1479                .into_iter()
1480                .collect::<Result<Vec<_>, _>>()
1481                .expect("readdir_recursive failed");
1482            assert_eq!(
1483                entries,
1484                vec![
1485                    build_direntry("a", DirentKind::File),
1486                    build_direntry("b", DirentKind::File),
1487                    build_direntry("subdir/a", DirentKind::File),
1488                    build_direntry("subdir/subsubdir/a", DirentKind::File),
1489                    build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1490                ]
1491            );
1492        }
1493        {
1494            let tempdir = TempDir::new().expect("failed to create tmp dir");
1495            let dir = create_nested_dir(&tempdir).await;
1496            remove_dir_recursive(&dir, "subdir").await.expect("remove_dir_recursive failed");
1497            let entries = readdir_recursive(&dir, None)
1498                .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1499                .await
1500                .into_iter()
1501                .collect::<Result<Vec<_>, _>>()
1502                .expect("readdir_recursive failed");
1503            assert_eq!(
1504                entries,
1505                vec![
1506                    build_direntry("a", DirentKind::File),
1507                    build_direntry("b", DirentKind::File),
1508                    build_direntry("emptydir", DirentKind::Directory),
1509                ]
1510            );
1511        }
1512        {
1513            let tempdir = TempDir::new().expect("failed to create tmp dir");
1514            let dir = create_nested_dir(&tempdir).await;
1515            let subdir = open_directory(&dir, "subdir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1516                .await
1517                .expect("could not open subdir");
1518            remove_dir_recursive(&subdir, "subsubdir").await.expect("remove_dir_recursive failed");
1519            let entries = readdir_recursive(&dir, None)
1520                .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1521                .await
1522                .into_iter()
1523                .collect::<Result<Vec<_>, _>>()
1524                .expect("readdir_recursive failed");
1525            assert_eq!(
1526                entries,
1527                vec![
1528                    build_direntry("a", DirentKind::File),
1529                    build_direntry("b", DirentKind::File),
1530                    build_direntry("emptydir", DirentKind::Directory),
1531                    build_direntry("subdir/a", DirentKind::File),
1532                ]
1533            );
1534        }
1535        {
1536            let tempdir = TempDir::new().expect("failed to create tmp dir");
1537            let dir = create_nested_dir(&tempdir).await;
1538            let subsubdir =
1539                open_directory(&dir, "subdir/subsubdir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1540                    .await
1541                    .expect("could not open subsubdir");
1542            remove_dir_recursive(&subsubdir, "emptydir")
1543                .await
1544                .expect("remove_dir_recursive failed");
1545            let entries = readdir_recursive(&dir, None)
1546                .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1547                .await
1548                .into_iter()
1549                .collect::<Result<Vec<_>, _>>()
1550                .expect("readdir_recursive failed");
1551            assert_eq!(
1552                entries,
1553                vec![
1554                    build_direntry("a", DirentKind::File),
1555                    build_direntry("b", DirentKind::File),
1556                    build_direntry("emptydir", DirentKind::Directory),
1557                    build_direntry("subdir/a", DirentKind::File),
1558                    build_direntry("subdir/subsubdir/a", DirentKind::File),
1559                ]
1560            );
1561        }
1562    }
1563
1564    #[fasync::run_singlethreaded(test)]
1565    async fn test_remove_dir_recursive_errors() {
1566        {
1567            let tempdir = TempDir::new().expect("failed to create tmp dir");
1568            let dir = create_nested_dir(&tempdir).await;
1569            let res = remove_dir_recursive(&dir, "baddir").await;
1570            let res = res.expect_err("remove_dir did not fail");
1571            match res {
1572                EnumerateError::Fidl("rewind", fidl_error) if fidl_error.is_closed() => {}
1573                _ => panic!("unexpected error {:?}", res),
1574            }
1575        }
1576        {
1577            let tempdir = TempDir::new().expect("failed to create tmp dir");
1578            let dir = create_nested_dir(&tempdir).await;
1579            let res = remove_dir_recursive(&dir, ".").await;
1580            let expected: Result<(), EnumerateError> =
1581                Err(EnumerateError::Unlink(zx_status::Status::INVALID_ARGS));
1582            assert_eq!(format!("{:?}", res), format!("{:?}", expected));
1583        }
1584    }
1585
1586    // create_directory_recursive
1587
1588    #[fasync::run_singlethreaded(test)]
1589    async fn create_directory_recursive_test() {
1590        let tempdir = TempDir::new().unwrap();
1591
1592        let path = "path/to/example/dir";
1593        let file_name = "example_file_name";
1594        let data = "file contents";
1595
1596        let root_dir = open_in_namespace(
1597            tempdir.path().to_str().unwrap(),
1598            fio::PERM_READABLE | fio::PERM_WRITABLE,
1599        )
1600        .expect("open_in_namespace failed");
1601
1602        let sub_dir =
1603            create_directory_recursive(&root_dir, &path, fio::PERM_READABLE | fio::PERM_WRITABLE)
1604                .await
1605                .expect("create_directory_recursive failed");
1606        let file = open_file(
1607            &sub_dir,
1608            &file_name,
1609            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1610        )
1611        .await
1612        .expect("open_file failed");
1613
1614        write(&file, &data).await.expect("writing to the file failed");
1615
1616        let contents = std::fs::read_to_string(tempdir.path().join(path).join(file_name))
1617            .expect("read_to_string failed");
1618        assert_eq!(&contents, &data, "File contents did not match");
1619    }
1620
1621    async fn create_nested_dir(tempdir: &TempDir) -> fio::DirectoryProxy {
1622        let dir = open_in_namespace(
1623            tempdir.path().to_str().unwrap(),
1624            fio::PERM_READABLE | fio::PERM_WRITABLE,
1625        )
1626        .expect("could not open tmp dir");
1627        create_directory_recursive(&dir, "emptydir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1628            .await
1629            .expect("failed to create emptydir");
1630        create_directory_recursive(
1631            &dir,
1632            "subdir/subsubdir/emptydir",
1633            fio::PERM_READABLE | fio::PERM_WRITABLE,
1634        )
1635        .await
1636        .expect("failed to create subdir/subsubdir/emptydir");
1637        create_file(&dir, "a").await;
1638        create_file(&dir, "b").await;
1639        create_file(&dir, "subdir/a").await;
1640        create_file(&dir, "subdir/subsubdir/a").await;
1641        dir
1642    }
1643
1644    async fn create_file(dir: &fio::DirectoryProxy, path: &str) {
1645        open_file(
1646            dir,
1647            path,
1648            fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1649        )
1650        .await
1651        .unwrap_or_else(|e| panic!("failed to create {}: {:?}", path, e));
1652    }
1653
1654    fn build_direntry(name: &str, kind: DirentKind) -> DirEntry {
1655        DirEntry { name: name.to_string(), kind }
1656    }
1657
1658    // DirEntry
1659
1660    #[test]
1661    fn test_direntry_is_dir() {
1662        assert!(build_direntry("foo", DirentKind::Directory).is_dir());
1663
1664        // Negative test
1665        assert!(!build_direntry("foo", DirentKind::File).is_dir());
1666        assert!(!build_direntry("foo", DirentKind::Unknown).is_dir());
1667    }
1668
1669    #[test]
1670    fn test_direntry_chaining() {
1671        let parent = build_direntry("foo", DirentKind::Directory);
1672
1673        let child1 = build_direntry("bar", DirentKind::Directory);
1674        let chained1 = parent.chain(&child1);
1675        assert_eq!(&chained1.name, "foo/bar");
1676        assert_eq!(chained1.kind, DirentKind::Directory);
1677
1678        let child2 = build_direntry("baz", DirentKind::File);
1679        let chained2 = parent.chain(&child2);
1680        assert_eq!(&chained2.name, "foo/baz");
1681        assert_eq!(chained2.kind, DirentKind::File);
1682    }
1683
1684    // read_file
1685
1686    #[fasync::run_singlethreaded(test)]
1687    async fn test_read_file() {
1688        let contents = read_file(&open_pkg(), "/data/file").await.unwrap();
1689        assert_eq!(&contents, DATA_FILE_CONTENTS.as_bytes());
1690    }
1691
1692    #[fasync::run_singlethreaded(test)]
1693    async fn test_read_file_to_string() {
1694        let contents = read_file_to_string(&open_pkg(), "/data/file").await.unwrap();
1695        assert_eq!(contents, DATA_FILE_CONTENTS);
1696    }
1697
1698    #[fasync::run_singlethreaded(test)]
1699    async fn test_read_missing_file() {
1700        let result = read_file(&open_pkg(), "/data/missing").await;
1701        assert_matches!(
1702            result,
1703            Err(ReadError::Open(OpenError::OpenError(zx_status::Status::NOT_FOUND)))
1704        );
1705        assert_matches!(result, Err(e) if e.is_not_found_error());
1706    }
1707
1708    #[fasync::run_singlethreaded(test)]
1709    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1710    async fn atomic_write_file_writes_to_file() {
1711        let (_tmp, proxy) = open_data();
1712        let contents = b"atomic write contents";
1713        match atomic_write_file(&proxy, "atomic-file", contents).await {
1714            Ok(_) => (),
1715            // Don't assume that the filesystem running on the system supports unnamed temp files.
1716            Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1717            Err(err) => panic!("atomic_write_file failed: {err:?}"),
1718        };
1719
1720        let file_contents = read_file(&proxy, "atomic-file").await.unwrap();
1721        assert_eq!(file_contents, contents);
1722    }
1723
1724    #[fasync::run_singlethreaded(test)]
1725    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1726    async fn atomic_write_file_overwrites_existing_file() {
1727        let (_tmp, proxy) = open_data();
1728        let initial_contents = b"initial contents";
1729        match atomic_write_file(&proxy, "atomic-file", initial_contents).await {
1730            Ok(_) => (),
1731            // Don't assume that the filesystem running on the system supports unnamed temp files.
1732            Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1733            Err(err) => panic!("atomic_write_file failed: {err:?}"),
1734        };
1735
1736        let new_contents = b"new contents";
1737        atomic_write_file(&proxy, "atomic-file", new_contents).await.unwrap();
1738
1739        let file_contents = read_file(&proxy, "atomic-file").await.unwrap();
1740        assert_eq!(file_contents, new_contents);
1741    }
1742
1743    #[fasync::run_singlethreaded(test)]
1744    #[cfg(fuchsia_api_level_at_least = "HEAD")]
1745    async fn atomic_write_file_nested() {
1746        let (_tmp, proxy) = open_data();
1747        let contents = b"atomic write contents";
1748        create_directory(&proxy, "dir", fio::PERM_READABLE | fio::PERM_WRITABLE).await.unwrap();
1749        match atomic_write_file(&proxy, "dir/atomic-file", contents).await {
1750            Ok(_) => (),
1751            // Don't assume that the filesystem running on the system supports unnamed temp files.
1752            Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1753            Err(err) => panic!("atomic_write_file failed: {err:?}"),
1754        };
1755
1756        let file_contents = read_file(&proxy, "dir/atomic-file").await.unwrap();
1757        assert_eq!(file_contents, contents);
1758    }
1759}