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