virtual_fs/
host_fs.rs

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
41// Copied from cargo
42// https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
43pub 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        // https://github.com/rust-lang/rust/issues/86442
184        // DirectoryNotEmpty is not implemented consistently
185        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        // TODO: handle create implying write, etc.
338        let read = conf.read();
339        let write = conf.write();
340
341        // according to Rust's stdlib, specifying both truncate and append is nonsensical,
342        // and it will return an error if we try to open a file with both flags set.
343        // in order to prevent this, and stay compatible with native binaries, we just ignore
344        // the append flag if truncate is set. the rationale behind this decision is that
345        // truncate is going to be applied first and append is going to be ignored anyway.
346        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/// A thin wrapper around `std::fs::File`
371#[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    /// creates a new host file from a `std::fs::File` and a path
483    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        // FIXME: no unwrap!
518        self.inner_std.metadata().unwrap()
519    }
520}
521
522//#[cfg_attr(feature = "enable-serde", typetag::serde)]
523#[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/// A wrapper type around Stdout that implements `VirtualFile`.
667#[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
688/// Default size for write buffers.
689///
690/// Chosen to be both sufficiently large, and a multiple of the default page
691/// size on most systems.
692///
693/// This value has limited meaning, since it is only used for buffer size hints,
694/// and those hints are often ignored.
695const DEFAULT_BUF_SIZE_HINT: usize = 8 * 1024;
696
697//#[cfg_attr(feature = "enable-serde", typetag::serde)]
698#[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/// A wrapper type around Stderr that implements `VirtualFile`.
796#[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//#[cfg_attr(feature = "enable-serde", typetag::serde)]
876#[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/// A wrapper type around Stdin that implements `VirtualFile`.
916#[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//#[cfg_attr(feature = "enable-serde", typetag::serde)]
1002#[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        // On Windows, rename "to" must not be an existing directory
1233        #[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        // it seems created is not available on musl, at least on CI testing.
1351        #[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        // Some platforms (e.g. mac) symlink /tmp to /private/tmp, so we need to canonicalize
1395        // the path to get the real one, making sure the guest and host paths line up.
1396        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}