virtual_fs/
lib.rs

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