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