Skip to main content

fuchsia_fs/
directory.rs

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