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(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 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 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
198pub trait VirtualFile: fmt::Debug + Write + Read + Seek + Upcastable {
201 fn last_accessed(&self) -> u64;
203
204 fn last_modified(&self) -> u64;
206
207 fn created_time(&self) -> u64;
209
210 fn size(&self) -> u64;
212
213 fn set_len(&mut self, new_size: u64) -> Result<()>;
216
217 fn unlink(&mut self) -> Result<()>;
219
220 fn sync_to_disk(&self) -> Result<()> {
224 Ok(())
225 }
226
227 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 fn bytes_available_read(&self) -> Result<Option<usize>> {
236 Ok(None)
237 }
238
239 fn bytes_available_write(&self) -> Result<Option<usize>> {
242 Ok(None)
243 }
244
245 fn is_open(&self) -> bool {
248 true
249 }
250
251 fn get_fd(&self) -> Option<FileDescriptor> {
254 None
255 }
256}
257
258pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum StdioMode {
284 Piped,
286 Inherit,
288 Null,
290 Log,
292}
293
294#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
296pub enum FsError {
297 #[error("fd not a directory")]
299 BaseNotDirectory,
300 #[error("fd not a file")]
302 NotAFile,
303 #[error("invalid fd")]
305 InvalidFd,
306 #[error("file exists")]
308 AlreadyExists,
309 #[error("lock error")]
311 Lock,
312 #[error("io error")]
315 IOError,
316 #[error("address is in use")]
318 AddressInUse,
319 #[error("address could not be found")]
321 AddressNotAvailable,
322 #[error("broken pipe (was closed)")]
324 BrokenPipe,
325 #[error("connection aborted")]
327 ConnectionAborted,
328 #[error("connection refused")]
330 ConnectionRefused,
331 #[error("connection reset")]
333 ConnectionReset,
334 #[error("operation interrupted")]
336 Interrupted,
337 #[error("invalid internal data")]
339 InvalidData,
340 #[error("invalid input")]
342 InvalidInput,
343 #[error("connection is not open")]
345 NotConnected,
346 #[error("entity not found")]
348 EntityNotFound,
349 #[error("can't access device")]
351 NoDevice,
352 #[error("permission denied")]
354 PermissionDenied,
355 #[error("time out")]
357 TimedOut,
358 #[error("unexpected eof")]
360 UnexpectedEof,
361 #[error("blocking operation. try again")]
363 WouldBlock,
364 #[error("write returned 0")]
366 WriteZero,
367 #[error("directory not empty")]
369 DirectoryNotEmpty,
370 #[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 _ => FsError::UnknownError,
398 }
399 }
400}
401
402#[derive(Debug)]
403pub struct ReadDir {
404 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 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)] #[derive(Clone, Debug, Default)]
446pub 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)]
486pub struct FileType {
488 pub dir: bool,
489 pub file: bool,
490 pub symlink: bool,
491 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}