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;
39mod 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
78pub 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 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 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 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 pub fn options(&mut self, options: OpenOptionsConfig) -> &mut Self {
270 self.conf = options;
271 self
272 }
273
274 pub fn read(&mut self, read: bool) -> &mut Self {
279 self.conf.read = read;
280 self
281 }
282
283 pub fn write(&mut self, write: bool) -> &mut Self {
291 self.conf.write = write;
292 self
293 }
294
295 pub fn append(&mut self, append: bool) -> &mut Self {
302 self.conf.append = append;
303 self
304 }
305
306 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
313 self.conf.truncate = truncate;
314 self
315 }
316
317 pub fn create(&mut self, create: bool) -> &mut Self {
319 self.conf.create = create;
320 self
321 }
322
323 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
337pub trait VirtualFile:
340 fmt::Debug + AsyncRead + AsyncWrite + AsyncSeek + Unpin + Upcastable + Send
341{
342 fn last_accessed(&self) -> u64;
344
345 fn last_modified(&self) -> u64;
347
348 fn created_time(&self) -> u64;
350
351 #[allow(unused_variables)]
352 fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
354 Ok(())
355 }
356
357 fn size(&self) -> u64;
359
360 fn set_len(&mut self, new_size: u64) -> Result<()>;
363
364 fn unlink(&mut self) -> Result<()>;
370
371 fn is_open(&self) -> bool {
374 true
375 }
376
377 fn get_special_fd(&self) -> Option<u32> {
381 None
382 }
383
384 fn write_from_mmap(&mut self, _offset: u64, _len: u64) -> std::io::Result<()> {
387 Err(std::io::ErrorKind::Unsupported.into())
388 }
389
390 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 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 fn as_owned_buffer(&self) -> Option<OwnedBuffer> {
424 None
425 }
426
427 fn poll_read_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
429
430 fn poll_write_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
432}
433
434pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
459pub enum StdioMode {
460 Piped,
462 Inherit,
464 Null,
466 Log,
468}
469
470#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
472pub enum FsError {
473 #[error("fd not a directory")]
475 BaseNotDirectory,
476 #[error("fd not a file")]
478 NotAFile,
479 #[error("invalid fd")]
481 InvalidFd,
482 #[error("file exists")]
484 AlreadyExists,
485 #[error("lock error")]
487 Lock,
488 #[error("io error")]
491 IOError,
492 #[error("address is in use")]
494 AddressInUse,
495 #[error("address could not be found")]
497 AddressNotAvailable,
498 #[error("broken pipe (was closed)")]
500 BrokenPipe,
501 #[error("connection aborted")]
503 ConnectionAborted,
504 #[error("connection refused")]
506 ConnectionRefused,
507 #[error("connection reset")]
509 ConnectionReset,
510 #[error("operation interrupted")]
512 Interrupted,
513 #[error("invalid internal data")]
515 InvalidData,
516 #[error("invalid input")]
518 InvalidInput,
519 #[error("connection is not open")]
521 NotConnected,
522 #[error("entry not found")]
524 EntryNotFound,
525 #[error("can't access device")]
527 NoDevice,
528 #[error("permission denied")]
530 PermissionDenied,
531 #[error("time out")]
533 TimedOut,
534 #[error("unexpected eof")]
536 UnexpectedEof,
537 #[error("blocking operation. try again")]
539 WouldBlock,
540 #[error("write returned 0")]
542 WriteZero,
543 #[error("directory not empty")]
545 DirectoryNotEmpty,
546 #[error("storage full")]
547 StorageFull,
548 #[error("unknown error found")]
550 UnknownError,
551 #[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 io::ErrorKind::Other => FsError::IOError,
579 _ => 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 };
618 kind.into()
619 }
620}
621
622#[derive(Debug)]
623pub struct ReadDir {
624 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 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)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
673pub 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)]
713pub struct FileType {
715 pub dir: bool,
716 pub file: bool,
717 pub symlink: bool,
718 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}