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