1mod fd;
9mod fd_list;
10mod inode_guard;
11mod notification;
12pub(crate) mod relative_path_hack;
13
14use std::{
15 borrow::{Borrow, Cow},
16 collections::{HashMap, HashSet, VecDeque},
17 ops::{Deref, DerefMut},
18 path::{Component, Path, PathBuf},
19 pin::Pin,
20 sync::{
21 Arc, Mutex, RwLock, Weak,
22 atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering},
23 },
24 task::{Context, Poll},
25};
26
27use self::fd_list::FdList;
28use crate::{
29 net::socket::InodeSocketKind,
30 state::{Stderr, Stdin, Stdout},
31};
32use futures::{Future, TryStreamExt, future::BoxFuture};
33#[cfg(feature = "enable-serde")]
34use serde_derive::{Deserialize, Serialize};
35use tokio::io::AsyncWriteExt;
36use tracing::{debug, trace};
37use virtual_fs::{
38 FileSystem, FsError, OpenOptions, UnionFileSystem, VirtualFile, copy_reference,
39 tmp_fs::TmpFileSystem,
40};
41use wasmer_config::package::PackageId;
42use wasmer_wasix_types::{
43 types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO},
44 wasi::{
45 Errno, Fd as WasiFd, Fdflags, Fdflagsext, Fdstat, Filesize, Filestat, Filetype,
46 Preopentype, Prestat, PrestatEnum, Rights, Socktype,
47 },
48};
49
50pub use self::fd::{EpollFd, EpollInterest, EpollJoinGuard, Fd, FdInner, InodeVal, Kind};
51pub(crate) use self::inode_guard::{
52 InodeValFilePollGuard, InodeValFilePollGuardJoin, InodeValFilePollGuardMode,
53 InodeValFileReadGuard, InodeValFileWriteGuard, POLL_GUARD_MAX_RET, WasiStateFileGuard,
54};
55pub use self::notification::NotificationInner;
56use self::relative_path_hack::RelativeOrAbsolutePathHack;
57use crate::syscalls::map_io_err;
58use crate::{ALL_RIGHTS, bin_factory::BinaryPackage, state::PreopenedDir};
59
60pub const VIRTUAL_ROOT_FD: WasiFd = 3;
74
75pub const FS_STDIN_INO: Inode = Inode(10);
78pub const FS_STDOUT_INO: Inode = Inode(11);
79pub const FS_STDERR_INO: Inode = Inode(12);
80pub const FS_ROOT_INO: Inode = Inode(13);
81
82const STDIN_DEFAULT_RIGHTS: Rights = {
83 Rights::from_bits_truncate(
86 Rights::FD_DATASYNC.bits()
87 | Rights::FD_READ.bits()
88 | Rights::FD_SYNC.bits()
89 | Rights::FD_ADVISE.bits()
90 | Rights::FD_FILESTAT_GET.bits()
91 | Rights::FD_FDSTAT_SET_FLAGS.bits()
92 | Rights::POLL_FD_READWRITE.bits(),
93 )
94};
95const STDOUT_DEFAULT_RIGHTS: Rights = {
96 Rights::from_bits_truncate(
99 Rights::FD_DATASYNC.bits()
100 | Rights::FD_SYNC.bits()
101 | Rights::FD_WRITE.bits()
102 | Rights::FD_ADVISE.bits()
103 | Rights::FD_FILESTAT_GET.bits()
104 | Rights::FD_FDSTAT_SET_FLAGS.bits()
105 | Rights::POLL_FD_READWRITE.bits(),
106 )
107};
108const STDERR_DEFAULT_RIGHTS: Rights = STDOUT_DEFAULT_RIGHTS;
109
110pub const MAX_SYMLINKS: u32 = 128;
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
115#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
116pub struct Inode(u64);
117
118impl Inode {
119 pub fn as_u64(&self) -> u64 {
120 self.0
121 }
122
123 pub fn from_path(str: &str) -> Self {
124 Inode(xxhash_rust::xxh64::xxh64(str.as_bytes(), 0))
125 }
126}
127
128#[derive(Debug, Clone)]
129pub struct InodeGuard {
130 ino: Inode,
131 inner: Arc<InodeVal>,
132
133 open_handles: Arc<AtomicI32>,
138}
139impl InodeGuard {
140 pub fn ino(&self) -> Inode {
141 self.ino
142 }
143
144 pub fn downgrade(&self) -> InodeWeakGuard {
145 InodeWeakGuard {
146 ino: self.ino,
147 open_handles: self.open_handles.clone(),
148 inner: Arc::downgrade(&self.inner),
149 }
150 }
151
152 pub fn ref_cnt(&self) -> usize {
153 Arc::strong_count(&self.inner)
154 }
155
156 pub fn handle_count(&self) -> u32 {
157 self.open_handles.load(Ordering::SeqCst) as u32
158 }
159
160 pub fn acquire_handle(&self) {
161 let prev_handles = self.open_handles.fetch_add(1, Ordering::SeqCst);
162 trace!(ino = %self.ino.0, new_count = %(prev_handles + 1), "acquiring handle for InodeGuard");
163 }
164
165 pub fn drop_one_handle(&self) {
166 let prev_handles = self.open_handles.fetch_sub(1, Ordering::SeqCst);
167
168 trace!(ino = %self.ino.0, %prev_handles, "dropping handle for InodeGuard");
169
170 if prev_handles > 1 {
172 return;
173 }
174
175 let mut guard = self.inner.write();
177
178 if prev_handles != 1 {
183 panic!("InodeGuard handle dropped too many times");
184 }
185
186 if self.open_handles.load(Ordering::SeqCst) != 0 {
188 return;
189 }
190
191 let ino = self.ino.0;
192 trace!(%ino, "InodeGuard has no more open handles");
193
194 match guard.deref_mut() {
195 Kind::File { handle, .. } if handle.is_some() => {
196 let file_ref_count = Arc::strong_count(handle.as_ref().unwrap());
197 trace!(%file_ref_count, %ino, "dropping file handle");
198 drop(handle.take().unwrap());
199 }
200 Kind::PipeRx { rx } => {
201 trace!(%ino, "closing pipe rx");
202 rx.close();
203 }
204 Kind::PipeTx { tx } => {
205 trace!(%ino, "closing pipe tx");
206 tx.close();
207 }
208 _ => (),
209 }
210 }
211}
212impl std::ops::Deref for InodeGuard {
213 type Target = InodeVal;
214 fn deref(&self) -> &Self::Target {
215 self.inner.deref()
216 }
217}
218
219#[derive(Debug, Clone)]
220pub struct InodeWeakGuard {
221 ino: Inode,
222 open_handles: Arc<AtomicI32>,
226 inner: Weak<InodeVal>,
227}
228impl InodeWeakGuard {
229 pub fn ino(&self) -> Inode {
230 self.ino
231 }
232 pub fn upgrade(&self) -> Option<InodeGuard> {
233 Weak::upgrade(&self.inner).map(|inner| InodeGuard {
234 ino: self.ino,
235 open_handles: self.open_handles.clone(),
236 inner,
237 })
238 }
239}
240
241#[derive(Debug)]
242#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
243struct WasiInodesProtected {
244 lookup: HashMap<Inode, Weak<InodeVal>>,
245}
246
247#[derive(Clone, Debug)]
248#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
249pub struct WasiInodes {
250 protected: Arc<RwLock<WasiInodesProtected>>,
251}
252
253impl WasiInodes {
254 pub fn new() -> Self {
255 Self {
256 protected: Arc::new(RwLock::new(WasiInodesProtected {
257 lookup: Default::default(),
258 })),
259 }
260 }
261
262 pub fn add_inode_val(&self, val: InodeVal) -> InodeGuard {
264 let val = Arc::new(val);
265 let st_ino = {
266 let guard = val.stat.read().unwrap();
267 guard.st_ino
268 };
269
270 let mut guard = self.protected.write().unwrap();
271 let ino = Inode(st_ino);
272 guard.lookup.insert(ino, Arc::downgrade(&val));
273
274 if guard.lookup.len() % 100 == 1 {
276 guard.lookup.retain(|_, v| Weak::strong_count(v) > 0);
277 }
278
279 let open_handles = Arc::new(AtomicI32::new(0));
280
281 InodeGuard {
282 ino,
283 open_handles,
284 inner: val,
285 }
286 }
287
288 pub(crate) fn stdout(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
290 Self::std_dev_get(fd_map, __WASI_STDOUT_FILENO)
291 }
292 pub(crate) fn stdout_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
294 Self::std_dev_get_mut(fd_map, __WASI_STDOUT_FILENO)
295 }
296
297 pub(crate) fn stderr(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
299 Self::std_dev_get(fd_map, __WASI_STDERR_FILENO)
300 }
301 pub(crate) fn stderr_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
303 Self::std_dev_get_mut(fd_map, __WASI_STDERR_FILENO)
304 }
305
306 #[allow(dead_code)]
309 pub(crate) fn stdin(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
310 Self::std_dev_get(fd_map, __WASI_STDIN_FILENO)
311 }
312 pub(crate) fn stdin_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
314 Self::std_dev_get_mut(fd_map, __WASI_STDIN_FILENO)
315 }
316
317 fn std_dev_get(fd_map: &RwLock<FdList>, fd: WasiFd) -> Result<InodeValFileReadGuard, FsError> {
320 if let Some(fd) = fd_map.read().unwrap().get(fd) {
321 let guard = fd.inode.read();
322 if let Kind::File {
323 handle: Some(handle),
324 ..
325 } = guard.deref()
326 {
327 Ok(InodeValFileReadGuard::new(handle))
328 } else {
329 Err(FsError::NotAFile)
331 }
332 } else {
333 Err(FsError::NoDevice)
335 }
336 }
337 fn std_dev_get_mut(
340 fd_map: &RwLock<FdList>,
341 fd: WasiFd,
342 ) -> Result<InodeValFileWriteGuard, FsError> {
343 if let Some(fd) = fd_map.read().unwrap().get(fd) {
344 let guard = fd.inode.read();
345 if let Kind::File {
346 handle: Some(handle),
347 ..
348 } = guard.deref()
349 {
350 Ok(InodeValFileWriteGuard::new(handle))
351 } else {
352 Err(FsError::NotAFile)
354 }
355 } else {
356 Err(FsError::NoDevice)
358 }
359 }
360}
361
362impl Default for WasiInodes {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368#[derive(Debug, Clone)]
369pub enum WasiFsRoot {
370 Sandbox(TmpFileSystem),
371 Overlay(
381 Arc<
382 virtual_fs::OverlayFileSystem<
383 TmpFileSystem,
384 [RelativeOrAbsolutePathHack<UnionFileSystem>; 1],
385 >,
386 >,
387 ),
388 Backing(Arc<dyn FileSystem + Send + Sync>),
389}
390
391impl FileSystem for WasiFsRoot {
392 fn readlink(&self, path: &Path) -> virtual_fs::Result<PathBuf> {
393 match self {
394 Self::Sandbox(fs) => fs.readlink(path),
395 Self::Overlay(overlay) => overlay.readlink(path),
396 Self::Backing(fs) => fs.readlink(path),
397 }
398 }
399
400 fn read_dir(&self, path: &Path) -> virtual_fs::Result<virtual_fs::ReadDir> {
401 match self {
402 Self::Sandbox(fs) => fs.read_dir(path),
403 Self::Overlay(overlay) => overlay.read_dir(path),
404 Self::Backing(fs) => fs.read_dir(path),
405 }
406 }
407
408 fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> {
409 match self {
410 Self::Sandbox(fs) => fs.create_dir(path),
411 Self::Overlay(overlay) => overlay.create_dir(path),
412 Self::Backing(fs) => fs.create_dir(path),
413 }
414 }
415
416 fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> {
417 match self {
418 Self::Sandbox(fs) => fs.remove_dir(path),
419 Self::Overlay(overlay) => overlay.remove_dir(path),
420 Self::Backing(fs) => fs.remove_dir(path),
421 }
422 }
423
424 fn rename<'a>(&'a self, from: &Path, to: &Path) -> BoxFuture<'a, virtual_fs::Result<()>> {
425 let from = from.to_owned();
426 let to = to.to_owned();
427 let this = self.clone();
428 Box::pin(async move {
429 match this {
430 Self::Sandbox(fs) => fs.rename(&from, &to).await,
431 Self::Overlay(overlay) => overlay.rename(&from, &to).await,
432 Self::Backing(fs) => fs.rename(&from, &to).await,
433 }
434 })
435 }
436
437 fn metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
438 match self {
439 Self::Sandbox(fs) => fs.metadata(path),
440 Self::Overlay(overlay) => overlay.metadata(path),
441 Self::Backing(fs) => fs.metadata(path),
442 }
443 }
444
445 fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
446 match self {
447 Self::Sandbox(fs) => fs.symlink_metadata(path),
448 Self::Overlay(overlay) => overlay.symlink_metadata(path),
449 Self::Backing(fs) => fs.symlink_metadata(path),
450 }
451 }
452
453 fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> {
454 match self {
455 Self::Sandbox(fs) => fs.remove_file(path),
456 Self::Overlay(overlay) => overlay.remove_file(path),
457 Self::Backing(fs) => fs.remove_file(path),
458 }
459 }
460
461 fn new_open_options(&self) -> OpenOptions<'_> {
462 match self {
463 Self::Sandbox(fs) => fs.new_open_options(),
464 Self::Overlay(overlay) => overlay.new_open_options(),
465 Self::Backing(fs) => fs.new_open_options(),
466 }
467 }
468
469 fn mount(
470 &self,
471 name: String,
472 path: &Path,
473 fs: Box<dyn FileSystem + Send + Sync>,
474 ) -> virtual_fs::Result<()> {
475 match self {
476 Self::Sandbox(root) => FileSystem::mount(root, name, path, fs),
477 Self::Overlay(overlay) => FileSystem::mount(overlay.primary(), name, path, fs),
478 Self::Backing(f) => f.mount(name, path, fs),
479 }
480 }
481}
482
483#[tracing::instrument(level = "trace", skip_all)]
490async fn merge_filesystems(
491 source: &dyn FileSystem,
492 destination: &dyn FileSystem,
493) -> Result<(), virtual_fs::FsError> {
494 tracing::warn!("Falling back to a recursive copy to merge filesystems");
495 let files = futures::stream::FuturesUnordered::new();
496
497 let mut to_check = VecDeque::new();
498 to_check.push_back(PathBuf::from("/"));
499
500 while let Some(path) = to_check.pop_front() {
501 let metadata = match source.metadata(&path) {
502 Ok(m) => m,
503 Err(err) => {
504 tracing::debug!(path=%path.display(), source_fs=?source, ?err, "failed to get metadata for path while merging file systems");
505 return Err(err);
506 }
507 };
508
509 if metadata.is_dir() {
510 create_dir_all(destination, &path)?;
511
512 for entry in source.read_dir(&path)? {
513 let entry = entry?;
514 to_check.push_back(entry.path);
515 }
516 } else if metadata.is_file() {
517 files.push(async move {
518 copy_reference(source, destination, &path)
519 .await
520 .map_err(virtual_fs::FsError::from)
521 });
522 } else {
523 tracing::debug!(
524 path=%path.display(),
525 ?metadata,
526 "Skipping unknown file type while merging"
527 );
528 }
529 }
530
531 files.try_collect().await
532}
533
534fn create_dir_all(fs: &dyn FileSystem, path: &Path) -> Result<(), virtual_fs::FsError> {
535 if fs.metadata(path).is_ok() {
536 return Ok(());
537 }
538
539 if let Some(parent) = path.parent() {
540 create_dir_all(fs, parent)?;
541 }
542
543 fs.create_dir(path)?;
544
545 Ok(())
546}
547
548#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
551pub struct WasiFs {
552 pub preopen_fds: RwLock<Vec<u32>>,
554 pub fd_map: RwLock<FdList>,
555 pub current_dir: Mutex<String>,
556 #[cfg_attr(feature = "enable-serde", serde(skip, default))]
557 pub root_fs: WasiFsRoot,
558 pub root_inode: InodeGuard,
559 pub has_unioned: Mutex<HashSet<PackageId>>,
560
561 is_wasix: AtomicBool,
566
567 pub(crate) init_preopens: Vec<PreopenedDir>,
569 pub(crate) init_vfs_preopens: Vec<String>,
571}
572
573impl WasiFs {
574 pub fn is_wasix(&self) -> bool {
575 self.is_wasix.load(Ordering::Relaxed)
578 }
579
580 pub fn set_is_wasix(&self, is_wasix: bool) {
581 self.is_wasix.store(is_wasix, Ordering::SeqCst);
582 }
583
584 pub fn fork(&self) -> Self {
586 Self {
587 preopen_fds: RwLock::new(self.preopen_fds.read().unwrap().clone()),
588 fd_map: RwLock::new(self.fd_map.read().unwrap().clone()),
589 current_dir: Mutex::new(self.current_dir.lock().unwrap().clone()),
590 is_wasix: AtomicBool::new(self.is_wasix.load(Ordering::Acquire)),
591 root_fs: self.root_fs.clone(),
592 root_inode: self.root_inode.clone(),
593 has_unioned: Mutex::new(self.has_unioned.lock().unwrap().clone()),
594 init_preopens: self.init_preopens.clone(),
595 init_vfs_preopens: self.init_vfs_preopens.clone(),
596 }
597 }
598
599 pub async fn close_cloexec_fds(&self) {
601 let to_close = {
602 if let Ok(map) = self.fd_map.read() {
603 map.iter()
604 .filter_map(|(k, v)| {
605 if v.inner.fd_flags.contains(Fdflagsext::CLOEXEC)
606 && !v.is_stdio
607 && !v.inode.is_preopened
608 {
609 tracing::trace!(fd = %k, "Closing FD due to CLOEXEC flag");
610 Some(k)
611 } else {
612 None
613 }
614 })
615 .collect::<HashSet<_>>()
616 } else {
617 HashSet::new()
618 }
619 };
620
621 let _ = tokio::join!(async {
622 for fd in &to_close {
623 self.flush(*fd).await.ok();
624 self.close_fd(*fd).ok();
625 }
626 });
627
628 if let Ok(mut map) = self.fd_map.write() {
629 for fd in &to_close {
630 map.remove(*fd);
631 }
632 }
633 }
634
635 pub async fn close_all(&self) {
637 let mut to_close = {
638 if let Ok(map) = self.fd_map.read() {
639 map.keys().collect::<HashSet<_>>()
640 } else {
641 HashSet::new()
642 }
643 };
644 to_close.insert(__WASI_STDOUT_FILENO);
645 to_close.insert(__WASI_STDERR_FILENO);
646
647 let _ = tokio::join!(async {
648 for fd in to_close {
649 self.flush(fd).await.ok();
650 self.close_fd(fd).ok();
651 }
652 });
653
654 if let Ok(mut map) = self.fd_map.write() {
655 map.clear();
656 }
657 }
658
659 pub async fn conditional_union(
662 &self,
663 binary: &BinaryPackage,
664 ) -> Result<(), virtual_fs::FsError> {
665 let Some(webc_fs) = &binary.webc_fs else {
666 return Ok(());
667 };
668
669 let needs_to_be_unioned = self.has_unioned.lock().unwrap().insert(binary.id.clone());
670 if !needs_to_be_unioned {
671 return Ok(());
672 }
673
674 match &self.root_fs {
675 WasiFsRoot::Sandbox(fs) => {
676 let fdyn: Arc<dyn FileSystem + Send + Sync> = webc_fs.clone();
678 fs.union(&fdyn);
679 Ok(())
680 }
681 WasiFsRoot::Overlay(overlay) => {
682 let union = &overlay.secondaries()[0];
683 union.0.merge(webc_fs, virtual_fs::UnionMergeMode::Skip)
684 }
685 WasiFsRoot::Backing(backing) => merge_filesystems(webc_fs, backing).await,
686 }
687 }
688
689 pub(crate) fn new_with_preopen(
691 inodes: &WasiInodes,
692 preopens: &[PreopenedDir],
693 vfs_preopens: &[String],
694 fs_backing: WasiFsRoot,
695 ) -> Result<Self, String> {
696 let mut wasi_fs = Self::new_init(fs_backing, inodes, FS_ROOT_INO)?;
697 wasi_fs.init_preopens = preopens.to_vec();
698 wasi_fs.init_vfs_preopens = vfs_preopens.to_vec();
699 wasi_fs.create_preopens(inodes, false)?;
700 Ok(wasi_fs)
701 }
702
703 pub(crate) fn relative_path_to_absolute(&self, mut path: String) -> String {
705 if !path.starts_with("/") {
706 let current_dir = self.current_dir.lock().unwrap();
707 path = format!("{}{}", current_dir.as_str(), &path[1..]);
708 if path.contains("//") {
709 path = path.replace("//", "/");
710 }
711 }
712 path
713 }
714
715 fn new_init(
718 fs_backing: WasiFsRoot,
719 inodes: &WasiInodes,
720 st_ino: Inode,
721 ) -> Result<Self, String> {
722 debug!("Initializing WASI filesystem");
723
724 let stat = Filestat {
725 st_filetype: Filetype::Directory,
726 st_ino: st_ino.as_u64(),
727 ..Filestat::default()
728 };
729 let root_kind = Kind::Root {
730 entries: HashMap::new(),
731 };
732 let root_inode = inodes.add_inode_val(InodeVal {
733 stat: RwLock::new(stat),
734 is_preopened: true,
735 name: RwLock::new("/".into()),
736 kind: RwLock::new(root_kind),
737 });
738
739 let wasi_fs = Self {
740 preopen_fds: RwLock::new(vec![]),
741 fd_map: RwLock::new(FdList::new()),
742 current_dir: Mutex::new("/".to_string()),
743 is_wasix: AtomicBool::new(false),
744 root_fs: fs_backing,
745 root_inode,
746 has_unioned: Mutex::new(HashSet::new()),
747 init_preopens: Default::default(),
748 init_vfs_preopens: Default::default(),
749 };
750 wasi_fs.create_stdin(inodes);
751 wasi_fs.create_stdout(inodes);
752 wasi_fs.create_stderr(inodes);
753 wasi_fs.create_rootfd()?;
754
755 Ok(wasi_fs)
756 }
757
758 #[allow(dead_code)]
768 #[allow(clippy::too_many_arguments)]
769 pub unsafe fn open_dir_all(
770 &mut self,
771 inodes: &WasiInodes,
772 base: WasiFd,
773 name: String,
774 rights: Rights,
775 rights_inheriting: Rights,
776 flags: Fdflags,
777 fd_flags: Fdflagsext,
778 ) -> Result<WasiFd, FsError> {
779 let mut cur_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
782
783 let path: &Path = Path::new(&name);
784 for c in path.components() {
786 let segment_name = c.as_os_str().to_string_lossy().to_string();
787 let guard = cur_inode.read();
788 match guard.deref() {
789 Kind::Dir { entries, .. } | Kind::Root { entries } => {
790 if let Some(_entry) = entries.get(&segment_name) {
791 return Err(FsError::AlreadyExists);
793 }
794
795 let kind = Kind::Dir {
796 parent: cur_inode.downgrade(),
797 path: PathBuf::from(""),
798 entries: HashMap::new(),
799 };
800
801 drop(guard);
802 let inode = self.create_inode_with_default_stat(
803 inodes,
804 kind,
805 false,
806 segment_name.clone().into(),
807 );
808
809 {
811 let mut guard = cur_inode.write();
812 match guard.deref_mut() {
813 Kind::Dir { entries, .. } | Kind::Root { entries } => {
814 entries.insert(segment_name, inode.clone());
815 }
816 _ => unreachable!("Dir or Root became not Dir or Root"),
817 }
818 }
819 cur_inode = inode;
820 }
821 _ => return Err(FsError::BaseNotDirectory),
822 }
823 }
824
825 self.create_fd(
827 rights,
828 rights_inheriting,
829 flags,
830 fd_flags,
831 Fd::READ | Fd::WRITE,
832 cur_inode,
833 )
834 .map_err(fs_error_from_wasi_err)
835 }
836
837 #[allow(dead_code, clippy::too_many_arguments)]
842 pub fn open_file_at(
843 &mut self,
844 inodes: &WasiInodes,
845 base: WasiFd,
846 file: Box<dyn VirtualFile + Send + Sync + 'static>,
847 open_flags: u16,
848 name: String,
849 rights: Rights,
850 rights_inheriting: Rights,
851 flags: Fdflags,
852 fd_flags: Fdflagsext,
853 ) -> Result<WasiFd, FsError> {
854 let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
857
858 let guard = base_inode.read();
859 match guard.deref() {
860 Kind::Dir { entries, .. } | Kind::Root { entries } => {
861 if let Some(_entry) = entries.get(&name) {
862 return Err(FsError::AlreadyExists);
864 }
865
866 let kind = Kind::File {
867 handle: Some(Arc::new(RwLock::new(file))),
868 path: PathBuf::from(""),
869 fd: None,
870 };
871
872 drop(guard);
873 let inode = self
874 .create_inode(inodes, kind, false, name.clone())
875 .map_err(|_| FsError::IOError)?;
876
877 {
878 let mut guard = base_inode.write();
879 match guard.deref_mut() {
880 Kind::Dir { entries, .. } | Kind::Root { entries } => {
881 entries.insert(name, inode.clone());
882 }
883 _ => unreachable!("Dir or Root became not Dir or Root"),
884 }
885 }
886
887 let real_fd = self
889 .create_fd(
890 rights,
891 rights_inheriting,
892 flags,
893 fd_flags,
894 open_flags,
895 inode.clone(),
896 )
897 .map_err(fs_error_from_wasi_err)?;
898
899 {
900 let mut guard = inode.kind.write().unwrap();
901 match guard.deref_mut() {
902 Kind::File { fd, .. } => {
903 *fd = Some(real_fd);
904 }
905 _ => unreachable!("We just created a Kind::File"),
906 }
907 }
908
909 Ok(real_fd)
910 }
911 _ => Err(FsError::BaseNotDirectory),
912 }
913 }
914
915 #[allow(dead_code)]
919 pub fn swap_file(
920 &self,
921 fd: WasiFd,
922 mut file: Box<dyn VirtualFile + Send + Sync + 'static>,
923 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
924 match fd {
925 __WASI_STDIN_FILENO => {
926 let mut target = WasiInodes::stdin_mut(&self.fd_map)?;
927 Ok(Some(target.swap(file)))
928 }
929 __WASI_STDOUT_FILENO => {
930 let mut target = WasiInodes::stdout_mut(&self.fd_map)?;
931 Ok(Some(target.swap(file)))
932 }
933 __WASI_STDERR_FILENO => {
934 let mut target = WasiInodes::stderr_mut(&self.fd_map)?;
935 Ok(Some(target.swap(file)))
936 }
937 _ => {
938 let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?;
939 {
940 let guard = base_inode.read();
942 match guard.deref() {
943 Kind::File { handle, .. } => {
944 if let Some(handle) = handle {
945 let mut handle = handle.write().unwrap();
946 std::mem::swap(handle.deref_mut(), &mut file);
947 return Ok(Some(file));
948 }
949 }
950 _ => return Err(FsError::NotAFile),
951 }
952 }
953 let mut guard = base_inode.write();
955 match guard.deref_mut() {
956 Kind::File { handle, .. } => {
957 if let Some(handle) = handle {
958 let mut handle = handle.write().unwrap();
959 std::mem::swap(handle.deref_mut(), &mut file);
960 Ok(Some(file))
961 } else {
962 handle.replace(Arc::new(RwLock::new(file)));
963 Ok(None)
964 }
965 }
966 _ => Err(FsError::NotAFile),
967 }
968 }
969 }
970 }
971
972 pub fn filestat_resync_size(&self, fd: WasiFd) -> Result<Filesize, Errno> {
974 let inode = self.get_fd_inode(fd)?;
975 let mut guard = inode.write();
976 match guard.deref_mut() {
977 Kind::File { handle, .. } => {
978 if let Some(h) = handle {
979 let h = h.read().unwrap();
980 let new_size = h.size();
981 drop(h);
982 drop(guard);
983
984 inode.stat.write().unwrap().st_size = new_size;
985 Ok(new_size as Filesize)
986 } else {
987 Err(Errno::Badf)
988 }
989 }
990 Kind::Dir { .. } | Kind::Root { .. } => Err(Errno::Isdir),
991 _ => Err(Errno::Inval),
992 }
993 }
994
995 pub fn set_current_dir(&self, path: &str) {
997 let mut guard = self.current_dir.lock().unwrap();
998 *guard = path.to_string();
999 }
1000
1001 pub fn get_current_dir(
1003 &self,
1004 inodes: &WasiInodes,
1005 base: WasiFd,
1006 ) -> Result<(InodeGuard, String), Errno> {
1007 self.get_current_dir_inner(inodes, base, 0)
1008 }
1009
1010 pub(crate) fn get_current_dir_inner(
1011 &self,
1012 inodes: &WasiInodes,
1013 base: WasiFd,
1014 symlink_count: u32,
1015 ) -> Result<(InodeGuard, String), Errno> {
1016 let current_dir = {
1017 let guard = self.current_dir.lock().unwrap();
1018 guard.clone()
1019 };
1020 let cur_inode = self.get_fd_inode(base)?;
1021 let inode = self.get_inode_at_path_inner(
1022 inodes,
1023 cur_inode,
1024 current_dir.as_str(),
1025 symlink_count,
1026 true,
1027 )?;
1028 Ok((inode, current_dir))
1029 }
1030
1031 fn get_inode_at_path_inner(
1045 &self,
1046 inodes: &WasiInodes,
1047 mut cur_inode: InodeGuard,
1048 path_str: &str,
1049 mut symlink_count: u32,
1050 follow_symlinks: bool,
1051 ) -> Result<InodeGuard, Errno> {
1052 if symlink_count > MAX_SYMLINKS {
1053 return Err(Errno::Mlink);
1054 }
1055
1056 let path: &Path = Path::new(path_str);
1057 let n_components = path.components().count();
1058
1059 'path_iter: for (i, component) in path.components().enumerate() {
1061 if matches!(component, Component::RootDir) {
1065 continue;
1066 }
1067
1068 let last_component = i + 1 == n_components;
1070 'symlink_resolution: while symlink_count < MAX_SYMLINKS {
1073 let processing_cur_inode = cur_inode.clone();
1074 let mut guard = processing_cur_inode.write();
1075 match guard.deref_mut() {
1076 Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"),
1077 Kind::Dir {
1078 entries,
1079 path,
1080 parent,
1081 ..
1082 } => {
1083 match component.as_os_str().to_string_lossy().borrow() {
1084 ".." => {
1085 if let Some(p) = parent.upgrade() {
1086 cur_inode = p;
1087 continue 'path_iter;
1088 } else {
1089 return Err(Errno::Access);
1090 }
1091 }
1092 "." => continue 'path_iter,
1093 _ => (),
1094 }
1095 let mut loop_for_symlink = false;
1097 if let Some(entry) =
1098 entries.get(component.as_os_str().to_string_lossy().as_ref())
1099 {
1100 cur_inode = entry.clone();
1101 } else {
1102 let file = {
1103 let mut cd = path.clone();
1104 cd.push(component);
1105 cd
1106 };
1107 let metadata = self
1108 .root_fs
1109 .symlink_metadata(&file)
1110 .ok()
1111 .ok_or(Errno::Noent)?;
1112 let file_type = metadata.file_type();
1113 let should_insert;
1116
1117 let kind = if file_type.is_dir() {
1118 should_insert = true;
1119 Kind::Dir {
1121 parent: cur_inode.downgrade(),
1122 path: file.clone(),
1123 entries: Default::default(),
1124 }
1125 } else if file_type.is_file() {
1126 should_insert = true;
1127 Kind::File {
1129 handle: None,
1130 path: file.clone(),
1131 fd: None,
1132 }
1133 } else if file_type.is_symlink() {
1134 should_insert = false;
1135 let link_value =
1136 self.root_fs.readlink(&file).ok().ok_or(Errno::Noent)?;
1137 debug!("attempting to decompose path {:?}", link_value);
1138
1139 let (pre_open_dir_fd, relative_path) = if link_value.is_relative() {
1140 self.path_into_pre_open_and_relative_path(&file)?
1141 } else {
1142 tracing::error!("Absolute symlinks are not yet supported");
1143 return Err(Errno::Notsup);
1144 };
1145 loop_for_symlink = true;
1146 symlink_count += 1;
1147 Kind::Symlink {
1148 base_po_dir: pre_open_dir_fd,
1149 path_to_symlink: relative_path.to_owned(),
1150 relative_path: link_value,
1151 }
1152 } else {
1153 #[cfg(unix)]
1154 {
1155 let file_type: Filetype = if file_type.is_char_device() {
1157 Filetype::CharacterDevice
1158 } else if file_type.is_block_device() {
1159 Filetype::BlockDevice
1160 } else if file_type.is_fifo() {
1161 Filetype::Unknown
1163 } else if file_type.is_socket() {
1164 Filetype::SocketStream
1167 } else {
1168 unimplemented!(
1169 "state::get_inode_at_path unknown file type: not file, directory, symlink, char device, block device, fifo, or socket"
1170 );
1171 };
1172
1173 let kind = Kind::File {
1174 handle: None,
1175 path: file.clone(),
1176 fd: None,
1177 };
1178 drop(guard);
1179 let new_inode = self.create_inode_with_stat(
1180 inodes,
1181 kind,
1182 false,
1183 file.to_string_lossy().to_string().into(),
1184 Filestat {
1185 st_filetype: file_type,
1186 st_ino: Inode::from_path(path_str).as_u64(),
1187 st_size: metadata.len(),
1188 st_ctim: metadata.created(),
1189 st_mtim: metadata.modified(),
1190 st_atim: metadata.accessed(),
1191 ..Filestat::default()
1192 },
1193 );
1194
1195 let mut guard = cur_inode.write();
1196 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1197 entries.insert(
1198 component.as_os_str().to_string_lossy().to_string(),
1199 new_inode.clone(),
1200 );
1201 } else {
1202 unreachable!(
1203 "Attempted to insert special device into non-directory"
1204 );
1205 }
1206 return Ok(new_inode);
1208 }
1209 #[cfg(not(unix))]
1210 unimplemented!(
1211 "state::get_inode_at_path unknown file type: not file, directory, or symlink"
1212 );
1213 };
1214 drop(guard);
1215
1216 let new_inode = self.create_inode(
1217 inodes,
1218 kind,
1219 false,
1220 file.to_string_lossy().to_string(),
1221 )?;
1222 if should_insert {
1223 let mut guard = processing_cur_inode.write();
1224 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1225 entries.insert(
1226 component.as_os_str().to_string_lossy().to_string(),
1227 new_inode.clone(),
1228 );
1229 }
1230 }
1231 cur_inode = new_inode;
1232
1233 if loop_for_symlink && follow_symlinks {
1234 debug!("Following symlink to {:?}", cur_inode);
1235 continue 'symlink_resolution;
1236 }
1237 }
1238 }
1239 Kind::Root { entries } => {
1240 match component {
1241 Component::ParentDir => continue 'path_iter,
1243 Component::CurDir => continue 'path_iter,
1245 _ => {}
1246 }
1247
1248 let component = component.as_os_str().to_string_lossy();
1249
1250 if let Some(entry) = entries.get(component.as_ref()) {
1251 cur_inode = entry.clone();
1252 } else if let Some(root) = entries.get(&"/".to_string()) {
1253 cur_inode = root.clone();
1254 continue 'symlink_resolution;
1255 } else {
1256 return Err(Errno::Notcapable);
1258 }
1259 }
1260 Kind::File { .. }
1261 | Kind::Socket { .. }
1262 | Kind::PipeRx { .. }
1263 | Kind::PipeTx { .. }
1264 | Kind::DuplexPipe { .. }
1265 | Kind::EventNotifications { .. }
1266 | Kind::Epoll { .. } => {
1267 return Err(Errno::Notdir);
1268 }
1269 Kind::Symlink {
1270 base_po_dir,
1271 path_to_symlink,
1272 relative_path,
1273 } => {
1274 let new_base_dir = *base_po_dir;
1275 let new_base_inode = self.get_fd_inode(new_base_dir)?;
1276
1277 let new_path = {
1279 let mut base = path_to_symlink.clone();
1283 base.pop();
1286 base.push(relative_path);
1287 base.to_string_lossy().to_string()
1288 };
1289 debug!("Following symlink recursively");
1290 drop(guard);
1291 let symlink_inode = self.get_inode_at_path_inner(
1292 inodes,
1293 new_base_inode,
1294 &new_path,
1295 symlink_count + 1,
1296 follow_symlinks,
1297 )?;
1298 cur_inode = symlink_inode;
1299 let guard = cur_inode.read();
1302 if let Kind::File { .. } = guard.deref() {
1303 if last_component {
1305 break 'symlink_resolution;
1306 }
1307 }
1308 continue 'symlink_resolution;
1309 }
1310 }
1311 break 'symlink_resolution;
1312 }
1313 }
1314
1315 Ok(cur_inode)
1316 }
1317
1318 fn path_into_pre_open_and_relative_path<'path>(
1328 &self,
1329 path: &'path Path,
1330 ) -> Result<(WasiFd, &'path Path), Errno> {
1331 enum BaseFdAndRelPath<'a> {
1332 None,
1333 BestMatch {
1334 fd: WasiFd,
1335 rel_path: &'a Path,
1336 max_seen: usize,
1337 },
1338 }
1339
1340 impl BaseFdAndRelPath<'_> {
1341 const fn max_seen(&self) -> usize {
1342 match self {
1343 Self::None => 0,
1344 Self::BestMatch { max_seen, .. } => *max_seen,
1345 }
1346 }
1347 }
1348 let mut res = BaseFdAndRelPath::None;
1349 let preopen_fds = self.preopen_fds.read().unwrap();
1351 for po_fd in preopen_fds.deref() {
1352 let po_inode = self
1353 .fd_map
1354 .read()
1355 .unwrap()
1356 .get(*po_fd)
1357 .unwrap()
1358 .inode
1359 .clone();
1360 let guard = po_inode.read();
1361 let po_path = match guard.deref() {
1362 Kind::Dir { path, .. } => &**path,
1363 Kind::Root { .. } => Path::new("/"),
1364 _ => unreachable!("Preopened FD that's not a directory or the root"),
1365 };
1366 if let Ok(stripped_path) = path.strip_prefix(po_path) {
1368 let new_prefix_len = po_path.as_os_str().len();
1370 if new_prefix_len >= res.max_seen() {
1373 res = BaseFdAndRelPath::BestMatch {
1374 fd: *po_fd,
1375 rel_path: stripped_path,
1376 max_seen: new_prefix_len,
1377 };
1378 }
1379 }
1380 }
1381 match res {
1382 BaseFdAndRelPath::None => Err(Errno::Inval),
1384 BaseFdAndRelPath::BestMatch { fd, rel_path, .. } => Ok((fd, rel_path)),
1385 }
1386 }
1387
1388 pub(crate) fn path_depth_from_fd(&self, fd: WasiFd, inode: InodeGuard) -> Result<usize, Errno> {
1391 let mut counter = 0;
1392 let base_inode = self.get_fd_inode(fd)?;
1393 let mut cur_inode = inode;
1394
1395 while cur_inode.ino() != base_inode.ino() {
1396 counter += 1;
1397
1398 let processing_cur_inode = cur_inode.clone();
1399 let guard = processing_cur_inode.read();
1400
1401 match guard.deref() {
1402 Kind::Dir { parent, .. } => {
1403 if let Some(p) = parent.upgrade() {
1404 cur_inode = p;
1405 }
1406 }
1407 _ => return Err(Errno::Inval),
1408 }
1409 }
1410
1411 Ok(counter)
1412 }
1413
1414 pub(crate) fn get_inode_at_path(
1421 &self,
1422 inodes: &WasiInodes,
1423 base: WasiFd,
1424 path: &str,
1425 follow_symlinks: bool,
1426 ) -> Result<InodeGuard, Errno> {
1427 let base_inode = self.get_fd_inode(base)?;
1428 self.get_inode_at_path_inner(inodes, base_inode, path, 0, follow_symlinks)
1429 }
1430
1431 pub(crate) fn get_parent_inode_at_path(
1434 &self,
1435 inodes: &WasiInodes,
1436 base: WasiFd,
1437 path: &Path,
1438 follow_symlinks: bool,
1439 ) -> Result<(InodeGuard, String), Errno> {
1440 let mut parent_dir = std::path::PathBuf::new();
1441 let mut components = path.components().rev();
1442 let new_entity_name = components
1443 .next()
1444 .ok_or(Errno::Inval)?
1445 .as_os_str()
1446 .to_string_lossy()
1447 .to_string();
1448 for comp in components.rev() {
1449 parent_dir.push(comp);
1450 }
1451 self.get_inode_at_path(inodes, base, &parent_dir.to_string_lossy(), follow_symlinks)
1452 .map(|v| (v, new_entity_name))
1453 }
1454
1455 pub fn get_fd(&self, fd: WasiFd) -> Result<Fd, Errno> {
1456 let ret = self
1457 .fd_map
1458 .read()
1459 .unwrap()
1460 .get(fd)
1461 .ok_or(Errno::Badf)
1462 .cloned();
1463
1464 if ret.is_err() && fd == VIRTUAL_ROOT_FD {
1465 Ok(Fd {
1466 inner: FdInner {
1467 rights: ALL_RIGHTS,
1468 rights_inheriting: ALL_RIGHTS,
1469 flags: Fdflags::empty(),
1470 offset: Arc::new(AtomicU64::new(0)),
1471 fd_flags: Fdflagsext::empty(),
1472 },
1473 open_flags: 0,
1474 inode: self.root_inode.clone(),
1475 is_stdio: false,
1476 })
1477 } else {
1478 ret
1479 }
1480 }
1481
1482 pub fn get_fd_inode(&self, fd: WasiFd) -> Result<InodeGuard, Errno> {
1483 if fd == VIRTUAL_ROOT_FD {
1485 return Ok(self.root_inode.clone());
1486 }
1487 self.fd_map
1488 .read()
1489 .unwrap()
1490 .get(fd)
1491 .ok_or(Errno::Badf)
1492 .map(|a| a.inode.clone())
1493 }
1494
1495 pub fn filestat_fd(&self, fd: WasiFd) -> Result<Filestat, Errno> {
1496 let inode = self.get_fd_inode(fd)?;
1497 let guard = inode.stat.read().unwrap();
1498 Ok(*guard.deref())
1499 }
1500
1501 pub fn fdstat(&self, fd: WasiFd) -> Result<Fdstat, Errno> {
1502 match fd {
1503 __WASI_STDIN_FILENO => {
1504 return Ok(Fdstat {
1505 fs_filetype: Filetype::CharacterDevice,
1506 fs_flags: Fdflags::empty(),
1507 fs_rights_base: STDIN_DEFAULT_RIGHTS,
1508 fs_rights_inheriting: Rights::empty(),
1509 });
1510 }
1511 __WASI_STDOUT_FILENO => {
1512 return Ok(Fdstat {
1513 fs_filetype: Filetype::CharacterDevice,
1514 fs_flags: Fdflags::APPEND,
1515 fs_rights_base: STDOUT_DEFAULT_RIGHTS,
1516 fs_rights_inheriting: Rights::empty(),
1517 });
1518 }
1519 __WASI_STDERR_FILENO => {
1520 return Ok(Fdstat {
1521 fs_filetype: Filetype::CharacterDevice,
1522 fs_flags: Fdflags::APPEND,
1523 fs_rights_base: STDERR_DEFAULT_RIGHTS,
1524 fs_rights_inheriting: Rights::empty(),
1525 });
1526 }
1527 VIRTUAL_ROOT_FD => {
1528 return Ok(Fdstat {
1529 fs_filetype: Filetype::Directory,
1530 fs_flags: Fdflags::empty(),
1531 fs_rights_base: ALL_RIGHTS,
1533 fs_rights_inheriting: ALL_RIGHTS,
1534 });
1535 }
1536 _ => (),
1537 }
1538 let fd = self.get_fd(fd)?;
1539
1540 let guard = fd.inode.read();
1541 let deref = guard.deref();
1542 Ok(Fdstat {
1543 fs_filetype: match deref {
1544 Kind::File { .. } => Filetype::RegularFile,
1545 Kind::Dir { .. } => Filetype::Directory,
1546 Kind::Symlink { .. } => Filetype::SymbolicLink,
1547 Kind::Socket { socket } => match &socket.inner.protected.read().unwrap().kind {
1548 InodeSocketKind::TcpStream { .. } => Filetype::SocketStream,
1549 InodeSocketKind::Raw { .. } => Filetype::SocketRaw,
1550 InodeSocketKind::PreSocket { props, .. } => match props.ty {
1551 Socktype::Stream => Filetype::SocketStream,
1552 Socktype::Dgram => Filetype::SocketDgram,
1553 Socktype::Raw => Filetype::SocketRaw,
1554 Socktype::Seqpacket => Filetype::SocketSeqpacket,
1555 _ => Filetype::Unknown,
1556 },
1557 _ => Filetype::Unknown,
1558 },
1559 _ => Filetype::Unknown,
1560 },
1561 fs_flags: fd.inner.flags,
1562 fs_rights_base: fd.inner.rights,
1563 fs_rights_inheriting: fd.inner.rights_inheriting, })
1565 }
1566
1567 pub fn prestat_fd(&self, fd: WasiFd) -> Result<Prestat, Errno> {
1568 let inode = self.get_fd_inode(fd)?;
1569 if inode.is_preopened {
1572 Ok(self.prestat_fd_inner(inode.deref()))
1573 } else {
1574 Err(Errno::Badf)
1575 }
1576 }
1577
1578 pub(crate) fn prestat_fd_inner(&self, inode_val: &InodeVal) -> Prestat {
1579 Prestat {
1580 pr_type: Preopentype::Dir,
1581 u: PrestatEnum::Dir {
1582 pr_name_len: inode_val.name.read().unwrap().len() as u32 + 1,
1586 }
1587 .untagged(),
1588 }
1589 }
1590
1591 #[allow(clippy::await_holding_lock)]
1592 pub async fn flush(&self, fd: WasiFd) -> Result<(), Errno> {
1593 match fd {
1594 __WASI_STDIN_FILENO => (),
1595 __WASI_STDOUT_FILENO => {
1596 let mut file =
1597 WasiInodes::stdout_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1598 file.flush().await.map_err(map_io_err)?
1599 }
1600 __WASI_STDERR_FILENO => {
1601 let mut file =
1602 WasiInodes::stderr_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1603 file.flush().await.map_err(map_io_err)?
1604 }
1605 _ => {
1606 let fd = self.get_fd(fd)?;
1607 if !fd.inner.rights.contains(Rights::FD_DATASYNC) {
1608 return Err(Errno::Access);
1609 }
1610
1611 let file = {
1612 let guard = fd.inode.read();
1613 match guard.deref() {
1614 Kind::File {
1615 handle: Some(file), ..
1616 } => file.clone(),
1617 Kind::Dir { .. } => return Err(Errno::Isdir),
1619 Kind::Buffer { .. } => return Ok(()),
1620 _ => return Err(Errno::Io),
1621 }
1622 };
1623 drop(fd);
1624
1625 struct FlushPoller {
1626 file: Arc<RwLock<Box<dyn VirtualFile + Send + Sync>>>,
1627 }
1628 impl Future for FlushPoller {
1629 type Output = Result<(), Errno>;
1630 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1631 let mut file = self.file.write().unwrap();
1632 Pin::new(file.as_mut())
1633 .poll_flush(cx)
1634 .map_err(|_| Errno::Io)
1635 }
1636 }
1637 FlushPoller { file }.await?;
1638 }
1639 }
1640 Ok(())
1641 }
1642
1643 pub(crate) fn create_inode(
1645 &self,
1646 inodes: &WasiInodes,
1647 kind: Kind,
1648 is_preopened: bool,
1649 name: String,
1650 ) -> Result<InodeGuard, Errno> {
1651 let stat = self.get_stat_for_kind(&kind)?;
1652 Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name.into(), stat))
1653 }
1654
1655 pub(crate) fn create_inode_with_default_stat(
1657 &self,
1658 inodes: &WasiInodes,
1659 kind: Kind,
1660 is_preopened: bool,
1661 name: Cow<'static, str>,
1662 ) -> InodeGuard {
1663 let stat = Filestat::default();
1664 self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)
1665 }
1666
1667 pub(crate) fn create_inode_with_stat(
1669 &self,
1670 inodes: &WasiInodes,
1671 kind: Kind,
1672 is_preopened: bool,
1673 name: Cow<'static, str>,
1674 mut stat: Filestat,
1675 ) -> InodeGuard {
1676 match &kind {
1677 Kind::File {
1678 handle: Some(handle),
1679 ..
1680 } => {
1681 let guard = handle.read().unwrap();
1682 stat.st_size = guard.size();
1683 }
1684 Kind::Buffer { buffer } => {
1685 stat.st_size = buffer.len() as u64;
1686 }
1687 _ => {}
1688 }
1689
1690 let st_ino = Inode::from_path(&name);
1691 stat.st_ino = st_ino.as_u64();
1692
1693 inodes.add_inode_val(InodeVal {
1694 stat: RwLock::new(stat),
1695 is_preopened,
1696 name: RwLock::new(name),
1697 kind: RwLock::new(kind),
1698 })
1699 }
1700
1701 pub fn create_fd(
1702 &self,
1703 rights: Rights,
1704 rights_inheriting: Rights,
1705 fs_flags: Fdflags,
1706 fd_flags: Fdflagsext,
1707 open_flags: u16,
1708 inode: InodeGuard,
1709 ) -> Result<WasiFd, Errno> {
1710 self.create_fd_ext(
1711 rights,
1712 rights_inheriting,
1713 fs_flags,
1714 fd_flags,
1715 open_flags,
1716 inode,
1717 None,
1718 false,
1719 )
1720 }
1721
1722 #[allow(clippy::too_many_arguments)]
1723 pub fn with_fd(
1724 &self,
1725 rights: Rights,
1726 rights_inheriting: Rights,
1727 fs_flags: Fdflags,
1728 fd_flags: Fdflagsext,
1729 open_flags: u16,
1730 inode: InodeGuard,
1731 idx: WasiFd,
1732 ) -> Result<(), Errno> {
1733 self.create_fd_ext(
1734 rights,
1735 rights_inheriting,
1736 fs_flags,
1737 fd_flags,
1738 open_flags,
1739 inode,
1740 Some(idx),
1741 true,
1742 )?;
1743 Ok(())
1744 }
1745
1746 #[allow(clippy::too_many_arguments)]
1747 pub fn create_fd_ext(
1748 &self,
1749 rights: Rights,
1750 rights_inheriting: Rights,
1751 fs_flags: Fdflags,
1752 fd_flags: Fdflagsext,
1753 open_flags: u16,
1754 inode: InodeGuard,
1755 idx: Option<WasiFd>,
1756 exclusive: bool,
1757 ) -> Result<WasiFd, Errno> {
1758 let is_stdio = matches!(
1759 idx,
1760 Some(__WASI_STDIN_FILENO) | Some(__WASI_STDOUT_FILENO) | Some(__WASI_STDERR_FILENO)
1761 );
1762 let fd = Fd {
1763 inner: FdInner {
1764 rights,
1765 rights_inheriting,
1766 flags: fs_flags,
1767 offset: Arc::new(AtomicU64::new(0)),
1768 fd_flags,
1769 },
1770 open_flags,
1771 inode,
1772 is_stdio,
1773 };
1774
1775 let mut guard = self.fd_map.write().unwrap();
1776
1777 match idx {
1778 Some(idx) => {
1779 if guard.insert(exclusive, idx, fd) {
1780 Ok(idx)
1781 } else {
1782 Err(Errno::Exist)
1783 }
1784 }
1785 None => Ok(guard.insert_first_free(fd)),
1786 }
1787 }
1788
1789 pub fn clone_fd(&self, fd: WasiFd) -> Result<WasiFd, Errno> {
1790 self.clone_fd_ext(fd, 0, None)
1791 }
1792
1793 pub fn clone_fd_ext(
1794 &self,
1795 fd: WasiFd,
1796 min_result_fd: WasiFd,
1797 cloexec: Option<bool>,
1798 ) -> Result<WasiFd, Errno> {
1799 let fd = self.get_fd(fd)?;
1800 Ok(self.fd_map.write().unwrap().insert_first_free_after(
1801 Fd {
1802 inner: FdInner {
1803 rights: fd.inner.rights,
1804 rights_inheriting: fd.inner.rights_inheriting,
1805 flags: fd.inner.flags,
1806 offset: fd.inner.offset.clone(),
1807 fd_flags: match cloexec {
1808 None => fd.inner.fd_flags,
1809 Some(cloexec) => {
1810 let mut f = fd.inner.fd_flags;
1811 f.set(Fdflagsext::CLOEXEC, cloexec);
1812 f
1813 }
1814 },
1815 },
1816 open_flags: fd.open_flags,
1817 inode: fd.inode,
1818 is_stdio: fd.is_stdio,
1819 },
1820 min_result_fd,
1821 ))
1822 }
1823
1824 pub unsafe fn remove_inode(&self, inodes: &WasiInodes, ino: Inode) -> Option<Arc<InodeVal>> {
1833 let mut guard = inodes.protected.write().unwrap();
1834 guard.lookup.remove(&ino).and_then(|a| Weak::upgrade(&a))
1835 }
1836
1837 pub(crate) fn create_stdout(&self, inodes: &WasiInodes) {
1838 self.create_std_dev_inner(
1839 inodes,
1840 Box::<Stdout>::default(),
1841 "stdout",
1842 __WASI_STDOUT_FILENO,
1843 STDOUT_DEFAULT_RIGHTS,
1844 Fdflags::APPEND,
1845 FS_STDOUT_INO,
1846 );
1847 }
1848
1849 pub(crate) fn create_stdin(&self, inodes: &WasiInodes) {
1850 self.create_std_dev_inner(
1851 inodes,
1852 Box::<Stdin>::default(),
1853 "stdin",
1854 __WASI_STDIN_FILENO,
1855 STDIN_DEFAULT_RIGHTS,
1856 Fdflags::empty(),
1857 FS_STDIN_INO,
1858 );
1859 }
1860
1861 pub(crate) fn create_stderr(&self, inodes: &WasiInodes) {
1862 self.create_std_dev_inner(
1863 inodes,
1864 Box::<Stderr>::default(),
1865 "stderr",
1866 __WASI_STDERR_FILENO,
1867 STDERR_DEFAULT_RIGHTS,
1868 Fdflags::APPEND,
1869 FS_STDERR_INO,
1870 );
1871 }
1872
1873 pub(crate) fn create_rootfd(&self) -> Result<(), String> {
1874 let all_rights = ALL_RIGHTS;
1876 let root_rights = all_rights
1879 ;
1895 let fd = self
1896 .create_fd(
1897 root_rights,
1898 root_rights,
1899 Fdflags::empty(),
1900 Fdflagsext::empty(),
1901 Fd::READ,
1902 self.root_inode.clone(),
1903 )
1904 .map_err(|e| format!("Could not create root fd: {e}"))?;
1905 self.preopen_fds.write().unwrap().push(fd);
1906 Ok(())
1907 }
1908
1909 pub(crate) fn create_preopens(
1910 &self,
1911 inodes: &WasiInodes,
1912 ignore_duplicates: bool,
1913 ) -> Result<(), String> {
1914 for preopen_name in self.init_vfs_preopens.iter() {
1915 let kind = Kind::Dir {
1916 parent: self.root_inode.downgrade(),
1917 path: PathBuf::from(preopen_name),
1918 entries: Default::default(),
1919 };
1920 let rights = Rights::FD_ADVISE
1921 | Rights::FD_TELL
1922 | Rights::FD_SEEK
1923 | Rights::FD_READ
1924 | Rights::PATH_OPEN
1925 | Rights::FD_READDIR
1926 | Rights::PATH_READLINK
1927 | Rights::PATH_FILESTAT_GET
1928 | Rights::FD_FILESTAT_GET
1929 | Rights::PATH_LINK_SOURCE
1930 | Rights::PATH_RENAME_SOURCE
1931 | Rights::POLL_FD_READWRITE
1932 | Rights::SOCK_SHUTDOWN;
1933 let inode = self
1934 .create_inode(inodes, kind, true, preopen_name.clone())
1935 .map_err(|e| {
1936 format!(
1937 "Failed to create inode for preopened dir (name `{preopen_name}`): WASI error code: {e}",
1938 )
1939 })?;
1940 let fd_flags = Fd::READ;
1941 let fd = self
1942 .create_fd(
1943 rights,
1944 rights,
1945 Fdflags::empty(),
1946 Fdflagsext::empty(),
1947 fd_flags,
1948 inode.clone(),
1949 )
1950 .map_err(|e| format!("Could not open fd for file {preopen_name:?}: {e}"))?;
1951 {
1952 let mut guard = self.root_inode.write();
1953 if let Kind::Root { entries } = guard.deref_mut() {
1954 let existing_entry = entries.insert(preopen_name.clone(), inode);
1955 if existing_entry.is_some() && !ignore_duplicates {
1956 return Err(format!("Found duplicate entry for alias `{preopen_name}`"));
1957 }
1958 }
1959 }
1960 self.preopen_fds.write().unwrap().push(fd);
1961 }
1962
1963 for PreopenedDir {
1964 path,
1965 alias,
1966 read,
1967 write,
1968 create,
1969 } in self.init_preopens.iter()
1970 {
1971 debug!(
1972 "Attempting to preopen {} with alias {:?}",
1973 &path.to_string_lossy(),
1974 &alias
1975 );
1976 let cur_dir_metadata = self
1977 .root_fs
1978 .metadata(path)
1979 .map_err(|e| format!("Could not get metadata for file {path:?}: {e}"))?;
1980
1981 let kind = if cur_dir_metadata.is_dir() {
1982 Kind::Dir {
1983 parent: self.root_inode.downgrade(),
1984 path: path.clone(),
1985 entries: Default::default(),
1986 }
1987 } else {
1988 return Err(format!(
1989 "WASI only supports pre-opened directories right now; found \"{}\"",
1990 &path.to_string_lossy()
1991 ));
1992 };
1993
1994 let rights = {
1995 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
1997 if *read {
1998 rights |= Rights::FD_READ
1999 | Rights::PATH_OPEN
2000 | Rights::FD_READDIR
2001 | Rights::PATH_READLINK
2002 | Rights::PATH_FILESTAT_GET
2003 | Rights::FD_FILESTAT_GET
2004 | Rights::PATH_LINK_SOURCE
2005 | Rights::PATH_RENAME_SOURCE
2006 | Rights::POLL_FD_READWRITE
2007 | Rights::SOCK_SHUTDOWN;
2008 }
2009 if *write {
2010 rights |= Rights::FD_DATASYNC
2011 | Rights::FD_FDSTAT_SET_FLAGS
2012 | Rights::FD_WRITE
2013 | Rights::FD_SYNC
2014 | Rights::FD_ALLOCATE
2015 | Rights::PATH_OPEN
2016 | Rights::PATH_RENAME_TARGET
2017 | Rights::PATH_FILESTAT_SET_SIZE
2018 | Rights::PATH_FILESTAT_SET_TIMES
2019 | Rights::FD_FILESTAT_SET_SIZE
2020 | Rights::FD_FILESTAT_SET_TIMES
2021 | Rights::PATH_REMOVE_DIRECTORY
2022 | Rights::PATH_UNLINK_FILE
2023 | Rights::POLL_FD_READWRITE
2024 | Rights::SOCK_SHUTDOWN;
2025 }
2026 if *create {
2027 rights |= Rights::PATH_CREATE_DIRECTORY
2028 | Rights::PATH_CREATE_FILE
2029 | Rights::PATH_LINK_TARGET
2030 | Rights::PATH_OPEN
2031 | Rights::PATH_RENAME_TARGET
2032 | Rights::PATH_SYMLINK;
2033 }
2034
2035 rights
2036 };
2037 let inode = if let Some(alias) = &alias {
2038 self.create_inode(inodes, kind, true, alias.clone())
2039 } else {
2040 self.create_inode(inodes, kind, true, path.to_string_lossy().into_owned())
2041 }
2042 .map_err(|e| {
2043 format!("Failed to create inode for preopened dir: WASI error code: {e}")
2044 })?;
2045 let fd_flags = {
2046 let mut fd_flags = 0;
2047 if *read {
2048 fd_flags |= Fd::READ;
2049 }
2050 if *write {
2051 fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE;
2053 }
2054 if *create {
2055 fd_flags |= Fd::CREATE;
2056 }
2057 fd_flags
2058 };
2059 let fd = self
2060 .create_fd(
2061 rights,
2062 rights,
2063 Fdflags::empty(),
2064 Fdflagsext::empty(),
2065 fd_flags,
2066 inode.clone(),
2067 )
2068 .map_err(|e| format!("Could not open fd for file {path:?}: {e}"))?;
2069 {
2070 let mut guard = self.root_inode.write();
2071 if let Kind::Root { entries } = guard.deref_mut() {
2072 let key = if let Some(alias) = &alias {
2073 alias.clone()
2074 } else {
2075 path.to_string_lossy().into_owned()
2076 };
2077 let existing_entry = entries.insert(key.clone(), inode);
2078 if existing_entry.is_some() && !ignore_duplicates {
2079 return Err(format!("Found duplicate entry for alias `{key}`"));
2080 }
2081 }
2082 }
2083 self.preopen_fds.write().unwrap().push(fd);
2084 }
2085
2086 Ok(())
2087 }
2088
2089 #[allow(clippy::too_many_arguments)]
2090 pub(crate) fn create_std_dev_inner(
2091 &self,
2092 inodes: &WasiInodes,
2093 handle: Box<dyn VirtualFile + Send + Sync + 'static>,
2094 name: &'static str,
2095 raw_fd: WasiFd,
2096 rights: Rights,
2097 fd_flags: Fdflags,
2098 st_ino: Inode,
2099 ) {
2100 let inode = {
2101 let stat = Filestat {
2102 st_filetype: Filetype::CharacterDevice,
2103 st_ino: st_ino.as_u64(),
2104 ..Filestat::default()
2105 };
2106 let kind = Kind::File {
2107 fd: Some(raw_fd),
2108 handle: Some(Arc::new(RwLock::new(handle))),
2109 path: "".into(),
2110 };
2111 inodes.add_inode_val(InodeVal {
2112 stat: RwLock::new(stat),
2113 is_preopened: true,
2114 name: RwLock::new(name.to_string().into()),
2115 kind: RwLock::new(kind),
2116 })
2117 };
2118 self.fd_map.write().unwrap().insert(
2119 false,
2120 raw_fd,
2121 Fd {
2122 inner: FdInner {
2123 rights,
2124 rights_inheriting: Rights::empty(),
2125 flags: fd_flags,
2126 offset: Arc::new(AtomicU64::new(0)),
2127 fd_flags: Fdflagsext::empty(),
2128 },
2129 open_flags: 0,
2131 inode,
2132 is_stdio: true,
2133 },
2134 );
2135 }
2136
2137 pub fn get_stat_for_kind(&self, kind: &Kind) -> Result<Filestat, Errno> {
2138 let md = match kind {
2139 Kind::File { handle, path, .. } => match handle {
2140 Some(wf) => {
2141 let wf = wf.read().unwrap();
2142 return Ok(Filestat {
2143 st_filetype: Filetype::RegularFile,
2144 st_ino: Inode::from_path(path.to_string_lossy().as_ref()).as_u64(),
2145 st_size: wf.size(),
2146 st_atim: wf.last_accessed(),
2147 st_mtim: wf.last_modified(),
2148 st_ctim: wf.created_time(),
2149
2150 ..Filestat::default()
2151 });
2152 }
2153 None => self
2154 .root_fs
2155 .metadata(path)
2156 .map_err(fs_error_into_wasi_err)?,
2157 },
2158 Kind::Dir { path, .. } => self
2159 .root_fs
2160 .metadata(path)
2161 .map_err(fs_error_into_wasi_err)?,
2162 Kind::Symlink {
2163 base_po_dir,
2164 path_to_symlink,
2165 ..
2166 } => {
2167 let guard = self.fd_map.read().unwrap();
2168 let base_po_inode = &guard.get(*base_po_dir).unwrap().inode;
2169 let guard = base_po_inode.read();
2170 match guard.deref() {
2171 Kind::Root { .. } => self
2172 .root_fs
2173 .symlink_metadata(path_to_symlink)
2174 .map_err(fs_error_into_wasi_err)?,
2175 Kind::Dir { path, .. } => {
2176 let mut real_path = path.clone();
2177 real_path.push(path_to_symlink);
2184 self.root_fs
2185 .symlink_metadata(&real_path)
2186 .map_err(fs_error_into_wasi_err)?
2187 }
2188 _ => unreachable!(
2190 "Symlink pointing to something that's not a directory as its base preopened directory"
2191 ),
2192 }
2193 }
2194 _ => return Err(Errno::Io),
2195 };
2196 Ok(Filestat {
2197 st_filetype: virtual_file_type_to_wasi_file_type(md.file_type()),
2198 st_size: md.len(),
2199 st_atim: md.accessed(),
2200 st_mtim: md.modified(),
2201 st_ctim: md.created(),
2202 ..Filestat::default()
2203 })
2204 }
2205
2206 pub(crate) fn close_fd(&self, fd: WasiFd) -> Result<(), Errno> {
2208 let mut fd_map = self.fd_map.write().unwrap();
2209
2210 let pfd = fd_map.remove(fd).ok_or(Errno::Badf);
2211 match pfd {
2212 Ok(fd_ref) => {
2213 let inode = fd_ref.inode.ino().as_u64();
2214 let ref_cnt = fd_ref.inode.ref_cnt();
2215 if ref_cnt == 1 {
2216 trace!(%fd, %inode, %ref_cnt, "closing file descriptor");
2217 } else {
2218 trace!(%fd, %inode, %ref_cnt, "weakening file descriptor");
2219 }
2220 }
2221 Err(err) => {
2222 trace!(%fd, "closing file descriptor failed - {}", err);
2223 }
2224 }
2225 Ok(())
2226 }
2227}
2228
2229impl std::fmt::Debug for WasiFs {
2230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2231 if let Ok(guard) = self.current_dir.try_lock() {
2232 write!(f, "current_dir={} ", guard.as_str())?;
2233 } else {
2234 write!(f, "current_dir=(locked) ")?;
2235 }
2236 if let Ok(guard) = self.fd_map.read() {
2237 write!(
2238 f,
2239 "next_fd={} max_fd={:?} ",
2240 guard.next_free_fd(),
2241 guard.last_fd()
2242 )?;
2243 } else {
2244 write!(f, "next_fd=(locked) max_fd=(locked) ")?;
2245 }
2246 write!(f, "{:?}", self.root_fs)
2247 }
2248}
2249
2250pub fn default_fs_backing() -> Arc<dyn virtual_fs::FileSystem + Send + Sync> {
2252 cfg_if::cfg_if! {
2253 if #[cfg(feature = "host-fs")] {
2254 Arc::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
2255 } else if #[cfg(not(feature = "host-fs"))] {
2256 Arc::<virtual_fs::mem_fs::FileSystem>::default()
2257 } else {
2258 Arc::<FallbackFileSystem>::default()
2259 }
2260 }
2261}
2262
2263#[derive(Debug, Default)]
2264pub struct FallbackFileSystem;
2265
2266impl FallbackFileSystem {
2267 fn fail() -> ! {
2268 panic!(
2269 "No filesystem set for wasmer-wasi, please enable either the `host-fs` or `mem-fs` feature or set your custom filesystem with `WasiEnvBuilder::set_fs`"
2270 );
2271 }
2272}
2273
2274impl FileSystem for FallbackFileSystem {
2275 fn readlink(&self, _path: &Path) -> virtual_fs::Result<PathBuf> {
2276 Self::fail()
2277 }
2278 fn read_dir(&self, _path: &Path) -> Result<virtual_fs::ReadDir, FsError> {
2279 Self::fail();
2280 }
2281 fn create_dir(&self, _path: &Path) -> Result<(), FsError> {
2282 Self::fail();
2283 }
2284 fn remove_dir(&self, _path: &Path) -> Result<(), FsError> {
2285 Self::fail();
2286 }
2287 fn rename<'a>(&'a self, _from: &Path, _to: &Path) -> BoxFuture<'a, Result<(), FsError>> {
2288 Self::fail();
2289 }
2290 fn metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2291 Self::fail();
2292 }
2293 fn symlink_metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2294 Self::fail();
2295 }
2296 fn remove_file(&self, _path: &Path) -> Result<(), FsError> {
2297 Self::fail();
2298 }
2299 fn new_open_options(&self) -> virtual_fs::OpenOptions<'_> {
2300 Self::fail();
2301 }
2302 fn mount(
2303 &self,
2304 _name: String,
2305 _path: &Path,
2306 _fs: Box<dyn FileSystem + Send + Sync>,
2307 ) -> virtual_fs::Result<()> {
2308 Self::fail()
2309 }
2310}
2311
2312pub fn virtual_file_type_to_wasi_file_type(file_type: virtual_fs::FileType) -> Filetype {
2313 if file_type.is_dir() {
2315 Filetype::Directory
2316 } else if file_type.is_file() {
2317 Filetype::RegularFile
2318 } else if file_type.is_symlink() {
2319 Filetype::SymbolicLink
2320 } else {
2321 Filetype::Unknown
2322 }
2323}
2324
2325pub fn fs_error_from_wasi_err(err: Errno) -> FsError {
2326 match err {
2327 Errno::Badf => FsError::InvalidFd,
2328 Errno::Exist => FsError::AlreadyExists,
2329 Errno::Io => FsError::IOError,
2330 Errno::Addrinuse => FsError::AddressInUse,
2331 Errno::Addrnotavail => FsError::AddressNotAvailable,
2332 Errno::Pipe => FsError::BrokenPipe,
2333 Errno::Connaborted => FsError::ConnectionAborted,
2334 Errno::Connrefused => FsError::ConnectionRefused,
2335 Errno::Connreset => FsError::ConnectionReset,
2336 Errno::Intr => FsError::Interrupted,
2337 Errno::Inval => FsError::InvalidInput,
2338 Errno::Notconn => FsError::NotConnected,
2339 Errno::Nodev => FsError::NoDevice,
2340 Errno::Noent => FsError::EntryNotFound,
2341 Errno::Perm => FsError::PermissionDenied,
2342 Errno::Timedout => FsError::TimedOut,
2343 Errno::Proto => FsError::UnexpectedEof,
2344 Errno::Again => FsError::WouldBlock,
2345 Errno::Nospc => FsError::WriteZero,
2346 Errno::Notempty => FsError::DirectoryNotEmpty,
2347 _ => FsError::UnknownError,
2348 }
2349}
2350
2351pub fn fs_error_into_wasi_err(fs_error: FsError) -> Errno {
2352 match fs_error {
2353 FsError::AlreadyExists => Errno::Exist,
2354 FsError::AddressInUse => Errno::Addrinuse,
2355 FsError::AddressNotAvailable => Errno::Addrnotavail,
2356 FsError::BaseNotDirectory => Errno::Notdir,
2357 FsError::BrokenPipe => Errno::Pipe,
2358 FsError::ConnectionAborted => Errno::Connaborted,
2359 FsError::ConnectionRefused => Errno::Connrefused,
2360 FsError::ConnectionReset => Errno::Connreset,
2361 FsError::Interrupted => Errno::Intr,
2362 FsError::InvalidData => Errno::Io,
2363 FsError::InvalidFd => Errno::Badf,
2364 FsError::InvalidInput => Errno::Inval,
2365 FsError::IOError => Errno::Io,
2366 FsError::NoDevice => Errno::Nodev,
2367 FsError::NotAFile => Errno::Inval,
2368 FsError::NotConnected => Errno::Notconn,
2369 FsError::EntryNotFound => Errno::Noent,
2370 FsError::PermissionDenied => Errno::Perm,
2371 FsError::TimedOut => Errno::Timedout,
2372 FsError::UnexpectedEof => Errno::Proto,
2373 FsError::WouldBlock => Errno::Again,
2374 FsError::WriteZero => Errno::Nospc,
2375 FsError::DirectoryNotEmpty => Errno::Notempty,
2376 FsError::StorageFull => Errno::Overflow,
2377 FsError::Lock | FsError::UnknownError => Errno::Io,
2378 FsError::Unsupported => Errno::Notsup,
2379 }
2380}