Skip to main content

fuchsia_fs/
directory.rs

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