1use crate::{
2 DirEntry, FileType, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, Result,
3 VirtualFile,
4};
5use bytes::{Buf, Bytes};
6use futures::future::BoxFuture;
7#[cfg(feature = "enable-serde")]
8use serde::{Deserialize, Serialize, de};
9use std::convert::TryInto;
10use std::fs;
11use std::io::{self, Seek};
12use std::path::{Component, Path, PathBuf};
13use std::pin::Pin;
14use std::sync::Arc;
15use std::task::{Context, Poll};
16use std::time::{SystemTime, UNIX_EPOCH};
17use tokio::fs as tfs;
18use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
19use tokio::runtime::Handle;
20
21#[derive(Debug, Clone)]
22#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
23pub struct FileSystem {
24 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))]
25 handle: Handle,
26 root: PathBuf,
27}
28
29#[allow(dead_code)]
30fn default_handle() -> Handle {
31 Handle::current()
32}
33
34pub fn canonicalize(path: &Path) -> Result<PathBuf> {
35 if !path.exists() {
36 return Err(FsError::InvalidInput);
37 }
38 dunce::canonicalize(path).map_err(Into::into)
39}
40
41pub fn normalize_path(path: &Path) -> PathBuf {
44 let mut components = path.components().peekable();
45 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
46 components.next();
47 PathBuf::from(c.as_os_str())
48 } else {
49 PathBuf::new()
50 };
51
52 for component in components {
53 match component {
54 Component::Prefix(..) => unreachable!(),
55 Component::RootDir => {
56 ret.push(component.as_os_str());
57 }
58 Component::CurDir => {}
59 Component::ParentDir => {
60 ret.pop();
61 }
62 Component::Normal(c) => {
63 ret.push(c);
64 }
65 }
66 }
67 ret
68}
69
70fn path_suffix_to_guest_absolute(stripped: &Path) -> PathBuf {
71 let mut stripped = stripped.to_string_lossy().into_owned();
72 if std::path::MAIN_SEPARATOR == '\\' {
73 stripped = stripped.replace('\\', "/");
74 }
75
76 PathBuf::from(format!("/{}", stripped.trim_start_matches('/')))
77}
78
79fn strip_host_root(root: &Path, target: &Path) -> Option<PathBuf> {
80 target
81 .strip_prefix(root)
82 .ok()
83 .map(path_suffix_to_guest_absolute)
84}
85
86fn host_root_relative_target(root: &Path, target: PathBuf) -> PathBuf {
87 if root == Path::new("/") || !target.is_absolute() {
88 return target;
89 }
90
91 if let Some(target) = strip_host_root(root, &target) {
92 return target;
93 }
94
95 if let Ok(canonical_target) = canonicalize(&target)
96 && let Some(target) = strip_host_root(root, &canonical_target)
97 {
98 return target;
99 }
100
101 target
102}
103
104impl FileSystem {
105 pub fn new(handle: Handle, root: impl Into<PathBuf>) -> Result<Self> {
106 let root = canonicalize(&root.into())?;
107
108 Ok(FileSystem { handle, root })
109 }
110
111 fn prepare_path(&self, path: &Path) -> Result<PathBuf> {
112 let path = normalize_path(path);
113
114 if matches!(path.components().next(), Some(Component::Prefix(..))) {
115 return Err(FsError::InvalidInput);
116 }
117
118 if self.root != Path::new("/") && path.starts_with(&self.root) {
119 return Err(FsError::InvalidInput);
120 }
121
122 let path = path.strip_prefix("/").unwrap_or(&path);
123 let path = self.root.join(path);
124
125 debug_assert!(path.starts_with(&self.root));
126 Ok(path)
127 }
128}
129
130impl crate::FileSystem for FileSystem {
131 fn readlink(&self, path: &Path) -> Result<PathBuf> {
132 let path = self.prepare_path(path)?;
133
134 let target = fs::read_link(path)?;
135 Ok(host_root_relative_target(&self.root, target))
136 }
137
138 fn read_dir(&self, path: &Path) -> Result<ReadDir> {
139 let path = self.prepare_path(path)?;
140
141 let read_dir = fs::read_dir(path)?;
142 let mut data = read_dir
143 .map(|entry| {
144 let entry = entry?;
145
146 let path = entry
147 .path()
148 .strip_prefix(&self.root)
149 .map_err(|_| FsError::InvalidData)?
150 .to_owned();
151 let path = Path::new("/").join(path);
152
153 let metadata = fs::symlink_metadata(entry.path())?;
154
155 Ok(DirEntry {
156 path,
157 metadata: Ok(metadata.try_into()?),
158 })
159 })
160 .collect::<std::result::Result<Vec<DirEntry>, io::Error>>()
161 .map_err::<FsError, _>(Into::into)?;
162 data.sort_by(|a, b| a.path.file_name().cmp(&b.path.file_name()));
163 Ok(ReadDir::new(data))
164 }
165
166 fn create_dir(&self, path: &Path) -> Result<()> {
167 let path = self.prepare_path(path)?;
168
169 if path.parent().is_none() {
170 return Err(FsError::BaseNotDirectory);
171 }
172
173 fs::create_dir(path).map_err(Into::into)
174 }
175
176 fn remove_dir(&self, path: &Path) -> Result<()> {
177 let path = self.prepare_path(path)?;
178
179 if path.parent().is_none() {
180 return Err(FsError::BaseNotDirectory);
181 }
182
183 if path.is_dir()
186 && fs::read_dir(&path)
187 .map(|mut s| s.next().is_some())
188 .unwrap_or(false)
189 {
190 return Err(FsError::DirectoryNotEmpty);
191 }
192 fs::remove_dir(path).map_err(Into::into)
193 }
194
195 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
196 Box::pin(async move {
197 use filetime::{FileTime, set_file_mtime};
198 let norm_from = normalize_path(from);
199 let norm_to = normalize_path(to);
200
201 if norm_from.parent().is_none() {
202 return Err(FsError::BaseNotDirectory);
203 }
204 if norm_to.parent().is_none() {
205 return Err(FsError::BaseNotDirectory);
206 }
207
208 let from = self.prepare_path(from)?;
209 let to = self.prepare_path(to)?;
210
211 if !from.exists() {
212 return Err(FsError::EntryNotFound);
213 }
214 let from_parent = from.parent().unwrap();
215 let to_parent = to.parent().unwrap();
216 if !from_parent.exists() {
217 return Err(FsError::EntryNotFound);
218 }
219 if !to_parent.exists() {
220 return Err(FsError::EntryNotFound);
221 }
222 let result = if from_parent != to_parent {
223 let _ = std::fs::create_dir_all(to_parent);
224 if from.is_dir() {
225 fs_extra::move_items(
226 &[&from],
227 &to,
228 &fs_extra::dir::CopyOptions {
229 copy_inside: true,
230 ..Default::default()
231 },
232 )
233 .map(|_| ())
234 .map_err(|_| FsError::UnknownError)?;
235 let _ = fs_extra::remove_items(&[&from]);
236 Ok(())
237 } else {
238 fs::copy(&from, &to).map(|_| ()).map_err(FsError::from)?;
239 fs::remove_file(&from).map(|_| ()).map_err(Into::into)
240 }
241 } else {
242 fs::rename(&from, &to).map_err(Into::into)
243 };
244 let _ = set_file_mtime(&to, FileTime::now()).map(|_| ());
245 result
246 })
247 }
248
249 fn remove_file(&self, path: &Path) -> Result<()> {
250 let path = self.prepare_path(path)?;
251
252 if path.parent().is_none() {
253 return Err(FsError::BaseNotDirectory);
254 }
255
256 fs::remove_file(path).map_err(Into::into)
257 }
258
259 fn new_open_options(&self) -> OpenOptions<'_> {
260 OpenOptions::new(self)
261 }
262
263 fn metadata(&self, path: &Path) -> Result<Metadata> {
264 let path = self.prepare_path(path)?;
265
266 fs::metadata(path)
267 .and_then(TryInto::try_into)
268 .map_err(Into::into)
269 }
270
271 fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
272 let path = self.prepare_path(path)?;
273
274 fs::symlink_metadata(path)
275 .and_then(TryInto::try_into)
276 .map_err(Into::into)
277 }
278}
279
280impl TryInto<Metadata> for std::fs::Metadata {
281 type Error = io::Error;
282
283 fn try_into(self) -> std::result::Result<Metadata, Self::Error> {
284 let filetype = self.file_type();
285 let (char_device, block_device, socket, fifo) = {
286 #[cfg(unix)]
287 {
288 use std::os::unix::fs::FileTypeExt;
289 (
290 filetype.is_char_device(),
291 filetype.is_block_device(),
292 filetype.is_socket(),
293 filetype.is_fifo(),
294 )
295 }
296 #[cfg(not(unix))]
297 {
298 (false, false, false, false)
299 }
300 };
301
302 Ok(Metadata {
303 ft: FileType {
304 dir: filetype.is_dir(),
305 file: filetype.is_file(),
306 symlink: filetype.is_symlink(),
307 char_device,
308 block_device,
309 socket,
310 fifo,
311 },
312 accessed: self
313 .accessed()
314 .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
315 .map_or(0, |time| time.as_nanos() as u64),
316 created: self
317 .created()
318 .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
319 .map_or(0, |time| time.as_nanos() as u64),
320 modified: self
321 .modified()
322 .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
323 .map_or(0, |time| time.as_nanos() as u64),
324 len: self.len(),
325 })
326 }
327}
328
329impl crate::FileOpener for FileSystem {
330 fn open(
331 &self,
332 path: &Path,
333 conf: &OpenOptionsConfig,
334 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
335 let path = self.prepare_path(path)?;
336
337 let read = conf.read();
339 let write = conf.write();
340
341 let append = if conf.truncate { false } else { conf.append() };
347
348 let mut oo = fs::OpenOptions::new();
349 oo.read(conf.read())
350 .write(conf.write())
351 .create_new(conf.create_new())
352 .create(conf.create())
353 .append(append)
354 .truncate(conf.truncate())
355 .open(&path)
356 .map_err(Into::into)
357 .map(|file| {
358 Box::new(File::new(
359 self.handle.clone(),
360 file,
361 path.to_owned(),
362 read,
363 write,
364 append,
365 )) as Box<dyn VirtualFile + Send + Sync + 'static>
366 })
367 }
368}
369
370#[derive(Debug)]
372#[cfg_attr(feature = "enable-serde", derive(Serialize))]
373pub struct File {
374 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))]
375 handle: Handle,
376 #[cfg_attr(feature = "enable-serde", serde(skip))]
377 inner: tfs::File,
378 #[cfg_attr(feature = "enable-serde", serde(skip_serializing))]
379 inner_std: fs::File,
380 pub host_path: PathBuf,
381 #[cfg(feature = "enable-serde")]
382 flags: u16,
383}
384
385#[cfg(feature = "enable-serde")]
386impl<'de> Deserialize<'de> for File {
387 fn deserialize<D>(deserializer: D) -> std::result::Result<File, D::Error>
388 where
389 D: serde::Deserializer<'de>,
390 {
391 #[derive(Deserialize)]
392 #[serde(field_identifier, rename_all = "snake_case")]
393 enum Field {
394 HostPath,
395 Flags,
396 }
397
398 struct FileVisitor;
399
400 impl<'de> de::Visitor<'de> for FileVisitor {
401 type Value = File;
402
403 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
404 formatter.write_str("struct File")
405 }
406
407 fn visit_seq<V>(self, mut seq: V) -> std::result::Result<Self::Value, V::Error>
408 where
409 V: de::SeqAccess<'de>,
410 {
411 let host_path = seq
412 .next_element()?
413 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
414 let flags = seq
415 .next_element()?
416 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
417 let inner = fs::OpenOptions::new()
418 .read(flags & File::READ != 0)
419 .write(flags & File::WRITE != 0)
420 .append(flags & File::APPEND != 0)
421 .open(&host_path)
422 .map_err(|_| de::Error::custom("Could not open file on this system"))?;
423 Ok(File {
424 handle: Handle::current(),
425 inner: tokio::fs::File::from_std(inner.try_clone().unwrap()),
426 inner_std: inner,
427 host_path,
428 flags,
429 })
430 }
431
432 fn visit_map<V>(self, mut map: V) -> std::result::Result<Self::Value, V::Error>
433 where
434 V: de::MapAccess<'de>,
435 {
436 let mut host_path = None;
437 let mut flags = None;
438 while let Some(key) = map.next_key()? {
439 match key {
440 Field::HostPath => {
441 if host_path.is_some() {
442 return Err(de::Error::duplicate_field("host_path"));
443 }
444 host_path = Some(map.next_value()?);
445 }
446 Field::Flags => {
447 if flags.is_some() {
448 return Err(de::Error::duplicate_field("flags"));
449 }
450 flags = Some(map.next_value()?);
451 }
452 }
453 }
454 let host_path = host_path.ok_or_else(|| de::Error::missing_field("host_path"))?;
455 let flags = flags.ok_or_else(|| de::Error::missing_field("flags"))?;
456 let inner = fs::OpenOptions::new()
457 .read(flags & File::READ != 0)
458 .write(flags & File::WRITE != 0)
459 .append(flags & File::APPEND != 0)
460 .open(&host_path)
461 .map_err(|_| de::Error::custom("Could not open file on this system"))?;
462 Ok(File {
463 handle: Handle::current(),
464 inner: tokio::fs::File::from_std(inner.try_clone().unwrap()),
465 inner_std: inner,
466 host_path,
467 flags,
468 })
469 }
470 }
471
472 const FIELDS: &[&str] = &["host_path", "flags"];
473 deserializer.deserialize_struct("File", FIELDS, FileVisitor)
474 }
475}
476
477impl File {
478 const READ: u16 = 1;
479 const WRITE: u16 = 2;
480 const APPEND: u16 = 4;
481
482 pub fn new(
484 handle: Handle,
485 file: fs::File,
486 host_path: PathBuf,
487 read: bool,
488 write: bool,
489 append: bool,
490 ) -> Self {
491 let mut _flags = 0;
492
493 if read {
494 _flags |= Self::READ;
495 }
496
497 if write {
498 _flags |= Self::WRITE;
499 }
500
501 if append {
502 _flags |= Self::APPEND;
503 }
504
505 let async_file = tfs::File::from_std(file.try_clone().unwrap());
506 Self {
507 handle,
508 inner_std: file,
509 inner: async_file,
510 host_path,
511 #[cfg(feature = "enable-serde")]
512 flags: _flags,
513 }
514 }
515
516 fn metadata(&self) -> std::fs::Metadata {
517 self.inner_std.metadata().unwrap()
519 }
520}
521
522#[async_trait::async_trait]
524impl VirtualFile for File {
525 fn last_accessed(&self) -> u64 {
526 self.metadata()
527 .accessed()
528 .ok()
529 .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
530 .map(|ct| ct.as_nanos() as u64)
531 .unwrap_or(0)
532 }
533
534 fn last_modified(&self) -> u64 {
535 self.metadata()
536 .modified()
537 .ok()
538 .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
539 .map(|ct| ct.as_nanos() as u64)
540 .unwrap_or(0)
541 }
542
543 fn created_time(&self) -> u64 {
544 self.metadata()
545 .created()
546 .ok()
547 .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
548 .map(|ct| ct.as_nanos() as u64)
549 .unwrap_or(0)
550 }
551
552 fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
553 let atime = atime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
554 let mtime = mtime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
555
556 filetime::set_file_handle_times(&self.inner_std, atime, mtime)
557 .map_err(|_| crate::FsError::IOError)
558 }
559
560 fn size(&self) -> u64 {
561 self.metadata().len()
562 }
563
564 fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
565 fs::File::set_len(&self.inner_std, new_size).map_err(Into::into)
566 }
567
568 fn unlink(&mut self) -> Result<()> {
569 fs::remove_file(&self.host_path).map_err(Into::into)
570 }
571
572 fn get_special_fd(&self) -> Option<u32> {
573 None
574 }
575
576 fn poll_read_ready(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
577 let cursor = match self.inner_std.stream_position() {
578 Ok(a) => a,
579 Err(err) => return Poll::Ready(Err(err)),
580 };
581 let end = match self.inner_std.seek(io::SeekFrom::End(0)) {
582 Ok(a) => a,
583 Err(err) => return Poll::Ready(Err(err)),
584 };
585 let _ = self.inner_std.seek(io::SeekFrom::Start(cursor));
586
587 let remaining = end - cursor;
588 Poll::Ready(Ok(remaining as usize))
589 }
590
591 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
592 Poll::Ready(Ok(8192))
593 }
594}
595
596impl AsyncRead for File {
597 fn poll_read(
598 mut self: Pin<&mut Self>,
599 cx: &mut Context<'_>,
600 buf: &mut tokio::io::ReadBuf<'_>,
601 ) -> Poll<io::Result<()>> {
602 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
603 let inner = Pin::new(&mut self.inner);
604 inner.poll_read(cx, buf)
605 }
606}
607
608impl AsyncWrite for File {
609 fn poll_write(
610 mut self: Pin<&mut Self>,
611 cx: &mut Context<'_>,
612 buf: &[u8],
613 ) -> Poll<io::Result<usize>> {
614 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
615 let inner = Pin::new(&mut self.inner);
616 inner.poll_write(cx, buf)
617 }
618
619 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
620 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
621 let inner = Pin::new(&mut self.inner);
622 inner.poll_flush(cx)
623 }
624
625 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
626 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
627 let inner = Pin::new(&mut self.inner);
628 inner.poll_shutdown(cx)
629 }
630
631 fn poll_write_vectored(
632 mut self: Pin<&mut Self>,
633 cx: &mut Context<'_>,
634 bufs: &[io::IoSlice<'_>],
635 ) -> Poll<io::Result<usize>> {
636 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
637 let inner = Pin::new(&mut self.inner);
638 inner.poll_write_vectored(cx, bufs)
639 }
640
641 fn is_write_vectored(&self) -> bool {
642 self.inner.is_write_vectored()
643 }
644}
645
646impl AsyncSeek for File {
647 fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
648 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
649 let inner = Pin::new(&mut self.inner);
650 inner.start_seek(position)
651 }
652
653 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
654 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
655 let inner = Pin::new(&mut self.inner);
656 inner.poll_complete(cx)
657 }
658}
659
660impl Drop for File {
661 fn drop(&mut self) {
662 tracing::trace!(?self.host_path, "Closing host file");
663 }
664}
665
666#[derive(Debug)]
668#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
669pub struct Stdout {
670 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))]
671 handle: Handle,
672 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stdout"))]
673 inner: tokio::io::Stdout,
674}
675#[allow(dead_code)]
676fn default_stdout() -> tokio::io::Stdout {
677 tokio::io::stdout()
678}
679impl Default for Stdout {
680 fn default() -> Self {
681 Self {
682 handle: Handle::current(),
683 inner: tokio::io::stdout(),
684 }
685 }
686}
687
688const DEFAULT_BUF_SIZE_HINT: usize = 8 * 1024;
696
697#[async_trait::async_trait]
699impl VirtualFile for Stdout {
700 fn last_accessed(&self) -> u64 {
701 0
702 }
703
704 fn last_modified(&self) -> u64 {
705 0
706 }
707
708 fn created_time(&self) -> u64 {
709 0
710 }
711
712 fn size(&self) -> u64 {
713 0
714 }
715
716 fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
717 Ok(())
718 }
719
720 fn unlink(&mut self) -> Result<()> {
721 Ok(())
722 }
723
724 fn get_special_fd(&self) -> Option<u32> {
725 Some(1)
726 }
727
728 fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
729 Poll::Ready(Ok(0))
730 }
731
732 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
733 Poll::Ready(Ok(DEFAULT_BUF_SIZE_HINT))
734 }
735}
736
737impl AsyncRead for Stdout {
738 fn poll_read(
739 self: Pin<&mut Self>,
740 _cx: &mut Context<'_>,
741 _buf: &mut tokio::io::ReadBuf<'_>,
742 ) -> Poll<io::Result<()>> {
743 Poll::Ready(Err(io::Error::other("can not read from stdout")))
744 }
745}
746
747impl AsyncWrite for Stdout {
748 fn poll_write(
749 mut self: Pin<&mut Self>,
750 cx: &mut Context<'_>,
751 buf: &[u8],
752 ) -> Poll<io::Result<usize>> {
753 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
754 let inner = Pin::new(&mut self.inner);
755 inner.poll_write(cx, buf)
756 }
757
758 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
759 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
760 let inner = Pin::new(&mut self.inner);
761 inner.poll_flush(cx)
762 }
763
764 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
765 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
766 let inner = Pin::new(&mut self.inner);
767 inner.poll_shutdown(cx)
768 }
769
770 fn poll_write_vectored(
771 mut self: Pin<&mut Self>,
772 cx: &mut Context<'_>,
773 bufs: &[io::IoSlice<'_>],
774 ) -> Poll<io::Result<usize>> {
775 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
776 let inner = Pin::new(&mut self.inner);
777 inner.poll_write_vectored(cx, bufs)
778 }
779
780 fn is_write_vectored(&self) -> bool {
781 self.inner.is_write_vectored()
782 }
783}
784
785impl AsyncSeek for Stdout {
786 fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
787 Err(io::Error::other("can not seek stdout"))
788 }
789
790 fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
791 Poll::Ready(Err(io::Error::other("can not seek stdout")))
792 }
793}
794
795#[derive(Debug)]
797#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
798pub struct Stderr {
799 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))]
800 handle: Handle,
801 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stderr"))]
802 inner: tokio::io::Stderr,
803}
804#[allow(dead_code)]
805fn default_stderr() -> tokio::io::Stderr {
806 tokio::io::stderr()
807}
808impl Default for Stderr {
809 fn default() -> Self {
810 Self {
811 handle: Handle::current(),
812 inner: tokio::io::stderr(),
813 }
814 }
815}
816
817impl AsyncRead for Stderr {
818 fn poll_read(
819 self: Pin<&mut Self>,
820 _cx: &mut Context<'_>,
821 _buf: &mut tokio::io::ReadBuf<'_>,
822 ) -> Poll<io::Result<()>> {
823 Poll::Ready(Err(io::Error::other("can not read from stderr")))
824 }
825}
826
827impl AsyncWrite for Stderr {
828 fn poll_write(
829 mut self: Pin<&mut Self>,
830 cx: &mut Context<'_>,
831 buf: &[u8],
832 ) -> Poll<io::Result<usize>> {
833 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
834 let inner = Pin::new(&mut self.inner);
835 inner.poll_write(cx, buf)
836 }
837
838 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
839 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
840 let inner = Pin::new(&mut self.inner);
841 inner.poll_flush(cx)
842 }
843
844 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
845 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
846 let inner = Pin::new(&mut self.inner);
847 inner.poll_shutdown(cx)
848 }
849
850 fn poll_write_vectored(
851 mut self: Pin<&mut Self>,
852 cx: &mut Context<'_>,
853 bufs: &[io::IoSlice<'_>],
854 ) -> Poll<io::Result<usize>> {
855 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
856 let inner = Pin::new(&mut self.inner);
857 inner.poll_write_vectored(cx, bufs)
858 }
859
860 fn is_write_vectored(&self) -> bool {
861 self.inner.is_write_vectored()
862 }
863}
864
865impl AsyncSeek for Stderr {
866 fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
867 Err(io::Error::other("can not seek stderr"))
868 }
869
870 fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
871 Poll::Ready(Err(io::Error::other("can not seek stderr")))
872 }
873}
874
875#[async_trait::async_trait]
877impl VirtualFile for Stderr {
878 fn last_accessed(&self) -> u64 {
879 0
880 }
881
882 fn last_modified(&self) -> u64 {
883 0
884 }
885
886 fn created_time(&self) -> u64 {
887 0
888 }
889
890 fn size(&self) -> u64 {
891 0
892 }
893
894 fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
895 Ok(())
896 }
897
898 fn unlink(&mut self) -> Result<()> {
899 Ok(())
900 }
901
902 fn get_special_fd(&self) -> Option<u32> {
903 Some(2)
904 }
905
906 fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
907 Poll::Ready(Ok(0))
908 }
909
910 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
911 Poll::Ready(Ok(8192))
912 }
913}
914
915#[derive(Debug)]
917#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
918pub struct Stdin {
919 read_buffer: Arc<std::sync::Mutex<Option<Bytes>>>,
920 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_handle"))]
921 handle: Handle,
922 #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_stdin"))]
923 inner: tokio::io::Stdin,
924}
925#[allow(dead_code)]
926fn default_stdin() -> tokio::io::Stdin {
927 tokio::io::stdin()
928}
929impl Default for Stdin {
930 fn default() -> Self {
931 Self {
932 handle: Handle::current(),
933 read_buffer: Arc::new(std::sync::Mutex::new(None)),
934 inner: tokio::io::stdin(),
935 }
936 }
937}
938
939impl AsyncRead for Stdin {
940 fn poll_read(
941 mut self: Pin<&mut Self>,
942 cx: &mut Context<'_>,
943 buf: &mut tokio::io::ReadBuf<'_>,
944 ) -> Poll<io::Result<()>> {
945 let max_size = buf.remaining();
946 {
947 let mut read_buffer = self.read_buffer.lock().unwrap();
948 if let Some(read_buffer) = read_buffer.as_mut() {
949 let buf_len = read_buffer.len();
950 if buf_len > 0 {
951 let read = buf_len.min(max_size);
952 buf.put_slice(&read_buffer[..read]);
953 read_buffer.advance(read);
954 return Poll::Ready(Ok(()));
955 }
956 }
957 }
958
959 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
960 let inner = Pin::new(&mut self.inner);
961 inner.poll_read(cx, buf)
962 }
963}
964
965impl AsyncWrite for Stdin {
966 fn poll_write(
967 self: Pin<&mut Self>,
968 _cx: &mut Context<'_>,
969 _buf: &[u8],
970 ) -> Poll<io::Result<usize>> {
971 Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
972 }
973
974 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
975 Poll::Ready(Err(io::Error::other("can not flush stdin")))
976 }
977
978 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
979 Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
980 }
981
982 fn poll_write_vectored(
983 self: Pin<&mut Self>,
984 _cx: &mut Context<'_>,
985 _bufs: &[io::IoSlice<'_>],
986 ) -> Poll<io::Result<usize>> {
987 Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
988 }
989}
990
991impl AsyncSeek for Stdin {
992 fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
993 Err(io::Error::other("can not seek stdin"))
994 }
995
996 fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
997 Poll::Ready(Err(io::Error::other("can not seek stdin")))
998 }
999}
1000
1001#[async_trait::async_trait]
1003impl VirtualFile for Stdin {
1004 fn last_accessed(&self) -> u64 {
1005 0
1006 }
1007 fn last_modified(&self) -> u64 {
1008 0
1009 }
1010 fn created_time(&self) -> u64 {
1011 0
1012 }
1013 fn size(&self) -> u64 {
1014 0
1015 }
1016 fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
1017 Ok(())
1018 }
1019 fn unlink(&mut self) -> Result<()> {
1020 Ok(())
1021 }
1022 fn get_special_fd(&self) -> Option<u32> {
1023 Some(0)
1024 }
1025 fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
1026 {
1027 let read_buffer = self.read_buffer.lock().unwrap();
1028 if let Some(read_buffer) = read_buffer.as_ref() {
1029 let buf_len = read_buffer.len();
1030 if buf_len > 0 {
1031 return Poll::Ready(Ok(buf_len));
1032 }
1033 }
1034 }
1035
1036 let _guard = Handle::try_current().map_err(|_| self.handle.enter());
1037 let inner = Pin::new(&mut self.inner);
1038
1039 let mut buf = [0u8; 8192];
1040 let mut read_buf = ReadBuf::new(&mut buf[..]);
1041 match inner.poll_read(cx, &mut read_buf) {
1042 Poll::Pending => Poll::Pending,
1043 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
1044 Poll::Ready(Ok(())) => {
1045 let buf = read_buf.filled();
1046 let buf_len = buf.len();
1047
1048 let mut read_buffer = self.read_buffer.lock().unwrap();
1049 read_buffer.replace(Bytes::from(buf.to_vec()));
1050 Poll::Ready(Ok(buf_len))
1051 }
1052 }
1053 }
1054 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
1055 Poll::Ready(Ok(0))
1056 }
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use tempfile::TempDir;
1062 use tokio::runtime::Handle;
1063
1064 use super::FileSystem;
1065 use crate::FileSystem as FileSystemTrait;
1066 use crate::FsError;
1067 use std::path::Path;
1068
1069 #[tokio::test]
1070 async fn test_new_filesystem() {
1071 let temp = TempDir::new().unwrap();
1072 std::fs::write(temp.path().join("foo2.txt"), b"").unwrap();
1073
1074 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1075 assert!(
1076 fs.read_dir(Path::new("/")).is_ok(),
1077 "NativeFS can read root"
1078 );
1079 assert!(
1080 fs.new_open_options()
1081 .read(true)
1082 .open(Path::new("/foo2.txt"))
1083 .is_ok(),
1084 "created foo2.txt"
1085 );
1086 }
1087
1088 #[tokio::test]
1089 async fn test_create_dir() {
1090 let temp: TempDir = TempDir::new().unwrap();
1091 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1092
1093 assert_eq!(
1094 fs.create_dir(Path::new("../")),
1095 Err(FsError::AlreadyExists),
1096 "creating a directory out of bounds",
1097 );
1098
1099 assert_eq!(
1100 fs.create_dir(Path::new("/foo")),
1101 Ok(()),
1102 "creating a directory",
1103 );
1104
1105 assert!(
1106 temp.path().join("foo").exists(),
1107 "foo dir exists in host_fs"
1108 );
1109
1110 let cur_dir = read_dir_names(&fs, "/");
1111
1112 if !cur_dir.contains(&"foo".to_string()) {
1113 panic!("cur_dir does not contain foo: {cur_dir:#?}");
1114 }
1115
1116 assert!(
1117 cur_dir.contains(&"foo".to_string()),
1118 "the root is updated and well-defined"
1119 );
1120
1121 assert_eq!(
1122 fs.create_dir(Path::new("foo/bar")),
1123 Ok(()),
1124 "creating a sub-directory",
1125 );
1126
1127 assert!(
1128 temp.path().join("foo").join("bar").exists(),
1129 "foo dir exists in host_fs"
1130 );
1131
1132 let foo_dir = read_dir_names(&fs, Path::new("/foo"));
1133
1134 assert!(
1135 foo_dir.contains(&"bar".to_string()),
1136 "the foo directory is updated and well-defined"
1137 );
1138
1139 let bar_dir = read_dir_names(&fs, Path::new("/foo/bar"));
1140
1141 assert!(
1142 bar_dir.is_empty(),
1143 "the foo directory is updated and well-defined"
1144 );
1145 }
1146
1147 #[tokio::test]
1148 async fn test_remove_dir() {
1149 let temp: TempDir = TempDir::new().unwrap();
1150 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1151
1152 assert_eq!(
1153 fs.remove_dir(Path::new("/foo")),
1154 Err(FsError::EntryNotFound),
1155 "cannot remove a directory that doesn't exist",
1156 );
1157
1158 assert_eq!(
1159 fs.create_dir(Path::new("foo")),
1160 Ok(()),
1161 "creating a directory",
1162 );
1163
1164 assert_eq!(
1165 fs.create_dir(Path::new("foo/bar")),
1166 Ok(()),
1167 "creating a sub-directory",
1168 );
1169
1170 assert!(temp.path().join("foo/bar").exists(), "./foo/bar exists");
1171
1172 assert_eq!(
1173 fs.remove_dir(Path::new("foo")),
1174 Err(FsError::DirectoryNotEmpty),
1175 "removing a directory that has children",
1176 );
1177
1178 assert_eq!(
1179 fs.remove_dir(Path::new("foo/bar")),
1180 Ok(()),
1181 "removing a sub-directory",
1182 );
1183
1184 assert_eq!(
1185 fs.remove_dir(Path::new("foo")),
1186 Ok(()),
1187 "removing a directory",
1188 );
1189
1190 let cur_dir = read_dir_names(&fs, "/");
1191
1192 assert!(
1193 !cur_dir.contains(&"foo".to_string()),
1194 "the foo directory still exists"
1195 );
1196 }
1197
1198 fn read_dir_names(fs: &FileSystem, path: impl AsRef<Path>) -> Vec<String> {
1199 fs.read_dir(path.as_ref())
1200 .unwrap()
1201 .filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
1202 .collect::<Vec<_>>()
1203 }
1204
1205 #[tokio::test]
1206 async fn test_rename() {
1207 let temp: TempDir = TempDir::new().unwrap();
1208 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1209 std::fs::create_dir_all(temp.path().join("foo").join("qux")).unwrap();
1210 let foo = Path::new("foo");
1211 let bar = Path::new("bar");
1212 let foo_realpath = temp.path().join(foo);
1213 let bar_realpath = temp.path().join(bar);
1214
1215 assert_eq!(
1216 fs.rename(Path::new("/"), Path::new("/bar")).await,
1217 Err(FsError::BaseNotDirectory),
1218 "renaming a directory that has no parent",
1219 );
1220 assert_eq!(
1221 fs.rename(Path::new("/foo"), Path::new("/")).await,
1222 Err(FsError::BaseNotDirectory),
1223 "renaming to a directory that has no parent",
1224 );
1225
1226 assert_eq!(
1227 fs.rename(foo, &foo.join("bar").join("baz"),).await,
1228 Err(FsError::EntryNotFound),
1229 "renaming to a directory that has parent that doesn't exist",
1230 );
1231
1232 #[cfg(not(target_os = "windows"))]
1234 assert_eq!(fs.create_dir(bar), Ok(()));
1235
1236 assert_eq!(
1237 fs.rename(foo, bar).await,
1238 Ok(()),
1239 "renaming to a directory that has parent that exists",
1240 );
1241
1242 assert!(
1243 fs.new_open_options()
1244 .write(true)
1245 .create_new(true)
1246 .open(bar.join("hello1.txt"))
1247 .is_ok(),
1248 "creating a new file (`hello1.txt`)",
1249 );
1250 assert!(
1251 fs.new_open_options()
1252 .write(true)
1253 .create_new(true)
1254 .open(bar.join("hello2.txt"))
1255 .is_ok(),
1256 "creating a new file (`hello2.txt`)",
1257 );
1258
1259 let cur_dir = read_dir_names(&fs, Path::new("/"));
1260
1261 assert!(
1262 !cur_dir.contains(&"foo".to_string()),
1263 "the foo directory still exists"
1264 );
1265
1266 assert!(
1267 cur_dir.contains(&"bar".to_string()),
1268 "the bar directory still exists"
1269 );
1270
1271 let bar_dir = read_dir_names(&fs, bar);
1272
1273 if !bar_dir.contains(&"qux".to_string()) {
1274 println!("qux does not exist: {bar_dir:?}")
1275 }
1276
1277 let qux_dir = read_dir_names(&fs, bar.join("qux"));
1278
1279 assert!(qux_dir.is_empty(), "the qux directory is empty");
1280
1281 assert!(
1282 bar_realpath.join("hello1.txt").exists(),
1283 "the /bar/hello1.txt file exists"
1284 );
1285
1286 assert!(
1287 bar_realpath.join("hello2.txt").exists(),
1288 "the /bar/hello2.txt file exists"
1289 );
1290
1291 assert_eq!(fs.create_dir(foo), Ok(()), "create ./foo again");
1292
1293 assert_eq!(
1294 fs.rename(&bar.join("hello2.txt"), &foo.join("world2.txt"))
1295 .await,
1296 Ok(()),
1297 "renaming (and moving) a file",
1298 );
1299
1300 assert_eq!(
1301 fs.rename(foo, &bar.join("baz")).await,
1302 Ok(()),
1303 "renaming a directory",
1304 );
1305
1306 assert_eq!(
1307 fs.rename(&bar.join("hello1.txt"), &bar.join("world1.txt"))
1308 .await,
1309 Ok(()),
1310 "renaming a file (in the same directory)",
1311 );
1312
1313 assert!(bar_realpath.exists(), "./bar exists");
1314 assert!(bar_realpath.join("baz").exists(), "./bar/baz exists");
1315 assert!(!foo_realpath.exists(), "foo does not exist anymore");
1316 assert!(
1317 bar_realpath.join("baz/world2.txt").exists(),
1318 "/bar/baz/world2.txt exists"
1319 );
1320 assert!(
1321 bar_realpath.join("world1.txt").exists(),
1322 "/bar/world1.txt (ex hello1.txt) exists"
1323 );
1324 assert!(
1325 !bar_realpath.join("hello1.txt").exists(),
1326 "hello1.txt was moved"
1327 );
1328 assert!(
1329 !bar_realpath.join("hello2.txt").exists(),
1330 "hello2.txt was moved"
1331 );
1332 assert!(
1333 bar_realpath.join("baz/world2.txt").exists(),
1334 "world2.txt was moved to the correct place"
1335 );
1336 }
1337
1338 #[tokio::test]
1339 async fn test_metadata() {
1340 use std::thread::sleep;
1341 use std::time::Duration;
1342
1343 let temp = TempDir::new().unwrap();
1344
1345 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1346
1347 let root_metadata = fs.metadata(Path::new("/")).unwrap();
1348
1349 assert!(root_metadata.ft.dir);
1350 #[cfg(not(target_env = "musl"))]
1352 assert_eq!(root_metadata.accessed, root_metadata.created);
1353 #[cfg(not(target_env = "musl"))]
1354 assert_eq!(root_metadata.modified, root_metadata.created);
1355 assert!(root_metadata.modified > 0);
1356
1357 let foo = Path::new("foo");
1358
1359 assert_eq!(fs.create_dir(foo), Ok(()));
1360
1361 let foo_metadata = fs.metadata(foo);
1362 assert!(foo_metadata.is_ok());
1363 let foo_metadata = foo_metadata.unwrap();
1364
1365 assert!(foo_metadata.ft.dir);
1366 #[cfg(not(target_env = "musl"))]
1367 assert_eq!(foo_metadata.accessed, foo_metadata.created);
1368 #[cfg(not(target_env = "musl"))]
1369 assert_eq!(foo_metadata.modified, foo_metadata.created);
1370 assert!(foo_metadata.modified > 0);
1371
1372 sleep(Duration::from_secs(3));
1373
1374 let bar = Path::new("bar");
1375
1376 assert_eq!(fs.rename(foo, bar).await, Ok(()));
1377
1378 let bar_metadata = fs.metadata(bar).unwrap();
1379 assert!(bar_metadata.ft.dir);
1380 assert!(bar_metadata.accessed >= foo_metadata.accessed);
1381 assert_eq!(bar_metadata.created, foo_metadata.created);
1382 assert!(bar_metadata.modified > foo_metadata.modified);
1383
1384 let root_metadata = fs.metadata(bar).unwrap();
1385 assert!(
1386 root_metadata.modified > foo_metadata.modified,
1387 "the parent modified time was updated"
1388 );
1389 }
1390
1391 #[tokio::test]
1392 async fn test_rejects_host_absolute_paths_inside_root() {
1393 let temp = TempDir::new().unwrap();
1394 let temp_canon = super::canonicalize(temp.path()).expect("canonicalize temp dir");
1397
1398 let file_path = temp_canon.join("foo.txt");
1399 std::fs::write(&file_path, b"hello").unwrap();
1400
1401 let fs = FileSystem::new(Handle::current(), &temp_canon).expect("get filesystem");
1402
1403 assert_eq!(fs.metadata(&file_path), Err(FsError::InvalidInput));
1404 assert!(matches!(
1405 fs.new_open_options().read(true).open(&file_path),
1406 Err(FsError::InvalidInput)
1407 ));
1408 }
1409
1410 #[tokio::test]
1411 async fn test_remove_file() {
1412 let temp = TempDir::new().unwrap();
1413 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1414
1415 assert!(
1416 fs.new_open_options()
1417 .write(true)
1418 .create_new(true)
1419 .open(Path::new("foo.txt"))
1420 .is_ok(),
1421 "creating a new file",
1422 );
1423
1424 assert!(read_dir_names(&fs, Path::new("/")).contains(&"foo.txt".to_string()));
1425
1426 assert!(temp.path().join("foo.txt").is_file());
1427
1428 assert_eq!(
1429 fs.remove_file(Path::new("foo.txt")),
1430 Ok(()),
1431 "removing a file that exists",
1432 );
1433
1434 assert!(!temp.path().join("foo.txt").exists());
1435
1436 assert_eq!(
1437 fs.remove_file(Path::new("foo.txt")),
1438 Err(FsError::EntryNotFound),
1439 "removing a file that doesn't exists",
1440 );
1441 }
1442
1443 #[tokio::test]
1444 async fn test_readdir() {
1445 let temp = TempDir::new().unwrap();
1446 let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1447
1448 assert_eq!(fs.create_dir(Path::new("foo")), Ok(()), "creating `foo`");
1449 assert_eq!(
1450 fs.create_dir(Path::new("foo/sub")),
1451 Ok(()),
1452 "creating `sub`"
1453 );
1454 assert_eq!(fs.create_dir(Path::new("bar")), Ok(()), "creating `bar`");
1455 assert_eq!(fs.create_dir(Path::new("baz")), Ok(()), "creating `bar`");
1456 assert!(
1457 fs.new_open_options()
1458 .write(true)
1459 .create_new(true)
1460 .open(Path::new("a.txt"))
1461 .is_ok(),
1462 "creating `a.txt`",
1463 );
1464 assert!(
1465 fs.new_open_options()
1466 .write(true)
1467 .create_new(true)
1468 .open(Path::new("b.txt"))
1469 .is_ok(),
1470 "creating `b.txt`",
1471 );
1472
1473 let readdir = fs.read_dir(Path::new("/"));
1474
1475 assert!(
1476 readdir.is_ok(),
1477 "reading the directory `{}`",
1478 Path::new("/").display()
1479 );
1480
1481 let mut readdir = readdir.unwrap();
1482
1483 let next = readdir.next().unwrap().unwrap();
1484 assert!(next.path.ends_with("a.txt"), "checking entry #1");
1485 assert!(next.metadata().unwrap().is_file(), "checking entry #1");
1486
1487 let next = readdir.next().unwrap().unwrap();
1488 assert!(next.path.ends_with("b.txt"), "checking entry #2");
1489 assert!(next.metadata().unwrap().is_file(), "checking entry #2");
1490
1491 let next = readdir.next().unwrap().unwrap();
1492 assert!(next.path.ends_with("bar"), "checking entry #3");
1493 assert!(next.metadata().unwrap().is_dir(), "checking entry #3");
1494
1495 let next = readdir.next().unwrap().unwrap();
1496 assert!(next.path.ends_with("baz"), "checking entry #4");
1497 assert!(next.metadata().unwrap().is_dir(), "checking entry #4");
1498
1499 let next = readdir.next().unwrap().unwrap();
1500 assert!(next.path.ends_with("foo"), "checking entry #5");
1501 assert!(next.metadata().unwrap().is_dir(), "checking entry #5");
1502
1503 if let Some(s) = readdir.next() {
1504 panic!("next: {s:?}");
1505 }
1506 }
1507}