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