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