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