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::err_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::err_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::err_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::distr::{Alphanumeric, SampleString as _};
427 use rand::rngs::SmallRng;
428 let mut rng: SmallRng = rand::make_rng();
429
430 let flags = flags | fio::Flags::FLAG_MUST_CREATE;
431
432 loop {
433 let random_string = Alphanumeric.sample_string(&mut rng, 6);
434 let path = prefix.to_string() + &random_string;
435
436 match open_file(dir, &path, flags).await {
437 Ok(file) => return Ok((path, file)),
438 Err(OpenError::OpenError(zx_status::Status::ALREADY_EXISTS)) => {}
439 Err(err) => return Err(err),
440 }
441 }
442}
443
444async fn split_path<'a>(
447 dir: &fio::DirectoryProxy,
448 path: &'a str,
449) -> Result<(Option<fio::DirectoryProxy>, &'a str), OpenError> {
450 match path.rsplit_once('/') {
451 Some((parent, name)) => {
452 let proxy = open_directory(
453 dir,
454 parent,
455 fio::Flags::from_bits(fio::RW_STAR_DIR.bits()).unwrap(),
456 )
457 .await?;
458 Ok((Some(proxy), name))
459 }
460 None => Ok((None, path)),
461 }
462}
463
464pub async fn rename(dir: &fio::DirectoryProxy, src: &str, dst: &str) -> Result<(), RenameError> {
466 use flex_client::Event;
467 let (src_parent, src_filename) = split_path(dir, src).await?;
468 let src_parent = src_parent.as_ref().unwrap_or(dir);
469 let (dst_parent, dst_filename) = split_path(dir, dst).await?;
470 let dst_parent = dst_parent.as_ref().unwrap_or(dir);
471 let (status, dst_parent_dir_token) =
472 dst_parent.get_token().await.map_err(RenameError::SendGetTokenRequest)?;
473 zx_status::Status::ok(status).map_err(RenameError::GetTokenError)?;
474 let event = Event::from(dst_parent_dir_token.ok_or(RenameError::NoHandleError)?);
475 src_parent
476 .rename(src_filename, event, dst_filename)
477 .await
478 .map_err(RenameError::SendRenameRequest)?
479 .map_err(|s| RenameError::RenameError(zx_status::Status::err_from_raw(s)))
480}
481
482pub use fio::DirentType as DirentKind;
483
484#[derive(Clone, Eq, Ord, PartialOrd, PartialEq, Debug)]
486pub struct DirEntry {
487 pub name: String,
489
490 pub kind: DirentKind,
492}
493
494impl DirEntry {
495 fn root() -> Self {
496 Self { name: "".to_string(), kind: DirentKind::Directory }
497 }
498
499 fn is_dir(&self) -> bool {
500 self.kind == DirentKind::Directory
501 }
502
503 fn is_root(&self) -> bool {
504 self.is_dir() && self.name.is_empty()
505 }
506
507 fn chain(&self, subentry: &DirEntry) -> DirEntry {
508 if self.name.is_empty() {
509 DirEntry { name: subentry.name.clone(), kind: subentry.kind }
510 } else {
511 DirEntry { name: format!("{}/{}", self.name, subentry.name), kind: subentry.kind }
512 }
513 }
514}
515
516pub fn readdir_recursive_filtered<'a, ResultFn, RecurseFn>(
523 dir: &'a fio::DirectoryProxy,
524 timeout: Option<MonotonicDuration>,
525 results_filter: ResultFn,
526 recurse_filter: RecurseFn,
527) -> BoxStream<'a, Result<DirEntry, RecursiveEnumerateError>>
528where
529 ResultFn: Fn(&DirEntry, Option<&Vec<DirEntry>>) -> bool + Send + Sync + Copy + 'a,
530 RecurseFn: Fn(&DirEntry) -> bool + Send + Sync + Copy + 'a,
531{
532 let mut pending = VecDeque::new();
533 pending.push_back(DirEntry::root());
534 let results: VecDeque<DirEntry> = VecDeque::new();
535
536 stream::unfold((results, pending), move |(mut results, mut pending)| {
537 async move {
538 loop {
539 if !results.is_empty() {
541 let result = results.pop_front().unwrap();
542 return Some((Ok(result), (results, pending)));
543 }
544
545 if pending.is_empty() {
548 return None;
549 }
550
551 let dir_entry = pending.pop_front().unwrap();
553
554 let sub_dir;
555 let dir_ref = if dir_entry.is_root() {
556 dir
557 } else {
558 match open_directory_async(dir, &dir_entry.name, fio::Flags::empty()) {
559 Ok(dir) => {
560 sub_dir = dir;
561 &sub_dir
562 }
563 Err(err) => {
564 let error = RecursiveEnumerateError::Open { name: dir_entry.name, err };
565 return Some((Err(error), (results, pending)));
566 }
567 }
568 };
569
570 let readdir_result = match timeout {
571 Some(timeout_duration) => readdir_with_timeout(dir_ref, timeout_duration).await,
572 None => readdir(&dir_ref).await,
573 };
574 let subentries = match readdir_result {
575 Ok(subentries) => subentries,
576 Err(EnumerateError::Timeout) => {
578 return Some((Err(RecursiveEnumerateError::Timeout), (results, pending)));
579 }
580 Err(err) => {
581 let error =
582 Err(RecursiveEnumerateError::ReadDir { name: dir_entry.name, err });
583 return Some((error, (results, pending)));
584 }
585 };
586
587 if subentries.is_empty()
590 && results_filter(&dir_entry, Some(&subentries))
591 && !dir_entry.name.is_empty()
592 {
593 return Some((Ok(dir_entry), (results, pending)));
594 }
595
596 for subentry in subentries.into_iter() {
597 let subentry = dir_entry.chain(&subentry);
598 if subentry.is_dir() && recurse_filter(&subentry) {
599 pending.push_back(subentry.clone());
600 }
601 if results_filter(&subentry, None) {
602 results.push_back(subentry);
603 }
604 }
605 }
606 }
607 })
608 .boxed()
609}
610
611pub fn readdir_recursive(
616 dir: &fio::DirectoryProxy,
617 timeout: Option<MonotonicDuration>,
618) -> BoxStream<'_, Result<DirEntry, RecursiveEnumerateError>> {
619 readdir_recursive_filtered(
620 dir,
621 timeout,
622 |entry: &DirEntry, contents: Option<&Vec<DirEntry>>| {
623 !entry.is_dir() || (contents.is_some() && contents.unwrap().is_empty())
626 },
627 |_| true,
628 )
629}
630
631async fn readdir_inner(
632 dir: &fio::DirectoryProxy,
633 include_dot: bool,
634) -> Result<Vec<DirEntry>, EnumerateError> {
635 let status = dir.rewind().await.map_err(|e| EnumerateError::Fidl("rewind", e))?;
636 zx_status::Status::ok(status).map_err(EnumerateError::Rewind)?;
637
638 let mut entries = vec![];
639
640 loop {
641 let (status, buf) = dir
642 .read_dirents(fio::MAX_BUF)
643 .await
644 .map_err(|e| EnumerateError::Fidl("read_dirents", e))?;
645 zx_status::Status::ok(status).map_err(EnumerateError::ReadDirents)?;
646
647 if buf.is_empty() {
648 break;
649 }
650
651 for entry in parse_dir_entries(&buf) {
652 let entry = entry.map_err(EnumerateError::DecodeDirent)?;
653 if include_dot || entry.name != "." {
654 entries.push(entry);
655 }
656 }
657 }
658
659 entries.sort_unstable();
660
661 Ok(entries)
662}
663
664pub async fn readdir_inclusive(dir: &fio::DirectoryProxy) -> Result<Vec<DirEntry>, EnumerateError> {
667 readdir_inner(dir, true).await
668}
669
670pub async fn readdir(dir: &fio::DirectoryProxy) -> Result<Vec<DirEntry>, EnumerateError> {
673 readdir_inner(dir, false).await
674}
675
676pub async fn readdir_with_timeout(
680 dir: &fio::DirectoryProxy,
681 timeout: MonotonicDuration,
682) -> Result<Vec<DirEntry>, EnumerateError> {
683 readdir(&dir).on_timeout(timeout.after_now(), || Err(EnumerateError::Timeout)).await
684}
685
686pub async fn dir_contains(dir: &fio::DirectoryProxy, name: &str) -> Result<bool, EnumerateError> {
688 Ok(readdir(&dir).await?.iter().any(|e| e.name == name))
689}
690
691pub async fn dir_contains_with_timeout(
696 dir: &fio::DirectoryProxy,
697 name: &str,
698 timeout: MonotonicDuration,
699) -> Result<bool, EnumerateError> {
700 Ok(readdir_with_timeout(&dir, timeout).await?.iter().any(|e| e.name == name))
701}
702
703pub fn parse_dir_entries(mut buf: &[u8]) -> Vec<Result<DirEntry, DecodeDirentError>> {
708 #[derive(KnownLayout, FromBytes, Immutable, Unaligned)]
709 #[repr(C, packed)]
710 struct Dirent {
711 _ino: u64,
713 size: u8,
715 kind: u8,
717 }
720
721 let mut entries = vec![];
722
723 while !buf.is_empty() {
724 let Ok((dirent, rest)) = Ref::<_, Dirent>::from_prefix(buf) else {
725 entries.push(Err(DecodeDirentError::BufferOverrun));
726 return entries;
727 };
728
729 let entry = {
730 let size = usize::from(dirent.size);
732 if size > rest.len() {
733 entries.push(Err(DecodeDirentError::BufferOverrun));
734 return entries;
735 }
736
737 buf = &rest[size..];
739 match String::from_utf8(rest[..size].to_vec()) {
740 Ok(name) => Ok(DirEntry {
741 name,
742 kind: DirentKind::from_primitive(dirent.kind).unwrap_or(DirentKind::Unknown),
743 }),
744 Err(err) => Err(DecodeDirentError::InvalidUtf8(err.utf8_error())),
745 }
746 };
747
748 entries.push(entry);
749 }
750
751 entries
752}
753
754const DIR_FLAGS: fio::Flags = fio::Flags::empty()
755 .union(fio::Flags::PROTOCOL_DIRECTORY)
756 .union(PERM_READABLE)
757 .union(fio::Flags::PERM_INHERIT_WRITE);
758
759pub async fn remove_dir_recursive(
763 root_dir: &fio::DirectoryProxy,
764 name: &str,
765) -> Result<(), EnumerateError> {
766 let (dir, dir_server) = root_dir.domain().create_proxy::<fio::DirectoryMarker>();
767
768 #[cfg(fuchsia_api_level_at_least = "27")]
769 root_dir
770 .open(name, DIR_FLAGS, &fio::Options::default(), dir_server.into_channel())
771 .map_err(|e| EnumerateError::Fidl("open", e))?;
772 #[cfg(not(fuchsia_api_level_at_least = "27"))]
773 root_dir
774 .open3(name, DIR_FLAGS, &fio::Options::default(), dir_server.into_channel())
775 .map_err(|e| EnumerateError::Fidl("open", e))?;
776 remove_dir_contents(dir).await?;
777 root_dir
778 .unlink(
779 name,
780 &fio::UnlinkOptions {
781 flags: Some(fio::UnlinkFlags::MUST_BE_DIRECTORY),
782 ..Default::default()
783 },
784 )
785 .await
786 .map_err(|e| EnumerateError::Fidl("unlink", e))?
787 .map_err(|s| EnumerateError::Unlink(zx_status::Status::err_from_raw(s)))
788}
789
790fn remove_dir_contents(dir: fio::DirectoryProxy) -> BoxFuture<'static, Result<(), EnumerateError>> {
792 let fut = async move {
793 for dirent in readdir(&dir).await? {
794 match dirent.kind {
795 DirentKind::Directory => {
796 let (subdir, subdir_server) =
797 dir.domain().create_proxy::<fio::DirectoryMarker>();
798 #[cfg(fuchsia_api_level_at_least = "27")]
799 dir.open(
800 &dirent.name,
801 DIR_FLAGS,
802 &fio::Options::default(),
803 subdir_server.into_channel(),
804 )
805 .map_err(|e| EnumerateError::Fidl("open", e))?;
806 #[cfg(not(fuchsia_api_level_at_least = "27"))]
807 dir.open3(
808 &dirent.name,
809 DIR_FLAGS,
810 &fio::Options::default(),
811 subdir_server.into_channel(),
812 )
813 .map_err(|e| EnumerateError::Fidl("open", e))?;
814 remove_dir_contents(subdir).await?;
815 }
816 _ => {}
817 }
818 dir.unlink(&dirent.name, &fio::UnlinkOptions::default())
819 .await
820 .map_err(|e| EnumerateError::Fidl("unlink", e))?
821 .map_err(|s| EnumerateError::Unlink(zx_status::Status::err_from_raw(s)))?;
822 }
823 Ok(())
824 };
825 Box::pin(fut)
826}
827
828#[cfg(not(feature = "fdomain"))]
831pub async fn read_file_to_string(
832 parent: &fio::DirectoryProxy,
833 path: &str,
834) -> Result<String, crate::file::ReadError> {
835 let contents = read_file(parent, path).await?;
836 Ok(String::from_utf8(contents)?)
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842 use crate::directory::OpenError;
843 use crate::file::{ReadError, WriteError, write};
844 use assert_matches::assert_matches;
845 use fuchsia_async as fasync;
846 use futures::channel::oneshot;
847 use proptest::prelude::*;
848 use tempfile::TempDir;
849 use vfs::file::vmo::read_only;
850 use vfs::pseudo_directory;
851 use vfs::remote::remote_dir;
852
853 const DATA_FILE_CONTENTS: &str = "Hello World!\n";
854
855 #[cfg(target_os = "fuchsia")]
856 const LONG_DURATION: MonotonicDuration = MonotonicDuration::from_seconds(30);
857
858 #[cfg(not(target_os = "fuchsia"))]
859 const LONG_DURATION: MonotonicDuration = MonotonicDuration::from_secs(30);
860
861 proptest! {
862 #[test]
863 fn test_parse_dir_entries_does_not_crash(buf in prop::collection::vec(any::<u8>(), 0..200)) {
864 parse_dir_entries(&buf);
865 }
866 }
867
868 fn open_pkg() -> fio::DirectoryProxy {
869 open_in_namespace("/pkg", fio::PERM_READABLE).unwrap()
870 }
871
872 fn open_tmp() -> (TempDir, fio::DirectoryProxy) {
873 let tempdir = TempDir::new().expect("failed to create tmp dir");
874 let proxy = open_in_namespace(
875 tempdir.path().to_str().unwrap(),
876 fio::PERM_READABLE | fio::PERM_WRITABLE,
877 )
878 .unwrap();
879 (tempdir, proxy)
880 }
881
882 fn open_data() -> (TempDir, fio::DirectoryProxy) {
883 let tempdir = TempDir::new_in("/data").expect("failed to create tmp dir in /data");
884 let proxy = open_in_namespace(
885 tempdir.path().to_str().unwrap(),
886 fio::PERM_READABLE | fio::PERM_WRITABLE,
887 )
888 .unwrap();
889 (tempdir, proxy)
890 }
891
892 #[fasync::run_singlethreaded(test)]
895 async fn open_in_namespace_opens_real_dir() {
896 let exists = open_in_namespace("/pkg", fio::PERM_READABLE).unwrap();
897 assert_matches!(close(exists).await, Ok(()));
898 }
899
900 #[fasync::run_singlethreaded(test)]
901 async fn open_in_namespace_opens_fake_subdir_of_root_namespace_entry() {
902 let notfound = open_in_namespace("/pkg/fake", fio::PERM_READABLE).unwrap();
903 assert_matches!(close(notfound).await, Err(_));
905 }
906
907 #[fasync::run_singlethreaded(test)]
908 async fn open_in_namespace_rejects_fake_root_namespace_entry() {
909 let result = open_in_namespace("/fake", fio::PERM_READABLE);
910 assert_matches!(result, Err(OpenError::Namespace(zx_status::Status::NOT_FOUND)));
911 assert_matches!(result, Err(e) if e.is_not_found_error());
912 }
913
914 #[fasync::run_singlethreaded(test)]
917 async fn open_directory_async_opens_real_dir() {
918 let pkg = open_pkg();
919 let data = open_directory_async(&pkg, "data", fio::PERM_READABLE).unwrap();
920 close(data).await.unwrap();
921 }
922
923 #[fasync::run_singlethreaded(test)]
924 async fn open_directory_async_opens_fake_dir() {
925 let pkg = open_pkg();
926 let fake = open_directory_async(&pkg, "fake", fio::PERM_READABLE).unwrap();
927 assert_matches!(close(fake).await, Err(_));
929 }
930
931 #[fasync::run_singlethreaded(test)]
934 async fn open_directory_opens_real_dir() {
935 let pkg = open_pkg();
936 let data = open_directory(&pkg, "data", fio::PERM_READABLE).await.unwrap();
937 close(data).await.unwrap();
938 }
939
940 #[fasync::run_singlethreaded(test)]
941 async fn open_directory_rejects_fake_dir() {
942 let pkg = open_pkg();
943
944 let result = open_directory(&pkg, "fake", fio::PERM_READABLE).await;
945 assert_matches!(result, Err(OpenError::OpenError(zx_status::Status::NOT_FOUND)));
946 assert_matches!(result, Err(e) if e.is_not_found_error());
947 }
948
949 #[fasync::run_singlethreaded(test)]
950 async fn open_directory_rejects_file() {
951 let pkg = open_pkg();
952
953 assert_matches!(
954 open_directory(&pkg, "data/file", fio::PERM_READABLE).await,
955 Err(OpenError::OpenError(zx_status::Status::NOT_DIR))
956 );
957 }
958
959 #[fasync::run_singlethreaded(test)]
962 async fn create_directory_simple() {
963 let (_tmp, proxy) = open_tmp();
964 let dir = create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
965 crate::directory::close(dir).await.unwrap();
966 }
967
968 #[fasync::run_singlethreaded(test)]
969 async fn create_directory_add_file() {
970 let (_tmp, proxy) = open_tmp();
971 let dir =
972 create_directory(&proxy, "dir", fio::PERM_READABLE | fio::PERM_WRITABLE).await.unwrap();
973 let file = open_file(&dir, "data", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
974 .await
975 .unwrap();
976 crate::file::close(file).await.unwrap();
977 }
978
979 #[fasync::run_singlethreaded(test)]
980 async fn create_directory_existing_dir_opens() {
981 let (_tmp, proxy) = open_tmp();
982 let dir = create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
983 crate::directory::close(dir).await.unwrap();
984 create_directory(&proxy, "dir", fio::PERM_READABLE).await.unwrap();
985 }
986
987 #[fasync::run_singlethreaded(test)]
988 async fn create_directory_existing_dir_fails_if_must_create() {
989 let (_tmp, proxy) = open_tmp();
990 let dir =
991 create_directory(&proxy, "dir", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
992 .await
993 .unwrap();
994 crate::directory::close(dir).await.unwrap();
995 assert_matches!(
996 create_directory(&proxy, "dir", fio::Flags::FLAG_MUST_CREATE | fio::PERM_READABLE)
997 .await,
998 Err(_)
999 );
1000 }
1001
1002 #[fasync::run_singlethreaded(test)]
1005 async fn open_file_no_describe_opens_real_file() {
1006 let pkg = open_pkg();
1007 let file = open_file_async(&pkg, "data/file", fio::PERM_READABLE).unwrap();
1008 crate::file::close(file).await.unwrap();
1009 }
1010
1011 #[fasync::run_singlethreaded(test)]
1012 async fn open_file_no_describe_opens_fake_file() {
1013 let pkg = open_pkg();
1014 let fake = open_file_async(&pkg, "data/fake", fio::PERM_READABLE).unwrap();
1015 assert_matches!(crate::file::close(fake).await, Err(_));
1017 }
1018
1019 #[fasync::run_singlethreaded(test)]
1022 async fn open_file_opens_real_file() {
1023 let pkg = open_pkg();
1024 let file = open_file(&pkg, "data/file", fio::PERM_READABLE).await.unwrap();
1025 assert_eq!(
1026 file.seek(fio::SeekOrigin::End, 0).await.unwrap(),
1027 Ok(DATA_FILE_CONTENTS.len() as u64),
1028 );
1029 crate::file::close(file).await.unwrap();
1030 }
1031
1032 #[fasync::run_singlethreaded(test)]
1033 async fn open_file_rejects_fake_file() {
1034 let pkg = open_pkg();
1035
1036 let result = open_file(&pkg, "data/fake", fio::PERM_READABLE).await;
1037 assert_matches!(result, Err(OpenError::OpenError(zx_status::Status::NOT_FOUND)));
1038 assert_matches!(result, Err(e) if e.is_not_found_error());
1039 }
1040
1041 #[fasync::run_singlethreaded(test)]
1042 async fn open_file_rejects_dir() {
1043 let pkg = open_pkg();
1044
1045 assert_matches!(
1046 open_file(&pkg, "data", fio::PERM_READABLE).await,
1047 Err(OpenError::UnexpectedNodeKind {
1048 expected: node::Kind::File,
1049 actual: node::Kind::Directory,
1050 } | node::OpenError::OpenError(zx_status::Status::NOT_FILE))
1051 );
1052 }
1053
1054 #[fasync::run_singlethreaded(test)]
1055 async fn open_file_flags() {
1056 let tempdir = TempDir::new().expect("failed to create tmp dir");
1057 std::fs::write(tempdir.path().join("read_write"), "rw/read_write")
1058 .expect("failed to write file");
1059 let dir = crate::directory::open_in_namespace(
1060 tempdir.path().to_str().unwrap(),
1061 fio::PERM_READABLE | fio::PERM_WRITABLE,
1062 )
1063 .expect("could not open tmp dir");
1064 let example_dir = pseudo_directory! {
1065 "ro" => pseudo_directory! {
1066 "read_only" => read_only("ro/read_only"),
1067 },
1068 "rw" => remote_dir(dir)
1069 };
1070 let example_dir_proxy = vfs::directory::serve(
1071 example_dir,
1072 vfs::execution_scope::ExecutionScope::new(),
1073 fio::PERM_READABLE | fio::PERM_WRITABLE,
1074 );
1075
1076 for (file_name, flags, should_succeed) in vec![
1077 ("ro/read_only", fio::PERM_READABLE, true),
1078 ("ro/read_only", fio::PERM_READABLE | fio::PERM_WRITABLE, false),
1079 ("ro/read_only", fio::PERM_WRITABLE, false),
1080 ("rw/read_write", fio::PERM_READABLE, true),
1081 ("rw/read_write", fio::PERM_READABLE | fio::PERM_WRITABLE, true),
1082 ("rw/read_write", fio::PERM_WRITABLE, true),
1083 ] {
1084 let file = open_file_async(&example_dir_proxy, file_name, flags).unwrap();
1087 match (should_succeed, file.query().await) {
1088 (true, Ok(_)) => (),
1089 (false, Err(_)) => continue,
1090 (true, Err(e)) => {
1091 panic!("failed to open when expected success, couldn't describe: {:?}", e)
1092 }
1093 (false, Ok(d)) => {
1094 panic!("successfully opened when expected failure, could describe: {:?}", d)
1095 }
1096 }
1097 if flags.intersects(fio::Flags::PERM_READ_BYTES) {
1098 assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
1099 }
1100 if flags.intersects(fio::Flags::PERM_WRITE_BYTES) {
1101 let _ = file.seek(fio::SeekOrigin::Start, 0).await.expect("Seek failed!");
1102 let _: u64 = file
1103 .write(file_name.as_bytes())
1104 .await
1105 .unwrap()
1106 .map_err(zx_status::Status::err_from_raw)
1107 .unwrap();
1108 }
1109 crate::file::close(file).await.unwrap();
1110
1111 match open_file(&example_dir_proxy, file_name, flags).await {
1114 Ok(file) if should_succeed => {
1115 if flags.intersects(fio::Flags::PERM_READ_BYTES) {
1116 assert_eq!(crate::file::read_to_string(&file).await.unwrap(), file_name);
1117 }
1118 if flags.intersects(fio::Flags::PERM_WRITE_BYTES) {
1119 let _ = file.seek(fio::SeekOrigin::Start, 0).await.expect("Seek failed!");
1120 let _: u64 = file
1121 .write(file_name.as_bytes())
1122 .await
1123 .unwrap()
1124 .map_err(zx_status::Status::err_from_raw)
1125 .unwrap();
1126 }
1127 crate::file::close(file).await.unwrap();
1128 }
1129 Ok(_) => {
1130 panic!("successfully opened when expected failure: {:?}", (file_name, flags))
1131 }
1132 Err(e) if should_succeed => {
1133 panic!("failed to open when expected success: {:?}", (e, file_name, flags))
1134 }
1135 Err(_) => {}
1136 }
1137 }
1138 }
1139
1140 #[fasync::run_singlethreaded(test)]
1143 async fn open_node_opens_real_node() {
1144 let pkg = open_pkg();
1145 let node = open_node(&pkg, "data", fio::PERM_READABLE).await.unwrap();
1146 crate::node::close(node).await.unwrap();
1147 }
1148
1149 #[fasync::run_singlethreaded(test)]
1150 async fn open_node_opens_fake_node() {
1151 let pkg = open_pkg();
1152 assert_matches!(open_node(&pkg, "fake", fio::PERM_READABLE).await, Err(_));
1154 }
1155
1156 #[fasync::run_singlethreaded(test)]
1159 async fn create_randomly_named_file_simple() {
1160 let (_tmp, proxy) = open_tmp();
1161 let (path, file) =
1162 create_randomly_named_file(&proxy, "prefix", fio::PERM_WRITABLE).await.unwrap();
1163 assert!(path.starts_with("prefix"));
1164 crate::file::close(file).await.unwrap();
1165 }
1166
1167 #[fasync::run_singlethreaded(test)]
1168 async fn create_randomly_named_file_subdir() {
1169 let (_tmp, proxy) = open_tmp();
1170 let _subdir = create_directory(&proxy, "subdir", fio::PERM_WRITABLE).await.unwrap();
1171 let (path, file) =
1172 create_randomly_named_file(&proxy, "subdir/file", fio::PERM_WRITABLE).await.unwrap();
1173 assert!(path.starts_with("subdir/file"));
1174 crate::file::close(file).await.unwrap();
1175 }
1176
1177 #[fasync::run_singlethreaded(test)]
1178 async fn create_randomly_named_file_no_prefix() {
1179 let (_tmp, proxy) = open_tmp();
1180 let (_path, file) =
1181 create_randomly_named_file(&proxy, "", fio::PERM_READABLE | fio::PERM_WRITABLE)
1182 .await
1183 .unwrap();
1184 crate::file::close(file).await.unwrap();
1185 }
1186
1187 #[fasync::run_singlethreaded(test)]
1188 async fn create_randomly_named_file_error() {
1189 let pkg = open_pkg();
1190 assert_matches!(create_randomly_named_file(&pkg, "", fio::Flags::empty()).await, Err(_));
1191 }
1192
1193 #[fasync::run_singlethreaded(test)]
1196 async fn rename_simple() {
1197 let (tmp, proxy) = open_tmp();
1198 let (path, file) =
1199 create_randomly_named_file(&proxy, "", fio::PERM_WRITABLE).await.unwrap();
1200 crate::file::close(file).await.unwrap();
1201 rename(&proxy, &path, "new_path").await.unwrap();
1202 assert!(!tmp.path().join(path).exists());
1203 assert!(tmp.path().join("new_path").exists());
1204 }
1205
1206 #[fasync::run_singlethreaded(test)]
1207 async fn rename_with_subdir() {
1208 let (tmp, proxy) = open_tmp();
1209 let _subdir1 = create_directory(&proxy, "subdir1", fio::PERM_WRITABLE).await.unwrap();
1210 let _subdir2 = create_directory(&proxy, "subdir2", fio::PERM_WRITABLE).await.unwrap();
1211 let (path, file) =
1212 create_randomly_named_file(&proxy, "subdir1/file", fio::PERM_WRITABLE).await.unwrap();
1213 crate::file::close(file).await.unwrap();
1214 rename(&proxy, &path, "subdir2/file").await.unwrap();
1215 assert!(!tmp.path().join(path).exists());
1216 assert!(tmp.path().join("subdir2/file").exists());
1217 }
1218
1219 #[fasync::run_singlethreaded(test)]
1220 async fn rename_directory() {
1221 let (tmp, proxy) = open_tmp();
1222 let dir = create_directory(&proxy, "dir", fio::PERM_WRITABLE).await.unwrap();
1223 close(dir).await.unwrap();
1224 rename(&proxy, "dir", "dir2").await.unwrap();
1225 assert!(!tmp.path().join("dir").exists());
1226 assert!(tmp.path().join("dir2").exists());
1227 }
1228
1229 #[fasync::run_singlethreaded(test)]
1230 async fn rename_overwrite_existing_file() {
1231 let (tmp, proxy) = open_tmp();
1232 std::fs::write(tmp.path().join("foo"), b"foo").unwrap();
1233 std::fs::write(tmp.path().join("bar"), b"bar").unwrap();
1234 rename(&proxy, "foo", "bar").await.unwrap();
1235 assert!(!tmp.path().join("foo").exists());
1236 assert_eq!(std::fs::read_to_string(tmp.path().join("bar")).unwrap(), "foo");
1237 }
1238
1239 #[fasync::run_singlethreaded(test)]
1240 async fn rename_non_existing_src_fails() {
1241 let (tmp, proxy) = open_tmp();
1242 assert_matches!(
1243 rename(&proxy, "foo", "bar").await,
1244 Err(RenameError::RenameError(zx_status::Status::NOT_FOUND))
1245 );
1246 assert!(!tmp.path().join("foo").exists());
1247 assert!(!tmp.path().join("bar").exists());
1248 }
1249
1250 #[fasync::run_singlethreaded(test)]
1251 async fn rename_to_non_existing_subdir_fails() {
1252 let (tmp, proxy) = open_tmp();
1253 std::fs::write(tmp.path().join("foo"), b"foo").unwrap();
1254 assert_matches!(
1255 rename(&proxy, "foo", "bar/foo").await,
1256 Err(RenameError::OpenError(OpenError::OpenError(zx_status::Status::NOT_FOUND)))
1257 );
1258 assert!(tmp.path().join("foo").exists());
1259 assert!(!tmp.path().join("bar/foo").exists());
1260 }
1261
1262 #[fasync::run_singlethreaded(test)]
1263 async fn rename_root_path_fails() {
1264 let (tmp, proxy) = open_tmp();
1265 assert_matches!(
1266 rename(&proxy, "/foo", "bar").await,
1267 Err(RenameError::OpenError(OpenError::OpenError(zx_status::Status::INVALID_ARGS)))
1268 );
1269 assert!(!tmp.path().join("bar").exists());
1270 }
1271
1272 #[test]
1275 fn test_parse_dir_entries_rejects_invalid_utf8() {
1276 #[rustfmt::skip]
1277 let buf = &[
1278 1, 0, 0, 0, 0, 0, 0, 0,
1281 1,
1283 fio::DirentType::File.into_primitive(),
1285 0x80,
1287 2, 0, 0, 0, 0, 0, 0, 0,
1290 4,
1292 fio::DirentType::File.into_primitive(),
1294 'o' as u8, 'k' as u8, 'a' as u8, 'y' as u8,
1296 ];
1297
1298 #[allow(unknown_lints, invalid_from_utf8)]
1299 let expected_err = std::str::from_utf8(&[0x80]).unwrap_err();
1300
1301 assert_eq!(
1302 parse_dir_entries(buf),
1303 vec![
1304 Err(DecodeDirentError::InvalidUtf8(expected_err)),
1305 Ok(DirEntry { name: "okay".to_string(), kind: DirentKind::File })
1306 ]
1307 );
1308 }
1309
1310 #[test]
1311 fn test_parse_dir_entries_overrun() {
1312 #[rustfmt::skip]
1313 let buf = &[
1314 0, 0, 0, 0, 0, 0, 0, 0,
1316 5,
1318 fio::DirentType::File.into_primitive(),
1320 't' as u8, 'e' as u8, 's' as u8, 't' as u8,
1322 ];
1323
1324 assert_eq!(parse_dir_entries(buf), vec![Err(DecodeDirentError::BufferOverrun)]);
1325 }
1326
1327 #[fasync::run_singlethreaded(test)]
1330 async fn test_readdir() {
1331 let dir = pseudo_directory! {
1332 "afile" => read_only(""),
1333 "zzz" => read_only(""),
1334 "subdir" => pseudo_directory! {
1335 "ignored" => read_only(""),
1336 },
1337 };
1338 let dir_proxy =
1339 vfs::directory::serve_read_only(dir, vfs::execution_scope::ExecutionScope::new());
1340
1341 for _ in 0..2 {
1343 let entries = readdir(&dir_proxy).await.expect("readdir failed");
1344 assert_eq!(
1345 entries,
1346 vec![
1347 build_direntry("afile", DirentKind::File),
1348 build_direntry("subdir", DirentKind::Directory),
1349 build_direntry("zzz", DirentKind::File),
1350 ]
1351 );
1352 }
1353 }
1354
1355 #[fasync::run_singlethreaded(test)]
1358 async fn test_dir_contains() {
1359 let dir = pseudo_directory! {
1360 "afile" => read_only(""),
1361 "zzz" => read_only(""),
1362 "subdir" => pseudo_directory! {
1363 "ignored" => read_only(""),
1364 },
1365 };
1366 let dir_proxy =
1367 vfs::directory::serve_read_only(dir, vfs::execution_scope::ExecutionScope::new());
1368
1369 for file in &["afile", "zzz", "subdir"] {
1370 assert!(dir_contains(&dir_proxy, file).await.unwrap());
1371 }
1372
1373 assert!(
1374 !dir_contains(&dir_proxy, "notin").await.expect("error checking if dir contains notin")
1375 );
1376 }
1377
1378 #[fasync::run_singlethreaded(test)]
1379 async fn test_dir_contains_with_timeout() {
1380 let tempdir = TempDir::new().expect("failed to create tmp dir");
1381 let dir = create_nested_dir(&tempdir).await;
1382 let first = dir_contains_with_timeout(&dir, "notin", LONG_DURATION)
1383 .await
1384 .expect("error checking dir contains notin");
1385 assert!(!first);
1386 let second = dir_contains_with_timeout(&dir, "a", LONG_DURATION)
1387 .await
1388 .expect("error checking dir contains a");
1389 assert!(second);
1390 }
1391
1392 #[fasync::run_singlethreaded(test)]
1395 async fn test_readdir_recursive() {
1396 let tempdir = TempDir::new().expect("failed to create tmp dir");
1397 let dir = create_nested_dir(&tempdir).await;
1398 for _ in 0..2 {
1400 let (tx, rx) = oneshot::channel();
1401 let clone_dir = clone(&dir).expect("clone dir");
1402 fasync::Task::spawn(async move {
1403 let entries = readdir_recursive(&clone_dir, None)
1404 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1405 .await
1406 .into_iter()
1407 .collect::<Result<Vec<_>, _>>()
1408 .expect("readdir_recursive failed");
1409 tx.send(entries).expect("sending entries failed");
1410 })
1411 .detach();
1412 let entries = rx.await.expect("receiving entries failed");
1413 assert_eq!(
1414 entries,
1415 vec![
1416 build_direntry("a", DirentKind::File),
1417 build_direntry("b", DirentKind::File),
1418 build_direntry("emptydir", DirentKind::Directory),
1419 build_direntry("subdir/a", DirentKind::File),
1420 build_direntry("subdir/subsubdir/a", DirentKind::File),
1421 build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1422 ]
1423 );
1424 }
1425 }
1426
1427 #[fasync::run_singlethreaded(test)]
1428 async fn test_readdir_recursive_timeout_expired() {
1429 let (dir, _server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
1433 let result = readdir_recursive(&dir, Some(zx::MonotonicDuration::from_nanos(0)))
1434 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1435 .await
1436 .into_iter()
1437 .collect::<Result<Vec<_>, _>>();
1438 assert!(result.is_err());
1439 }
1440
1441 #[fasync::run_singlethreaded(test)]
1442 async fn test_readdir_recursive_timeout() {
1443 let tempdir = TempDir::new().expect("failed to create tmp dir");
1444 let dir = create_nested_dir(&tempdir).await;
1445 let entries = readdir_recursive(&dir, Some(LONG_DURATION))
1446 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1447 .await
1448 .into_iter()
1449 .collect::<Result<Vec<_>, _>>()
1450 .expect("readdir_recursive failed");
1451 assert_eq!(
1452 entries,
1453 vec![
1454 build_direntry("a", DirentKind::File),
1455 build_direntry("b", DirentKind::File),
1456 build_direntry("emptydir", DirentKind::Directory),
1457 build_direntry("subdir/a", DirentKind::File),
1458 build_direntry("subdir/subsubdir/a", DirentKind::File),
1459 build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1460 ]
1461 );
1462 }
1463
1464 #[fasync::run_singlethreaded(test)]
1467 async fn test_remove_dir_recursive() {
1468 {
1469 let tempdir = TempDir::new().expect("failed to create tmp dir");
1470 let dir = create_nested_dir(&tempdir).await;
1471 remove_dir_recursive(&dir, "emptydir").await.expect("remove_dir_recursive failed");
1472 let entries = readdir_recursive(&dir, None)
1473 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1474 .await
1475 .into_iter()
1476 .collect::<Result<Vec<_>, _>>()
1477 .expect("readdir_recursive failed");
1478 assert_eq!(
1479 entries,
1480 vec![
1481 build_direntry("a", DirentKind::File),
1482 build_direntry("b", DirentKind::File),
1483 build_direntry("subdir/a", DirentKind::File),
1484 build_direntry("subdir/subsubdir/a", DirentKind::File),
1485 build_direntry("subdir/subsubdir/emptydir", DirentKind::Directory),
1486 ]
1487 );
1488 }
1489 {
1490 let tempdir = TempDir::new().expect("failed to create tmp dir");
1491 let dir = create_nested_dir(&tempdir).await;
1492 remove_dir_recursive(&dir, "subdir").await.expect("remove_dir_recursive failed");
1493 let entries = readdir_recursive(&dir, None)
1494 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1495 .await
1496 .into_iter()
1497 .collect::<Result<Vec<_>, _>>()
1498 .expect("readdir_recursive failed");
1499 assert_eq!(
1500 entries,
1501 vec![
1502 build_direntry("a", DirentKind::File),
1503 build_direntry("b", DirentKind::File),
1504 build_direntry("emptydir", DirentKind::Directory),
1505 ]
1506 );
1507 }
1508 {
1509 let tempdir = TempDir::new().expect("failed to create tmp dir");
1510 let dir = create_nested_dir(&tempdir).await;
1511 let subdir = open_directory(&dir, "subdir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1512 .await
1513 .expect("could not open subdir");
1514 remove_dir_recursive(&subdir, "subsubdir").await.expect("remove_dir_recursive failed");
1515 let entries = readdir_recursive(&dir, None)
1516 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1517 .await
1518 .into_iter()
1519 .collect::<Result<Vec<_>, _>>()
1520 .expect("readdir_recursive failed");
1521 assert_eq!(
1522 entries,
1523 vec![
1524 build_direntry("a", DirentKind::File),
1525 build_direntry("b", DirentKind::File),
1526 build_direntry("emptydir", DirentKind::Directory),
1527 build_direntry("subdir/a", DirentKind::File),
1528 ]
1529 );
1530 }
1531 {
1532 let tempdir = TempDir::new().expect("failed to create tmp dir");
1533 let dir = create_nested_dir(&tempdir).await;
1534 let subsubdir =
1535 open_directory(&dir, "subdir/subsubdir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1536 .await
1537 .expect("could not open subsubdir");
1538 remove_dir_recursive(&subsubdir, "emptydir")
1539 .await
1540 .expect("remove_dir_recursive failed");
1541 let entries = readdir_recursive(&dir, None)
1542 .collect::<Vec<Result<DirEntry, RecursiveEnumerateError>>>()
1543 .await
1544 .into_iter()
1545 .collect::<Result<Vec<_>, _>>()
1546 .expect("readdir_recursive failed");
1547 assert_eq!(
1548 entries,
1549 vec![
1550 build_direntry("a", DirentKind::File),
1551 build_direntry("b", DirentKind::File),
1552 build_direntry("emptydir", DirentKind::Directory),
1553 build_direntry("subdir/a", DirentKind::File),
1554 build_direntry("subdir/subsubdir/a", DirentKind::File),
1555 ]
1556 );
1557 }
1558 }
1559
1560 #[fasync::run_singlethreaded(test)]
1561 async fn test_remove_dir_recursive_errors() {
1562 {
1563 let tempdir = TempDir::new().expect("failed to create tmp dir");
1564 let dir = create_nested_dir(&tempdir).await;
1565 let res = remove_dir_recursive(&dir, "baddir").await;
1566 let res = res.expect_err("remove_dir did not fail");
1567 match res {
1568 EnumerateError::Fidl("rewind", fidl_error) if fidl_error.is_closed() => {}
1569 _ => panic!("unexpected error {:?}", res),
1570 }
1571 }
1572 {
1573 let tempdir = TempDir::new().expect("failed to create tmp dir");
1574 let dir = create_nested_dir(&tempdir).await;
1575 let res = remove_dir_recursive(&dir, ".").await;
1576 let expected: Result<(), EnumerateError> =
1577 Err(EnumerateError::Unlink(zx_status::Status::INVALID_ARGS));
1578 assert_eq!(format!("{:?}", res), format!("{:?}", expected));
1579 }
1580 }
1581
1582 #[fasync::run_singlethreaded(test)]
1585 async fn create_directory_recursive_test() {
1586 let tempdir = TempDir::new().unwrap();
1587
1588 let path = "path/to/example/dir";
1589 let file_name = "example_file_name";
1590 let data = "file contents";
1591
1592 let root_dir = open_in_namespace(
1593 tempdir.path().to_str().unwrap(),
1594 fio::PERM_READABLE | fio::PERM_WRITABLE,
1595 )
1596 .expect("open_in_namespace failed");
1597
1598 let sub_dir =
1599 create_directory_recursive(&root_dir, &path, fio::PERM_READABLE | fio::PERM_WRITABLE)
1600 .await
1601 .expect("create_directory_recursive failed");
1602 let file = open_file(
1603 &sub_dir,
1604 &file_name,
1605 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1606 )
1607 .await
1608 .expect("open_file failed");
1609
1610 write(&file, &data).await.expect("writing to the file failed");
1611
1612 let contents = std::fs::read_to_string(tempdir.path().join(path).join(file_name))
1613 .expect("read_to_string failed");
1614 assert_eq!(&contents, &data, "File contents did not match");
1615 }
1616
1617 async fn create_nested_dir(tempdir: &TempDir) -> fio::DirectoryProxy {
1618 let dir = open_in_namespace(
1619 tempdir.path().to_str().unwrap(),
1620 fio::PERM_READABLE | fio::PERM_WRITABLE,
1621 )
1622 .expect("could not open tmp dir");
1623 create_directory_recursive(&dir, "emptydir", fio::PERM_READABLE | fio::PERM_WRITABLE)
1624 .await
1625 .expect("failed to create emptydir");
1626 create_directory_recursive(
1627 &dir,
1628 "subdir/subsubdir/emptydir",
1629 fio::PERM_READABLE | fio::PERM_WRITABLE,
1630 )
1631 .await
1632 .expect("failed to create subdir/subsubdir/emptydir");
1633 create_file(&dir, "a").await;
1634 create_file(&dir, "b").await;
1635 create_file(&dir, "subdir/a").await;
1636 create_file(&dir, "subdir/subsubdir/a").await;
1637 dir
1638 }
1639
1640 async fn create_file(dir: &fio::DirectoryProxy, path: &str) {
1641 open_file(
1642 dir,
1643 path,
1644 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1645 )
1646 .await
1647 .unwrap_or_else(|e| panic!("failed to create {}: {:?}", path, e));
1648 }
1649
1650 fn build_direntry(name: &str, kind: DirentKind) -> DirEntry {
1651 DirEntry { name: name.to_string(), kind }
1652 }
1653
1654 #[test]
1657 fn test_direntry_is_dir() {
1658 assert!(build_direntry("foo", DirentKind::Directory).is_dir());
1659
1660 assert!(!build_direntry("foo", DirentKind::File).is_dir());
1662 assert!(!build_direntry("foo", DirentKind::Unknown).is_dir());
1663 }
1664
1665 #[test]
1666 fn test_direntry_chaining() {
1667 let parent = build_direntry("foo", DirentKind::Directory);
1668
1669 let child1 = build_direntry("bar", DirentKind::Directory);
1670 let chained1 = parent.chain(&child1);
1671 assert_eq!(&chained1.name, "foo/bar");
1672 assert_eq!(chained1.kind, DirentKind::Directory);
1673
1674 let child2 = build_direntry("baz", DirentKind::File);
1675 let chained2 = parent.chain(&child2);
1676 assert_eq!(&chained2.name, "foo/baz");
1677 assert_eq!(chained2.kind, DirentKind::File);
1678 }
1679
1680 #[fasync::run_singlethreaded(test)]
1683 async fn test_read_file() {
1684 let contents = read_file(&open_pkg(), "/data/file").await.unwrap();
1685 assert_eq!(&contents, DATA_FILE_CONTENTS.as_bytes());
1686 }
1687
1688 #[fasync::run_singlethreaded(test)]
1689 async fn test_read_file_to_string() {
1690 let contents = read_file_to_string(&open_pkg(), "/data/file").await.unwrap();
1691 assert_eq!(contents, DATA_FILE_CONTENTS);
1692 }
1693
1694 #[fasync::run_singlethreaded(test)]
1695 async fn test_read_missing_file() {
1696 let result = read_file(&open_pkg(), "/data/missing").await;
1697 assert_matches!(
1698 result,
1699 Err(ReadError::Open(OpenError::OpenError(zx_status::Status::NOT_FOUND)))
1700 );
1701 assert_matches!(result, Err(e) if e.is_not_found_error());
1702 }
1703
1704 #[fasync::run_singlethreaded(test)]
1705 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1706 async fn atomic_write_file_writes_to_file() {
1707 let (_tmp, proxy) = open_data();
1708 let contents = b"atomic write contents";
1709 match atomic_write_file(&proxy, "atomic-file", contents).await {
1710 Ok(_) => (),
1711 Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1713 Err(err) => panic!("atomic_write_file failed: {err:?}"),
1714 };
1715
1716 let file_contents = read_file(&proxy, "atomic-file").await.unwrap();
1717 assert_eq!(file_contents, contents);
1718 }
1719
1720 #[fasync::run_singlethreaded(test)]
1721 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1722 async fn atomic_write_file_overwrites_existing_file() {
1723 let (_tmp, proxy) = open_data();
1724 let initial_contents = b"initial contents";
1725 match atomic_write_file(&proxy, "atomic-file", initial_contents).await {
1726 Ok(_) => (),
1727 Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1729 Err(err) => panic!("atomic_write_file failed: {err:?}"),
1730 };
1731
1732 let new_contents = b"new contents";
1733 atomic_write_file(&proxy, "atomic-file", new_contents).await.unwrap();
1734
1735 let file_contents = read_file(&proxy, "atomic-file").await.unwrap();
1736 assert_eq!(file_contents, new_contents);
1737 }
1738
1739 #[fasync::run_singlethreaded(test)]
1740 #[cfg(fuchsia_api_level_at_least = "HEAD")]
1741 async fn atomic_write_file_nested() {
1742 let (_tmp, proxy) = open_data();
1743 let contents = b"atomic write contents";
1744 create_directory(&proxy, "dir", fio::PERM_READABLE | fio::PERM_WRITABLE).await.unwrap();
1745 match atomic_write_file(&proxy, "dir/atomic-file", contents).await {
1746 Ok(_) => (),
1747 Err(WriteError::Open(OpenError::OpenError(zx::Status::NOT_SUPPORTED))) => return,
1749 Err(err) => panic!("atomic_write_file failed: {err:?}"),
1750 };
1751
1752 let file_contents = read_file(&proxy, "dir/atomic-file").await.unwrap();
1753 assert_eq!(file_contents, contents);
1754 }
1755}