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