1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_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 null_file;
33pub mod passthru_fs;
34pub mod random_file;
35pub mod special_file;
36pub mod tmp_fs;
37pub mod union_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 null_file::*;
64pub use overlay_fs::OverlayFileSystem;
65pub use passthru_fs::*;
66pub use pipe::*;
67pub use special_file::*;
68pub use static_file::StaticFile;
69pub use tmp_fs::*;
70pub use trace_fs::TraceFileSystem;
71pub use union_fs::*;
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 ClonableVirtualFile: 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 remove_dir(&self, path: &Path) -> Result<()>;
93 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>;
94 fn metadata(&self, path: &Path) -> Result<Metadata>;
95 fn symlink_metadata(&self, path: &Path) -> Result<Metadata>;
99 fn remove_file(&self, path: &Path) -> Result<()>;
100
101 fn new_open_options(&self) -> OpenOptions<'_>;
102
103 fn mount(&self, name: String, path: &Path, fs: Box<dyn FileSystem + Send + Sync>)
104 -> Result<()>;
105}
106
107impl dyn FileSystem + 'static {
108 #[inline]
109 pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> {
110 self.upcast_any_ref().downcast_ref::<T>()
111 }
112 #[inline]
113 pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> {
114 self.upcast_any_mut().downcast_mut::<T>()
115 }
116}
117
118#[async_trait::async_trait]
119impl<D, F> FileSystem for D
120where
121 D: Deref<Target = F> + std::fmt::Debug + Send + Sync + 'static,
122 F: FileSystem + ?Sized,
123{
124 fn read_dir(&self, path: &Path) -> Result<ReadDir> {
125 (**self).read_dir(path)
126 }
127
128 fn readlink(&self, path: &Path) -> Result<PathBuf> {
129 (**self).readlink(path)
130 }
131
132 fn create_dir(&self, path: &Path) -> Result<()> {
133 (**self).create_dir(path)
134 }
135
136 fn remove_dir(&self, path: &Path) -> Result<()> {
137 (**self).remove_dir(path)
138 }
139
140 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
141 Box::pin(async { (**self).rename(from, to).await })
142 }
143
144 fn metadata(&self, path: &Path) -> Result<Metadata> {
145 (**self).metadata(path)
146 }
147
148 fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
149 (**self).symlink_metadata(path)
150 }
151
152 fn remove_file(&self, path: &Path) -> Result<()> {
153 (**self).remove_file(path)
154 }
155
156 fn new_open_options(&self) -> OpenOptions<'_> {
157 (**self).new_open_options()
158 }
159
160 fn mount(
161 &self,
162 name: String,
163 path: &Path,
164 fs: Box<dyn FileSystem + Send + Sync>,
165 ) -> Result<()> {
166 (**self).mount(name, path, fs)
167 }
168}
169
170pub trait FileOpener {
171 fn open(
172 &self,
173 path: &Path,
174 conf: &OpenOptionsConfig,
175 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>>;
176}
177
178#[derive(Debug, Clone)]
179pub struct OpenOptionsConfig {
180 pub read: bool,
181 pub write: bool,
182 pub create_new: bool,
183 pub create: bool,
184 pub append: bool,
185 pub truncate: bool,
186}
187
188impl OpenOptionsConfig {
189 pub fn minimum_rights(&self, parent_rights: &Self) -> Self {
191 Self {
192 read: parent_rights.read && self.read,
193 write: parent_rights.write && self.write,
194 create_new: parent_rights.create_new && self.create_new,
195 create: parent_rights.create && self.create,
196 append: parent_rights.append && self.append,
197 truncate: parent_rights.truncate && self.truncate,
198 }
199 }
200
201 pub const fn read(&self) -> bool {
202 self.read
203 }
204
205 pub const fn write(&self) -> bool {
206 self.write
207 }
208
209 pub const fn create_new(&self) -> bool {
210 self.create_new
211 }
212
213 pub const fn create(&self) -> bool {
214 self.create
215 }
216
217 pub const fn append(&self) -> bool {
218 self.append
219 }
220
221 pub const fn truncate(&self) -> bool {
222 self.truncate
223 }
224
225 pub const fn would_mutate(&self) -> bool {
228 let OpenOptionsConfig {
229 read: _,
230 write,
231 create_new,
232 create,
233 append,
234 truncate,
235 } = *self;
236 append || write || create || create_new || truncate
237 }
238}
239
240impl fmt::Debug for OpenOptions<'_> {
241 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242 self.conf.fmt(f)
243 }
244}
245
246pub struct OpenOptions<'a> {
247 opener: &'a dyn FileOpener,
248 conf: OpenOptionsConfig,
249}
250
251impl<'a> OpenOptions<'a> {
252 pub fn new(opener: &'a dyn FileOpener) -> Self {
253 Self {
254 opener,
255 conf: OpenOptionsConfig {
256 read: false,
257 write: false,
258 create_new: false,
259 create: false,
260 append: false,
261 truncate: false,
262 },
263 }
264 }
265
266 pub fn get_config(&self) -> OpenOptionsConfig {
267 self.conf.clone()
268 }
269
270 pub fn options(&mut self, options: OpenOptionsConfig) -> &mut Self {
272 self.conf = options;
273 self
274 }
275
276 pub fn read(&mut self, read: bool) -> &mut Self {
281 self.conf.read = read;
282 self
283 }
284
285 pub fn write(&mut self, write: bool) -> &mut Self {
293 self.conf.write = write;
294 self
295 }
296
297 pub fn append(&mut self, append: bool) -> &mut Self {
304 self.conf.append = append;
305 self
306 }
307
308 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
315 self.conf.truncate = truncate;
316 self
317 }
318
319 pub fn create(&mut self, create: bool) -> &mut Self {
321 self.conf.create = create;
322 self
323 }
324
325 pub fn create_new(&mut self, create_new: bool) -> &mut Self {
327 self.conf.create_new = create_new;
328 self
329 }
330
331 pub fn open<P: AsRef<Path>>(
332 &mut self,
333 path: P,
334 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
335 self.opener.open(path.as_ref(), &self.conf)
336 }
337}
338
339pub trait VirtualFile:
342 fmt::Debug + AsyncRead + AsyncWrite + AsyncSeek + Unpin + Upcastable + Send
343{
344 fn last_accessed(&self) -> u64;
346
347 fn last_modified(&self) -> u64;
349
350 fn created_time(&self) -> u64;
352
353 #[allow(unused_variables)]
354 fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
356 Ok(())
357 }
358
359 fn size(&self) -> u64;
361
362 fn set_len(&mut self, new_size: u64) -> Result<()>;
365
366 fn unlink(&mut self) -> Result<()>;
368
369 fn is_open(&self) -> bool {
372 true
373 }
374
375 fn get_special_fd(&self) -> Option<u32> {
379 None
380 }
381
382 fn write_from_mmap(&mut self, _offset: u64, _len: u64) -> std::io::Result<()> {
385 Err(std::io::ErrorKind::Unsupported.into())
386 }
387
388 fn copy_reference(
392 &mut self,
393 mut src: Box<dyn VirtualFile + Send + Sync + 'static>,
394 ) -> BoxFuture<'_, std::io::Result<()>> {
395 Box::pin(async move {
396 let bytes_written = tokio::io::copy(&mut src, self).await?;
397 tracing::trace!(bytes_written, "Copying file into host filesystem");
398 Ok(())
399 })
400 }
401
402 fn copy_from_owned_buffer(&mut self, src: &OwnedBuffer) -> BoxFuture<'_, std::io::Result<()>> {
406 let src = src.clone();
407 Box::pin(async move {
408 let mut bytes = src.as_slice();
409 let bytes_written = tokio::io::copy(&mut bytes, self).await?;
410 tracing::trace!(bytes_written, "Copying file into host filesystem");
411 Ok(())
412 })
413 }
414
415 fn as_owned_buffer(&self) -> Option<OwnedBuffer> {
422 None
423 }
424
425 fn poll_read_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
427
428 fn poll_write_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>>;
430}
431
432pub trait Upcastable {
435 fn upcast_any_ref(&'_ self) -> &'_ dyn Any;
436 fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any;
437 fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
438}
439
440impl<T: Any + fmt::Debug + 'static> Upcastable for T {
441 #[inline]
442 fn upcast_any_ref(&'_ self) -> &'_ dyn Any {
443 self
444 }
445 #[inline]
446 fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any {
447 self
448 }
449 #[inline]
450 fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
451 self
452 }
453}
454
455#[derive(Debug, Copy, Clone, PartialEq, Eq)]
457pub enum StdioMode {
458 Piped,
460 Inherit,
462 Null,
464 Log,
466}
467
468#[derive(Error, Copy, Clone, Debug, PartialEq, Eq)]
470pub enum FsError {
471 #[error("fd not a directory")]
473 BaseNotDirectory,
474 #[error("fd not a file")]
476 NotAFile,
477 #[error("invalid fd")]
479 InvalidFd,
480 #[error("file exists")]
482 AlreadyExists,
483 #[error("lock error")]
485 Lock,
486 #[error("io error")]
489 IOError,
490 #[error("address is in use")]
492 AddressInUse,
493 #[error("address could not be found")]
495 AddressNotAvailable,
496 #[error("broken pipe (was closed)")]
498 BrokenPipe,
499 #[error("connection aborted")]
501 ConnectionAborted,
502 #[error("connection refused")]
504 ConnectionRefused,
505 #[error("connection reset")]
507 ConnectionReset,
508 #[error("operation interrupted")]
510 Interrupted,
511 #[error("invalid internal data")]
513 InvalidData,
514 #[error("invalid input")]
516 InvalidInput,
517 #[error("connection is not open")]
519 NotConnected,
520 #[error("entry not found")]
522 EntryNotFound,
523 #[error("can't access device")]
525 NoDevice,
526 #[error("permission denied")]
528 PermissionDenied,
529 #[error("time out")]
531 TimedOut,
532 #[error("unexpected eof")]
534 UnexpectedEof,
535 #[error("blocking operation. try again")]
537 WouldBlock,
538 #[error("write returned 0")]
540 WriteZero,
541 #[error("directory not empty")]
543 DirectoryNotEmpty,
544 #[error("storage full")]
545 StorageFull,
546 #[error("unknown error found")]
548 UnknownError,
549 #[error("unsupported")]
551 Unsupported,
552}
553
554impl From<io::Error> for FsError {
555 fn from(io_error: io::Error) -> Self {
556 match io_error.kind() {
557 io::ErrorKind::AddrInUse => FsError::AddressInUse,
558 io::ErrorKind::AddrNotAvailable => FsError::AddressNotAvailable,
559 io::ErrorKind::AlreadyExists => FsError::AlreadyExists,
560 io::ErrorKind::BrokenPipe => FsError::BrokenPipe,
561 io::ErrorKind::ConnectionAborted => FsError::ConnectionAborted,
562 io::ErrorKind::ConnectionRefused => FsError::ConnectionRefused,
563 io::ErrorKind::ConnectionReset => FsError::ConnectionReset,
564 io::ErrorKind::Interrupted => FsError::Interrupted,
565 io::ErrorKind::InvalidData => FsError::InvalidData,
566 io::ErrorKind::InvalidInput => FsError::InvalidInput,
567 io::ErrorKind::NotConnected => FsError::NotConnected,
568 io::ErrorKind::NotFound => FsError::EntryNotFound,
569 io::ErrorKind::PermissionDenied => FsError::PermissionDenied,
570 io::ErrorKind::TimedOut => FsError::TimedOut,
571 io::ErrorKind::UnexpectedEof => FsError::UnexpectedEof,
572 io::ErrorKind::WouldBlock => FsError::WouldBlock,
573 io::ErrorKind::WriteZero => FsError::WriteZero,
574 io::ErrorKind::Other => FsError::IOError,
577 _ => FsError::UnknownError,
579 }
580 }
581}
582
583impl From<FsError> for io::Error {
584 fn from(val: FsError) -> Self {
585 let kind = match val {
586 FsError::AddressInUse => io::ErrorKind::AddrInUse,
587 FsError::AddressNotAvailable => io::ErrorKind::AddrNotAvailable,
588 FsError::AlreadyExists => io::ErrorKind::AlreadyExists,
589 FsError::BrokenPipe => io::ErrorKind::BrokenPipe,
590 FsError::ConnectionAborted => io::ErrorKind::ConnectionAborted,
591 FsError::ConnectionRefused => io::ErrorKind::ConnectionRefused,
592 FsError::ConnectionReset => io::ErrorKind::ConnectionReset,
593 FsError::Interrupted => io::ErrorKind::Interrupted,
594 FsError::InvalidData => io::ErrorKind::InvalidData,
595 FsError::InvalidInput => io::ErrorKind::InvalidInput,
596 FsError::NotConnected => io::ErrorKind::NotConnected,
597 FsError::EntryNotFound => io::ErrorKind::NotFound,
598 FsError::PermissionDenied => io::ErrorKind::PermissionDenied,
599 FsError::TimedOut => io::ErrorKind::TimedOut,
600 FsError::UnexpectedEof => io::ErrorKind::UnexpectedEof,
601 FsError::WouldBlock => io::ErrorKind::WouldBlock,
602 FsError::WriteZero => io::ErrorKind::WriteZero,
603 FsError::IOError => io::ErrorKind::Other,
604 FsError::BaseNotDirectory => io::ErrorKind::Other,
605 FsError::NotAFile => io::ErrorKind::Other,
606 FsError::InvalidFd => io::ErrorKind::Other,
607 FsError::Lock => io::ErrorKind::Other,
608 FsError::NoDevice => io::ErrorKind::Other,
609 FsError::DirectoryNotEmpty => io::ErrorKind::Other,
610 FsError::UnknownError => io::ErrorKind::Other,
611 FsError::StorageFull => io::ErrorKind::Other,
612 FsError::Unsupported => io::ErrorKind::Unsupported,
613 };
616 kind.into()
617 }
618}
619
620#[derive(Debug)]
621pub struct ReadDir {
622 pub(crate) data: Vec<DirEntry>,
624 index: usize,
625}
626
627impl ReadDir {
628 pub fn new(data: Vec<DirEntry>) -> Self {
629 Self { data, index: 0 }
630 }
631 pub fn is_empty(&self) -> bool {
632 self.data.is_empty()
633 }
634}
635
636#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct DirEntry {
638 pub path: PathBuf,
639 pub metadata: Result<Metadata>,
641}
642
643impl DirEntry {
644 pub fn path(&self) -> PathBuf {
645 self.path.clone()
646 }
647
648 pub fn metadata(&self) -> Result<Metadata> {
649 self.metadata.clone()
650 }
651
652 pub fn file_type(&self) -> Result<FileType> {
653 let metadata = self.metadata.clone()?;
654 Ok(metadata.file_type())
655 }
656
657 pub fn file_name(&self) -> OsString {
658 self.path
659 .file_name()
660 .unwrap_or(self.path.as_os_str())
661 .to_owned()
662 }
663
664 pub fn is_white_out(&self) -> Option<PathBuf> {
665 ops::is_white_out(&self.path)
666 }
667}
668
669#[allow(clippy::len_without_is_empty)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
671pub struct Metadata {
673 pub ft: FileType,
674 pub accessed: u64,
675 pub created: u64,
676 pub modified: u64,
677 pub len: u64,
678}
679
680impl Metadata {
681 pub fn is_file(&self) -> bool {
682 self.ft.is_file()
683 }
684
685 pub fn is_dir(&self) -> bool {
686 self.ft.is_dir()
687 }
688
689 pub fn accessed(&self) -> u64 {
690 self.accessed
691 }
692
693 pub fn created(&self) -> u64 {
694 self.created
695 }
696
697 pub fn modified(&self) -> u64 {
698 self.modified
699 }
700
701 pub fn file_type(&self) -> FileType {
702 self.ft.clone()
703 }
704
705 pub fn len(&self) -> u64 {
706 self.len
707 }
708}
709
710#[derive(Clone, Debug, Default, PartialEq, Eq)]
711pub struct FileType {
713 pub dir: bool,
714 pub file: bool,
715 pub symlink: bool,
716 pub char_device: bool,
719 pub block_device: bool,
720 pub socket: bool,
721 pub fifo: bool,
722}
723
724impl FileType {
725 pub fn new_dir() -> Self {
726 Self {
727 dir: true,
728 ..Default::default()
729 }
730 }
731
732 pub fn new_file() -> Self {
733 Self {
734 file: true,
735 ..Default::default()
736 }
737 }
738
739 pub fn is_dir(&self) -> bool {
740 self.dir
741 }
742 pub fn is_file(&self) -> bool {
743 self.file
744 }
745 pub fn is_symlink(&self) -> bool {
746 self.symlink
747 }
748 pub fn is_char_device(&self) -> bool {
749 self.char_device
750 }
751 pub fn is_block_device(&self) -> bool {
752 self.block_device
753 }
754 pub fn is_socket(&self) -> bool {
755 self.socket
756 }
757 pub fn is_fifo(&self) -> bool {
758 self.fifo
759 }
760}
761
762impl Iterator for ReadDir {
763 type Item = Result<DirEntry>;
764
765 fn next(&mut self) -> Option<Result<DirEntry>> {
766 if let Some(v) = self.data.get(self.index).cloned() {
767 self.index += 1;
768 return Some(Ok(v));
769 }
770 None
771 }
772}