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