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