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