Skip to main content

virtual_fs/
lib.rs

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