virtual_fs/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3#[cfg(test)]
4#[macro_use]
5extern crate pretty_assertions;
6
7use futures::future::BoxFuture;
8use shared_buffer::OwnedBuffer;
9use std::any::Any;
10use std::ffi::OsString;
11use std::fmt;
12use std::io;
13use std::ops::Deref;
14use std::path::{Path, PathBuf};
15use std::pin::Pin;
16use std::task::Context;
17use std::task::Poll;
18use thiserror::Error;
19
20pub mod arc_box_file;
21pub mod arc_file;
22pub mod arc_fs;
23pub mod buffer_file;
24pub mod builder;
25pub mod combine_file;
26pub mod cow_file;
27pub mod dual_write_file;
28pub mod empty_fs;
29#[cfg(feature = "host-fs")]
30pub mod host_fs;
31pub mod mem_fs;
32pub mod mount_fs;
33pub mod null_file;
34pub mod passthru_fs;
35pub mod random_file;
36pub mod special_file;
37pub mod tmp_fs;
38pub mod zero_file;
39// tty_file -> see wasmer_wasi::tty_file
40mod filesystems;
41pub(crate) mod ops;
42mod overlay_fs;
43pub mod pipe;
44mod static_file;
45#[cfg(feature = "static-fs")]
46pub mod static_fs;
47mod trace_fs;
48#[cfg(feature = "webc-fs")]
49mod webc_volume_fs;
50
51pub mod limiter;
52
53pub use arc_box_file::*;
54pub use arc_file::*;
55pub use arc_fs::*;
56pub use buffer_file::*;
57pub use builder::*;
58pub use combine_file::*;
59pub use cow_file::*;
60pub use dual_write_file::*;
61pub use empty_fs::*;
62pub use filesystems::FileSystems;
63pub use mount_fs::*;
64pub use null_file::*;
65pub use overlay_fs::OverlayFileSystem;
66pub use passthru_fs::*;
67pub use pipe::*;
68pub use special_file::*;
69pub use static_file::StaticFile;
70pub use tmp_fs::*;
71pub use trace_fs::TraceFileSystem;
72#[cfg(feature = "webc-fs")]
73pub use webc_volume_fs::WebcVolumeFileSystem;
74pub use zero_file::*;
75
76pub type Result<T> = std::result::Result<T, FsError>;
77
78// re-exports
79pub use tokio::io::ReadBuf;
80pub use tokio::io::{AsyncRead, AsyncReadExt};
81pub use tokio::io::{AsyncSeek, AsyncSeekExt};
82pub use tokio::io::{AsyncWrite, AsyncWriteExt};
83
84pub trait ClonableVirtualFile: VirtualFile + Clone {}
85
86pub use ops::{copy_reference, copy_reference_ext, create_dir_all, walk};
87
88pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable {
89    fn readlink(&self, path: &Path) -> Result<PathBuf>;
90    fn read_dir(&self, path: &Path) -> Result<ReadDir>;
91    fn create_dir(&self, path: &Path) -> Result<()>;
92    fn create_symlink(&self, _source: &Path, _target: &Path) -> Result<()> {
93        Err(FsError::Unsupported)
94    }
95    fn remove_dir(&self, path: &Path) -> Result<()>;
96    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>;
97    fn metadata(&self, path: &Path) -> Result<Metadata>;
98    /// This method gets metadata without following symlinks in the path.
99    /// Currently identical to `metadata` because symlinks aren't implemented
100    /// yet.
101    fn symlink_metadata(&self, path: &Path) -> Result<Metadata>;
102    fn remove_file(&self, path: &Path) -> Result<()>;
103
104    fn new_open_options(&self) -> OpenOptions<'_>;
105}
106
107impl dyn FileSystem + 'static {
108    #[inline]
109    pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> {
110        self.upcast_any_ref().downcast_ref::<T>()
111    }
112    #[inline]
113    pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> {
114        self.upcast_any_mut().downcast_mut::<T>()
115    }
116}
117
118#[async_trait::async_trait]
119impl<D, F> FileSystem for D
120where
121    D: Deref<Target = F> + std::fmt::Debug + Send + Sync + 'static,
122    F: FileSystem + ?Sized,
123{
124    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
125        (**self).read_dir(path)
126    }
127
128    fn readlink(&self, path: &Path) -> Result<PathBuf> {
129        (**self).readlink(path)
130    }
131
132    fn create_dir(&self, path: &Path) -> Result<()> {
133        (**self).create_dir(path)
134    }
135
136    fn create_symlink(&self, source: &Path, target: &Path) -> Result<()> {
137        (**self).create_symlink(source, target)
138    }
139
140    fn remove_dir(&self, path: &Path) -> Result<()> {
141        (**self).remove_dir(path)
142    }
143
144    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
145        Box::pin(async { (**self).rename(from, to).await })
146    }
147
148    fn metadata(&self, path: &Path) -> Result<Metadata> {
149        (**self).metadata(path)
150    }
151
152    fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
153        (**self).symlink_metadata(path)
154    }
155
156    fn remove_file(&self, path: &Path) -> Result<()> {
157        (**self).remove_file(path)
158    }
159
160    fn new_open_options(&self) -> OpenOptions<'_> {
161        (**self).new_open_options()
162    }
163}
164
165pub trait FileOpener {
166    fn open(
167        &self,
168        path: &Path,
169        conf: &OpenOptionsConfig,
170    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>>;
171}
172
173#[derive(Debug, Clone)]
174pub struct OpenOptionsConfig {
175    pub read: bool,
176    pub write: bool,
177    pub create_new: bool,
178    pub create: bool,
179    pub append: bool,
180    pub truncate: bool,
181}
182
183impl OpenOptionsConfig {
184    /// Returns the minimum allowed rights, given the rights of the parent directory
185    pub fn minimum_rights(&self, parent_rights: &Self) -> Self {
186        Self {
187            read: parent_rights.read && self.read,
188            write: parent_rights.write && self.write,
189            create_new: parent_rights.create_new && self.create_new,
190            create: parent_rights.create && self.create,
191            append: parent_rights.append && self.append,
192            truncate: parent_rights.truncate && self.truncate,
193        }
194    }
195
196    pub const fn read(&self) -> bool {
197        self.read
198    }
199
200    pub const fn write(&self) -> bool {
201        self.write
202    }
203
204    pub const fn create_new(&self) -> bool {
205        self.create_new
206    }
207
208    pub const fn create(&self) -> bool {
209        self.create
210    }
211
212    pub const fn append(&self) -> bool {
213        self.append
214    }
215
216    pub const fn truncate(&self) -> bool {
217        self.truncate
218    }
219
220    /// Would a file opened with this [`OpenOptionsConfig`] change files on the
221    /// filesystem.
222    pub const fn would_mutate(&self) -> bool {
223        let OpenOptionsConfig {
224            read: _,
225            write,
226            create_new,
227            create,
228            append,
229            truncate,
230        } = *self;
231        append || write || create || create_new || truncate
232    }
233}
234
235impl fmt::Debug for OpenOptions<'_> {
236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237        self.conf.fmt(f)
238    }
239}
240
241pub struct OpenOptions<'a> {
242    opener: &'a dyn FileOpener,
243    conf: OpenOptionsConfig,
244}
245
246impl<'a> OpenOptions<'a> {
247    pub fn new(opener: &'a dyn FileOpener) -> Self {
248        Self {
249            opener,
250            conf: OpenOptionsConfig {
251                read: false,
252                write: false,
253                create_new: false,
254                create: false,
255                append: false,
256                truncate: false,
257            },
258        }
259    }
260
261    pub fn get_config(&self) -> OpenOptionsConfig {
262        self.conf.clone()
263    }
264
265    /// Use an existing [`OpenOptionsConfig`] to configure this [`OpenOptions`].
266    pub fn options(&mut self, options: OpenOptionsConfig) -> &mut Self {
267        self.conf = options;
268        self
269    }
270
271    /// Sets the option for read access.
272    ///
273    /// This option, when true, will indicate that the file should be
274    /// `read`-able if opened.
275    pub fn read(&mut self, read: bool) -> &mut Self {
276        self.conf.read = read;
277        self
278    }
279
280    /// Sets the option for write access.
281    ///
282    /// This option, when true, will indicate that the file should be
283    /// `write`-able if opened.
284    ///
285    /// If the file already exists, any write calls on it will overwrite its
286    /// contents, without truncating it.
287    pub fn write(&mut self, write: bool) -> &mut Self {
288        self.conf.write = write;
289        self
290    }
291
292    /// Sets the option for the append mode.
293    ///
294    /// This option, when true, means that writes will append to a file instead
295    /// of overwriting previous contents.
296    /// Note that setting `.write(true).append(true)` has the same effect as
297    /// setting only `.append(true)`.
298    pub fn append(&mut self, append: bool) -> &mut Self {
299        self.conf.append = append;
300        self
301    }
302
303    /// Sets the option for truncating a previous file.
304    ///
305    /// If a file is successfully opened with this option set it will truncate
306    /// the file to 0 length if it already exists.
307    ///
308    /// The file must be opened with write access for truncate to work.
309    pub fn truncate(&mut self, truncate: bool) -> &mut Self {
310        self.conf.truncate = truncate;
311        self
312    }
313
314    /// Sets the option to create a new file, or open it if it already exists.
315    pub fn create(&mut self, create: bool) -> &mut Self {
316        self.conf.create = create;
317        self
318    }
319
320    /// Sets the option to create a new file, failing if it already exists.
321    pub fn create_new(&mut self, create_new: bool) -> &mut Self {
322        self.conf.create_new = create_new;
323        self
324    }
325
326    pub fn open<P: AsRef<Path>>(
327        &mut self,
328        path: P,
329    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
330        self.opener.open(path.as_ref(), &self.conf)
331    }
332}
333
334/// This trait relies on your file closing when it goes out of scope via `Drop`
335//#[cfg_attr(feature = "enable-serde", typetag::serde)]
336pub trait VirtualFile:
337    fmt::Debug + AsyncRead + AsyncWrite + AsyncSeek + Unpin + Upcastable + Send
338{
339    /// the last time the file was accessed in nanoseconds as a UNIX timestamp
340    fn last_accessed(&self) -> u64;
341
342    /// the last time the file was modified in nanoseconds as a UNIX timestamp
343    fn last_modified(&self) -> u64;
344
345    /// the time at which the file was created in nanoseconds as a UNIX timestamp
346    fn created_time(&self) -> u64;
347
348    #[allow(unused_variables)]
349    /// sets accessed and modified time
350    fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
351        Ok(())
352    }
353
354    /// the size of the file in bytes
355    fn size(&self) -> u64;
356
357    /// Change the size of the file, if the `new_size` is greater than the current size
358    /// the extra bytes will be allocated and zeroed
359    fn set_len(&mut self, new_size: u64) -> Result<()>;
360
361    /// Request deletion of the file
362    fn unlink(&mut self) -> Result<()>;
363
364    /// Indicates if the file is opened or closed. This function must not block
365    /// Defaults to a status of being constantly open
366    fn is_open(&self) -> bool {
367        true
368    }
369
370    /// Used for "special" files such as `stdin`, `stdout` and `stderr`.
371    /// Always returns the same file descriptor (0, 1 or 2). Returns `None`
372    /// on normal files
373    fn get_special_fd(&self) -> Option<u32> {
374        None
375    }
376
377    /// Writes to this file using an mmap offset and reference
378    /// (this method only works for mmap optimized file systems)
379    fn write_from_mmap(&mut self, _offset: u64, _len: u64) -> std::io::Result<()> {
380        Err(std::io::ErrorKind::Unsupported.into())
381    }
382
383    /// This method will copy a file from a source to this destination where
384    /// the default is to do a straight byte copy however file system implementors
385    /// may optimize this to do a zero copy
386    fn copy_reference(
387        &mut self,
388        mut src: Box<dyn VirtualFile + Send + Sync + 'static>,
389    ) -> BoxFuture<'_, std::io::Result<()>> {
390        Box::pin(async move {
391            let bytes_written = tokio::io::copy(&mut src, self).await?;
392            tracing::trace!(bytes_written, "Copying file into host filesystem");
393            Ok(())
394        })
395    }
396
397    /// This method will copy a file from a source to this destination where
398    /// the default is to do a straight byte copy however file system implementors
399    /// may optimize this to cheaply clone and store the OwnedBuffer directly
400    fn copy_from_owned_buffer(&mut self, src: &OwnedBuffer) -> BoxFuture<'_, std::io::Result<()>> {
401        let src = src.clone();
402        Box::pin(async move {
403            let mut bytes = src.as_slice();
404            let bytes_written = tokio::io::copy(&mut bytes, self).await?;
405            tracing::trace!(bytes_written, "Copying file into host filesystem");
406            Ok(())
407        })
408    }
409
410    /// Get the full contents of this file as an [`OwnedBuffer`].
411    ///
412    /// **NOTE**: Only implement this if the file is already available in-memory
413    /// and can be cloned cheaply!
414    ///
415    /// Allows consumers to do zero-copy cloning of the underlying data.
416    fn as_owned_buffer(&self) -> Option<OwnedBuffer> {
417        None
418    }
419
420    /// Polls the file for when there is data to be read
421    fn poll_read_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
422
423    /// Polls the file for when it is available for writing
424    fn poll_write_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
425}
426
427// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 .
428/// Trait needed to get downcasting from `VirtualFile` to work.
429pub trait Upcastable {
430    fn upcast_any_ref(&'_ self) -> &'_ dyn Any;
431    fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any;
432    fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
433}
434
435impl<T: Any + fmt::Debug + 'static> Upcastable for T {
436    #[inline]
437    fn upcast_any_ref(&'_ self) -> &'_ dyn Any {
438        self
439    }
440    #[inline]
441    fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any {
442        self
443    }
444    #[inline]
445    fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
446        self
447    }
448}
449
450/// Determines the mode that stdio handlers will operate in
451#[derive(Debug, Copy, Clone, PartialEq, Eq)]
452pub enum StdioMode {
453    /// Stdio will be piped to a file descriptor
454    Piped,
455    /// Stdio will inherit the file handlers of its parent
456    Inherit,
457    /// Stdio will be dropped
458    Null,
459    /// Stdio will be sent to the log handler
460    Log,
461}
462
463/// Error type for external users
464#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
465pub enum FsError {
466    /// The fd given as a base was not a directory so the operation was not possible
467    #[error("fd not a directory")]
468    BaseNotDirectory,
469    /// Expected a file but found not a file
470    #[error("fd not a file")]
471    NotAFile,
472    /// The fd given was not usable
473    #[error("invalid fd")]
474    InvalidFd,
475    /// File exists
476    #[error("file exists")]
477    AlreadyExists,
478    /// The filesystem has failed to lock a resource.
479    #[error("lock error")]
480    Lock,
481    /// Something failed when doing IO. These errors can generally not be handled.
482    /// It may work if tried again.
483    #[error("io error")]
484    IOError,
485    /// The address was in use
486    #[error("address is in use")]
487    AddressInUse,
488    /// The address could not be found
489    #[error("address could not be found")]
490    AddressNotAvailable,
491    /// A pipe was closed
492    #[error("broken pipe (was closed)")]
493    BrokenPipe,
494    /// The connection was aborted
495    #[error("connection aborted")]
496    ConnectionAborted,
497    /// The connection request was refused
498    #[error("connection refused")]
499    ConnectionRefused,
500    /// The connection was reset
501    #[error("connection reset")]
502    ConnectionReset,
503    /// The operation was interrupted before it could finish
504    #[error("operation interrupted")]
505    Interrupted,
506    /// Invalid internal data, if the argument data is invalid, use `InvalidInput`
507    #[error("invalid internal data")]
508    InvalidData,
509    /// The provided data is invalid
510    #[error("invalid input")]
511    InvalidInput,
512    /// Could not perform the operation because there was not an open connection
513    #[error("connection is not open")]
514    NotConnected,
515    /// The requested file or directory could not be found
516    #[error("entry not found")]
517    EntryNotFound,
518    /// The requested device couldn't be accessed
519    #[error("can't access device")]
520    NoDevice,
521    /// Caller was not allowed to perform this operation
522    #[error("permission denied")]
523    PermissionDenied,
524    /// The operation did not complete within the given amount of time
525    #[error("time out")]
526    TimedOut,
527    /// Found EOF when EOF was not expected
528    #[error("unexpected eof")]
529    UnexpectedEof,
530    /// Operation would block, this error lets the caller know that they can try again
531    #[error("blocking operation. try again")]
532    WouldBlock,
533    /// A call to write returned 0
534    #[error("write returned 0")]
535    WriteZero,
536    /// Directory not Empty
537    #[error("directory not empty")]
538    DirectoryNotEmpty,
539    #[error("storage full")]
540    StorageFull,
541    /// Some other unhandled error. If you see this, it's probably a bug.
542    #[error("unknown error found")]
543    UnknownError,
544    /// Operation is not supported on this filesystem
545    #[error("unsupported")]
546    Unsupported,
547}
548
549impl From<io::Error> for FsError {
550    fn from(io_error: io::Error) -> Self {
551        match io_error.kind() {
552            io::ErrorKind::AddrInUse => FsError::AddressInUse,
553            io::ErrorKind::AddrNotAvailable => FsError::AddressNotAvailable,
554            io::ErrorKind::AlreadyExists => FsError::AlreadyExists,
555            io::ErrorKind::BrokenPipe => FsError::BrokenPipe,
556            io::ErrorKind::ConnectionAborted => FsError::ConnectionAborted,
557            io::ErrorKind::ConnectionRefused => FsError::ConnectionRefused,
558            io::ErrorKind::ConnectionReset => FsError::ConnectionReset,
559            io::ErrorKind::Interrupted => FsError::Interrupted,
560            io::ErrorKind::InvalidData => FsError::InvalidData,
561            io::ErrorKind::InvalidInput => FsError::InvalidInput,
562            io::ErrorKind::NotConnected => FsError::NotConnected,
563            io::ErrorKind::NotFound => FsError::EntryNotFound,
564            io::ErrorKind::PermissionDenied => FsError::PermissionDenied,
565            io::ErrorKind::TimedOut => FsError::TimedOut,
566            io::ErrorKind::UnexpectedEof => FsError::UnexpectedEof,
567            io::ErrorKind::WouldBlock => FsError::WouldBlock,
568            io::ErrorKind::WriteZero => FsError::WriteZero,
569            // NOTE: Add this once the "io_error_more" Rust feature is stabilized
570            // io::ErrorKind::StorageFull => FsError::StorageFull,
571            io::ErrorKind::Other => FsError::IOError,
572            // if the following triggers, a new error type was added to this non-exhaustive enum
573            _ => FsError::UnknownError,
574        }
575    }
576}
577
578impl From<FsError> for io::Error {
579    fn from(val: FsError) -> Self {
580        let kind = match val {
581            FsError::AddressInUse => io::ErrorKind::AddrInUse,
582            FsError::AddressNotAvailable => io::ErrorKind::AddrNotAvailable,
583            FsError::AlreadyExists => io::ErrorKind::AlreadyExists,
584            FsError::BrokenPipe => io::ErrorKind::BrokenPipe,
585            FsError::ConnectionAborted => io::ErrorKind::ConnectionAborted,
586            FsError::ConnectionRefused => io::ErrorKind::ConnectionRefused,
587            FsError::ConnectionReset => io::ErrorKind::ConnectionReset,
588            FsError::Interrupted => io::ErrorKind::Interrupted,
589            FsError::InvalidData => io::ErrorKind::InvalidData,
590            FsError::InvalidInput => io::ErrorKind::InvalidInput,
591            FsError::NotConnected => io::ErrorKind::NotConnected,
592            FsError::EntryNotFound => io::ErrorKind::NotFound,
593            FsError::PermissionDenied => io::ErrorKind::PermissionDenied,
594            FsError::TimedOut => io::ErrorKind::TimedOut,
595            FsError::UnexpectedEof => io::ErrorKind::UnexpectedEof,
596            FsError::WouldBlock => io::ErrorKind::WouldBlock,
597            FsError::WriteZero => io::ErrorKind::WriteZero,
598            FsError::IOError => io::ErrorKind::Other,
599            FsError::BaseNotDirectory => io::ErrorKind::Other,
600            FsError::NotAFile => io::ErrorKind::Other,
601            FsError::InvalidFd => io::ErrorKind::Other,
602            FsError::Lock => io::ErrorKind::Other,
603            FsError::NoDevice => io::ErrorKind::Other,
604            FsError::DirectoryNotEmpty => io::ErrorKind::Other,
605            FsError::UnknownError => io::ErrorKind::Other,
606            FsError::StorageFull => io::ErrorKind::Other,
607            FsError::Unsupported => io::ErrorKind::Unsupported,
608            // NOTE: Add this once the "io_error_more" Rust feature is stabilized
609            // FsError::StorageFull => io::ErrorKind::StorageFull,
610        };
611        kind.into()
612    }
613}
614
615#[derive(Debug)]
616pub struct ReadDir {
617    // TODO: to do this properly we need some kind of callback to the core FS abstraction
618    pub(crate) data: Vec<DirEntry>,
619    index: usize,
620}
621
622impl ReadDir {
623    pub fn new(data: Vec<DirEntry>) -> Self {
624        Self { data, index: 0 }
625    }
626    pub fn is_empty(&self) -> bool {
627        self.data.is_empty()
628    }
629}
630
631#[derive(Debug, Clone, PartialEq, Eq)]
632pub struct DirEntry {
633    pub path: PathBuf,
634    // weird hack, to fix this we probably need an internal trait object or callbacks or something
635    pub metadata: Result<Metadata>,
636}
637
638impl DirEntry {
639    pub fn path(&self) -> PathBuf {
640        self.path.clone()
641    }
642
643    pub fn metadata(&self) -> Result<Metadata> {
644        self.metadata.clone()
645    }
646
647    pub fn file_type(&self) -> Result<FileType> {
648        let metadata = self.metadata.clone()?;
649        Ok(metadata.file_type())
650    }
651
652    pub fn file_name(&self) -> OsString {
653        self.path
654            .file_name()
655            .unwrap_or(self.path.as_os_str())
656            .to_owned()
657    }
658
659    pub fn is_white_out(&self) -> Option<PathBuf> {
660        ops::is_white_out(&self.path)
661    }
662}
663
664#[allow(clippy::len_without_is_empty)] // Clippy thinks it's an iterator.
665#[derive(Clone, Debug, Default, PartialEq, Eq)]
666// TODO: review this, proper solution would probably use a trait object internally
667pub struct Metadata {
668    pub ft: FileType,
669    pub accessed: u64,
670    pub created: u64,
671    pub modified: u64,
672    pub len: u64,
673}
674
675impl Metadata {
676    pub fn is_file(&self) -> bool {
677        self.ft.is_file()
678    }
679
680    pub fn is_dir(&self) -> bool {
681        self.ft.is_dir()
682    }
683
684    pub fn accessed(&self) -> u64 {
685        self.accessed
686    }
687
688    pub fn created(&self) -> u64 {
689        self.created
690    }
691
692    pub fn modified(&self) -> u64 {
693        self.modified
694    }
695
696    pub fn file_type(&self) -> FileType {
697        self.ft.clone()
698    }
699
700    pub fn len(&self) -> u64 {
701        self.len
702    }
703}
704
705#[derive(Clone, Debug, Default, PartialEq, Eq)]
706// TODO: review this, proper solution would probably use a trait object internally
707pub struct FileType {
708    pub dir: bool,
709    pub file: bool,
710    pub symlink: bool,
711    // TODO: the following 3 only exist on unix in the standard FS API.
712    // We should mirror that API and extend with that trait too.
713    pub char_device: bool,
714    pub block_device: bool,
715    pub socket: bool,
716    pub fifo: bool,
717}
718
719impl FileType {
720    pub fn new_dir() -> Self {
721        Self {
722            dir: true,
723            ..Default::default()
724        }
725    }
726
727    pub fn new_file() -> Self {
728        Self {
729            file: true,
730            ..Default::default()
731        }
732    }
733
734    pub fn is_dir(&self) -> bool {
735        self.dir
736    }
737    pub fn is_file(&self) -> bool {
738        self.file
739    }
740    pub fn is_symlink(&self) -> bool {
741        self.symlink
742    }
743    pub fn is_char_device(&self) -> bool {
744        self.char_device
745    }
746    pub fn is_block_device(&self) -> bool {
747        self.block_device
748    }
749    pub fn is_socket(&self) -> bool {
750        self.socket
751    }
752    pub fn is_fifo(&self) -> bool {
753        self.fifo
754    }
755}
756
757impl Iterator for ReadDir {
758    type Item = Result<DirEntry>;
759
760    fn next(&mut self) -> Option<Result<DirEntry>> {
761        if let Some(v) = self.data.get(self.index).cloned() {
762            self.index += 1;
763            return Some(Ok(v));
764        }
765        None
766    }
767}