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