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