wasmer_vfs/
lib.rs

1use std::any::Any;
2use std::ffi::OsString;
3use std::fmt;
4use std::io::{self, Read, Seek, Write};
5use std::path::{Path, PathBuf};
6use thiserror::Error;
7
8#[cfg(all(not(feature = "host-fs"), not(feature = "mem-fs")))]
9compile_error!("At least the `host-fs` or the `mem-fs` feature must be enabled. Please, pick one.");
10
11//#[cfg(all(feature = "mem-fs", feature = "enable-serde"))]
12//compile_warn!("`mem-fs` does not support `enable-serde` for the moment.");
13
14#[cfg(feature = "host-fs")]
15pub mod host_fs;
16#[cfg(feature = "mem-fs")]
17pub mod mem_fs;
18#[cfg(feature = "static-fs")]
19pub mod static_fs;
20#[cfg(feature = "webc-fs")]
21pub mod webc_fs;
22
23pub type Result<T> = std::result::Result<T, FsError>;
24
25#[derive(Debug)]
26#[repr(transparent)]
27pub struct FileDescriptor(usize);
28
29impl From<u32> for FileDescriptor {
30    fn from(a: u32) -> Self {
31        Self(a as usize)
32    }
33}
34
35impl From<FileDescriptor> for u32 {
36    fn from(a: FileDescriptor) -> u32 {
37        a.0 as u32
38    }
39}
40
41pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable {
42    fn read_dir(&self, path: &Path) -> Result<ReadDir>;
43    fn create_dir(&self, path: &Path) -> Result<()>;
44    fn remove_dir(&self, path: &Path) -> Result<()>;
45    fn rename(&self, from: &Path, to: &Path) -> Result<()>;
46    fn metadata(&self, path: &Path) -> Result<Metadata>;
47    /// This method gets metadata without following symlinks in the path.
48    /// Currently identical to `metadata` because symlinks aren't implemented
49    /// yet.
50    fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
51        self.metadata(path)
52    }
53    fn remove_file(&self, path: &Path) -> Result<()>;
54
55    fn new_open_options(&self) -> OpenOptions;
56}
57
58impl dyn FileSystem + 'static {
59    #[inline]
60    pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> {
61        self.upcast_any_ref().downcast_ref::<T>()
62    }
63    #[inline]
64    pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> {
65        self.upcast_any_mut().downcast_mut::<T>()
66    }
67}
68
69pub trait FileOpener {
70    fn open(
71        &mut self,
72        path: &Path,
73        conf: &OpenOptionsConfig,
74    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>>;
75}
76
77#[derive(Debug, Clone)]
78pub struct OpenOptionsConfig {
79    pub read: bool,
80    pub write: bool,
81    pub create_new: bool,
82    pub create: bool,
83    pub append: bool,
84    pub truncate: bool,
85}
86
87impl OpenOptionsConfig {
88    /// Returns the minimum allowed rights, given the rights of the parent directory
89    pub fn minimum_rights(&self, parent_rights: &Self) -> Self {
90        Self {
91            read: parent_rights.read && self.read,
92            write: parent_rights.write && self.write,
93            create_new: parent_rights.create_new && self.create_new,
94            create: parent_rights.create && self.create,
95            append: parent_rights.append && self.append,
96            truncate: parent_rights.truncate && self.truncate,
97        }
98    }
99
100    pub const fn read(&self) -> bool {
101        self.read
102    }
103
104    pub const fn write(&self) -> bool {
105        self.write
106    }
107
108    pub const fn create_new(&self) -> bool {
109        self.create_new
110    }
111
112    pub const fn create(&self) -> bool {
113        self.create
114    }
115
116    pub const fn append(&self) -> bool {
117        self.append
118    }
119
120    pub const fn truncate(&self) -> bool {
121        self.truncate
122    }
123}
124
125impl fmt::Debug for OpenOptions {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        self.conf.fmt(f)
128    }
129}
130
131pub struct OpenOptions {
132    opener: Box<dyn FileOpener>,
133    conf: OpenOptionsConfig,
134}
135
136impl OpenOptions {
137    pub fn new(opener: Box<dyn FileOpener>) -> Self {
138        Self {
139            opener,
140            conf: OpenOptionsConfig {
141                read: false,
142                write: false,
143                create_new: false,
144                create: false,
145                append: false,
146                truncate: false,
147            },
148        }
149    }
150
151    pub fn get_config(&self) -> OpenOptionsConfig {
152        self.conf.clone()
153    }
154
155    pub fn options(&mut self, options: OpenOptionsConfig) -> &mut Self {
156        self.conf = options;
157        self
158    }
159
160    pub fn read(&mut self, read: bool) -> &mut Self {
161        self.conf.read = read;
162        self
163    }
164
165    pub fn write(&mut self, write: bool) -> &mut Self {
166        self.conf.write = write;
167        self
168    }
169
170    pub fn append(&mut self, append: bool) -> &mut Self {
171        self.conf.append = append;
172        self
173    }
174
175    pub fn truncate(&mut self, truncate: bool) -> &mut Self {
176        self.conf.truncate = truncate;
177        self
178    }
179
180    pub fn create(&mut self, create: bool) -> &mut Self {
181        self.conf.create = create;
182        self
183    }
184
185    pub fn create_new(&mut self, create_new: bool) -> &mut Self {
186        self.conf.create_new = create_new;
187        self
188    }
189
190    pub fn open<P: AsRef<Path>>(
191        &mut self,
192        path: P,
193    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
194        self.opener.open(path.as_ref(), &self.conf)
195    }
196}
197
198/// This trait relies on your file closing when it goes out of scope via `Drop`
199//#[cfg_attr(feature = "enable-serde", typetag::serde)]
200pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable {
201    /// the last time the file was accessed in nanoseconds as a UNIX timestamp
202    fn last_accessed(&self) -> u64;
203
204    /// the last time the file was modified in nanoseconds as a UNIX timestamp
205    fn last_modified(&self) -> u64;
206
207    /// the time at which the file was created in nanoseconds as a UNIX timestamp
208    fn created_time(&self) -> u64;
209
210    /// the size of the file in bytes
211    fn size(&self) -> u64;
212
213    /// Change the size of the file, if the `new_size` is greater than the current size
214    /// the extra bytes will be allocated and zeroed
215    fn set_len(&mut self, new_size: u64) -> Result<()>;
216
217    /// Request deletion of the file
218    fn unlink(&mut self) -> Result<()>;
219
220    /// Store file contents and metadata to disk
221    /// Default implementation returns `Ok(())`.  You should implement this method if you care
222    /// about flushing your cache to permanent storage
223    fn sync_to_disk(&self) -> Result<()> {
224        Ok(())
225    }
226
227    /// Returns the number of bytes available.  This function must not block
228    fn bytes_available(&self) -> Result<usize> {
229        Ok(self.bytes_available_read()?.unwrap_or(0usize)
230            + self.bytes_available_write()?.unwrap_or(0usize))
231    }
232
233    /// Returns the number of bytes available.  This function must not block
234    /// Defaults to `None` which means the number of bytes is unknown
235    fn bytes_available_read(&self) -> Result<Option<usize>> {
236        Ok(None)
237    }
238
239    /// Returns the number of bytes available.  This function must not block
240    /// Defaults to `None` which means the number of bytes is unknown
241    fn bytes_available_write(&self) -> Result<Option<usize>> {
242        Ok(None)
243    }
244
245    /// Indicates if the file is opened or closed. This function must not block
246    /// Defaults to a status of being constantly open
247    fn is_open(&self) -> bool {
248        true
249    }
250
251    /// Used for polling.  Default returns `None` because this method cannot be implemented for most types
252    /// Returns the underlying host fd
253    fn get_fd(&self) -> Option<FileDescriptor> {
254        None
255    }
256}
257
258// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 .
259/// Trait needed to get downcasting from `VirtualFile` to work.
260pub trait Upcastable {
261    fn upcast_any_ref(&'_ self) -> &'_ dyn Any;
262    fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any;
263    fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
264}
265
266impl<T: Any + fmt::Debug + 'static> Upcastable for T {
267    #[inline]
268    fn upcast_any_ref(&'_ self) -> &'_ dyn Any {
269        self
270    }
271    #[inline]
272    fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any {
273        self
274    }
275    #[inline]
276    fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
277        self
278    }
279}
280
281/// Determines the mode that stdio handlers will operate in
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum StdioMode {
284    /// Stdio will be piped to a file descriptor
285    Piped,
286    /// Stdio will inherit the file handlers of its parent
287    Inherit,
288    /// Stdio will be dropped
289    Null,
290    /// Stdio will be sent to the log handler
291    Log,
292}
293
294/// Error type for external users
295#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
296pub enum FsError {
297    /// The fd given as a base was not a directory so the operation was not possible
298    #[error("fd not a directory")]
299    BaseNotDirectory,
300    /// Expected a file but found not a file
301    #[error("fd not a file")]
302    NotAFile,
303    /// The fd given was not usable
304    #[error("invalid fd")]
305    InvalidFd,
306    /// File exists
307    #[error("file exists")]
308    AlreadyExists,
309    /// The filesystem has failed to lock a resource.
310    #[error("lock error")]
311    Lock,
312    /// Something failed when doing IO. These errors can generally not be handled.
313    /// It may work if tried again.
314    #[error("io error")]
315    IOError,
316    /// The address was in use
317    #[error("address is in use")]
318    AddressInUse,
319    /// The address could not be found
320    #[error("address could not be found")]
321    AddressNotAvailable,
322    /// A pipe was closed
323    #[error("broken pipe (was closed)")]
324    BrokenPipe,
325    /// The connection was aborted
326    #[error("connection aborted")]
327    ConnectionAborted,
328    /// The connection request was refused
329    #[error("connection refused")]
330    ConnectionRefused,
331    /// The connection was reset
332    #[error("connection reset")]
333    ConnectionReset,
334    /// The operation was interrupted before it could finish
335    #[error("operation interrupted")]
336    Interrupted,
337    /// Invalid internal data, if the argument data is invalid, use `InvalidInput`
338    #[error("invalid internal data")]
339    InvalidData,
340    /// The provided data is invalid
341    #[error("invalid input")]
342    InvalidInput,
343    /// Could not perform the operation because there was not an open connection
344    #[error("connection is not open")]
345    NotConnected,
346    /// The requested file or directory could not be found
347    #[error("entity not found")]
348    EntityNotFound,
349    /// The requested device couldn't be accessed
350    #[error("can't access device")]
351    NoDevice,
352    /// Caller was not allowed to perform this operation
353    #[error("permission denied")]
354    PermissionDenied,
355    /// The operation did not complete within the given amount of time
356    #[error("time out")]
357    TimedOut,
358    /// Found EOF when EOF was not expected
359    #[error("unexpected eof")]
360    UnexpectedEof,
361    /// Operation would block, this error lets the caller know that they can try again
362    #[error("blocking operation. try again")]
363    WouldBlock,
364    /// A call to write returned 0
365    #[error("write returned 0")]
366    WriteZero,
367    /// Directory not Empty
368    #[error("directory not empty")]
369    DirectoryNotEmpty,
370    /// Some other unhandled error. If you see this, it's probably a bug.
371    #[error("unknown error found")]
372    UnknownError,
373}
374
375impl From<io::Error> for FsError {
376    fn from(io_error: io::Error) -> Self {
377        match io_error.kind() {
378            io::ErrorKind::AddrInUse => FsError::AddressInUse,
379            io::ErrorKind::AddrNotAvailable => FsError::AddressNotAvailable,
380            io::ErrorKind::AlreadyExists => FsError::AlreadyExists,
381            io::ErrorKind::BrokenPipe => FsError::BrokenPipe,
382            io::ErrorKind::ConnectionAborted => FsError::ConnectionAborted,
383            io::ErrorKind::ConnectionRefused => FsError::ConnectionRefused,
384            io::ErrorKind::ConnectionReset => FsError::ConnectionReset,
385            io::ErrorKind::Interrupted => FsError::Interrupted,
386            io::ErrorKind::InvalidData => FsError::InvalidData,
387            io::ErrorKind::InvalidInput => FsError::InvalidInput,
388            io::ErrorKind::NotConnected => FsError::NotConnected,
389            io::ErrorKind::NotFound => FsError::EntityNotFound,
390            io::ErrorKind::PermissionDenied => FsError::PermissionDenied,
391            io::ErrorKind::TimedOut => FsError::TimedOut,
392            io::ErrorKind::UnexpectedEof => FsError::UnexpectedEof,
393            io::ErrorKind::WouldBlock => FsError::WouldBlock,
394            io::ErrorKind::WriteZero => FsError::WriteZero,
395            io::ErrorKind::Other => FsError::IOError,
396            // if the following triggers, a new error type was added to this non-exhaustive enum
397            _ => FsError::UnknownError,
398        }
399    }
400}
401
402#[derive(Debug)]
403pub struct ReadDir {
404    // TODO: to do this properly we need some kind of callback to the core FS abstraction
405    data: Vec<DirEntry>,
406    index: usize,
407}
408
409impl ReadDir {
410    pub fn new(data: Vec<DirEntry>) -> Self {
411        Self { data, index: 0 }
412    }
413}
414
415#[derive(Debug, Clone)]
416pub struct DirEntry {
417    pub path: PathBuf,
418    // weird hack, to fix this we probably need an internal trait object or callbacks or something
419    pub metadata: Result<Metadata>,
420}
421
422impl DirEntry {
423    pub fn path(&self) -> PathBuf {
424        self.path.clone()
425    }
426
427    pub fn metadata(&self) -> Result<Metadata> {
428        self.metadata.clone()
429    }
430
431    pub fn file_type(&self) -> Result<FileType> {
432        let metadata = self.metadata.clone()?;
433        Ok(metadata.file_type())
434    }
435
436    pub fn file_name(&self) -> OsString {
437        self.path
438            .file_name()
439            .unwrap_or(self.path.as_os_str())
440            .to_owned()
441    }
442}
443
444#[allow(clippy::len_without_is_empty)] // Clippy thinks it's an iterator.
445#[derive(Clone, Debug, Default)]
446// TODO: review this, proper solution would probably use a trait object internally
447pub struct Metadata {
448    pub ft: FileType,
449    pub accessed: u64,
450    pub created: u64,
451    pub modified: u64,
452    pub len: u64,
453}
454
455impl Metadata {
456    pub fn is_file(&self) -> bool {
457        self.ft.is_file()
458    }
459
460    pub fn is_dir(&self) -> bool {
461        self.ft.is_dir()
462    }
463
464    pub fn accessed(&self) -> u64 {
465        self.accessed
466    }
467
468    pub fn created(&self) -> u64 {
469        self.created
470    }
471
472    pub fn modified(&self) -> u64 {
473        self.modified
474    }
475
476    pub fn file_type(&self) -> FileType {
477        self.ft.clone()
478    }
479
480    pub fn len(&self) -> u64 {
481        self.len
482    }
483}
484
485#[derive(Clone, Debug, Default)]
486// TODO: review this, proper solution would probably use a trait object internally
487pub struct FileType {
488    pub dir: bool,
489    pub file: bool,
490    pub symlink: bool,
491    // TODO: the following 3 only exist on unix in the standard FS API.
492    // We should mirror that API and extend with that trait too.
493    pub char_device: bool,
494    pub block_device: bool,
495    pub socket: bool,
496    pub fifo: bool,
497}
498
499impl FileType {
500    pub fn is_dir(&self) -> bool {
501        self.dir
502    }
503    pub fn is_file(&self) -> bool {
504        self.file
505    }
506    pub fn is_symlink(&self) -> bool {
507        self.symlink
508    }
509    pub fn is_char_device(&self) -> bool {
510        self.char_device
511    }
512    pub fn is_block_device(&self) -> bool {
513        self.block_device
514    }
515    pub fn is_socket(&self) -> bool {
516        self.socket
517    }
518    pub fn is_fifo(&self) -> bool {
519        self.fifo
520    }
521}
522
523impl Iterator for ReadDir {
524    type Item = Result<DirEntry>;
525
526    fn next(&mut self) -> Option<Result<DirEntry>> {
527        if let Some(v) = self.data.get(self.index).cloned() {
528            self.index += 1;
529            return Some(Ok(v));
530        }
531        None
532    }
533}