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, path: String) -> String {
705 if path.starts_with('/') {
706 return path;
707 }
708
709 let current_dir = self.current_dir.lock().unwrap();
710 format!("{}/{}", current_dir.trim_end_matches('/'), path)
711 }
712
713 fn new_init(
716 fs_backing: WasiFsRoot,
717 inodes: &WasiInodes,
718 st_ino: Inode,
719 ) -> Result<Self, String> {
720 debug!("Initializing WASI filesystem");
721
722 let stat = Filestat {
723 st_filetype: Filetype::Directory,
724 st_ino: st_ino.as_u64(),
725 ..Filestat::default()
726 };
727 let root_kind = Kind::Root {
728 entries: HashMap::new(),
729 };
730 let root_inode = inodes.add_inode_val(InodeVal {
731 stat: RwLock::new(stat),
732 is_preopened: true,
733 name: RwLock::new("/".into()),
734 kind: RwLock::new(root_kind),
735 });
736
737 let wasi_fs = Self {
738 preopen_fds: RwLock::new(vec![]),
739 fd_map: RwLock::new(FdList::new()),
740 current_dir: Mutex::new("/".to_string()),
741 is_wasix: AtomicBool::new(false),
742 root_fs: fs_backing,
743 root_inode,
744 has_unioned: Mutex::new(HashSet::new()),
745 init_preopens: Default::default(),
746 init_vfs_preopens: Default::default(),
747 };
748 wasi_fs.create_stdin(inodes);
749 wasi_fs.create_stdout(inodes);
750 wasi_fs.create_stderr(inodes);
751 wasi_fs.create_rootfd()?;
752
753 Ok(wasi_fs)
754 }
755
756 #[allow(dead_code)]
766 #[allow(clippy::too_many_arguments)]
767 pub unsafe fn open_dir_all(
768 &mut self,
769 inodes: &WasiInodes,
770 base: WasiFd,
771 name: String,
772 rights: Rights,
773 rights_inheriting: Rights,
774 flags: Fdflags,
775 fd_flags: Fdflagsext,
776 ) -> Result<WasiFd, FsError> {
777 let mut cur_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
780
781 let path: &Path = Path::new(&name);
782 for c in path.components() {
784 let segment_name = c.as_os_str().to_string_lossy().to_string();
785 let guard = cur_inode.read();
786 match guard.deref() {
787 Kind::Dir { entries, .. } | Kind::Root { entries } => {
788 if let Some(_entry) = entries.get(&segment_name) {
789 return Err(FsError::AlreadyExists);
791 }
792
793 let kind = Kind::Dir {
794 parent: cur_inode.downgrade(),
795 path: PathBuf::from(""),
796 entries: HashMap::new(),
797 };
798
799 drop(guard);
800 let inode = self.create_inode_with_default_stat(
801 inodes,
802 kind,
803 false,
804 segment_name.clone().into(),
805 );
806
807 {
809 let mut guard = cur_inode.write();
810 match guard.deref_mut() {
811 Kind::Dir { entries, .. } | Kind::Root { entries } => {
812 entries.insert(segment_name, inode.clone());
813 }
814 _ => unreachable!("Dir or Root became not Dir or Root"),
815 }
816 }
817 cur_inode = inode;
818 }
819 _ => return Err(FsError::BaseNotDirectory),
820 }
821 }
822
823 self.create_fd(
825 rights,
826 rights_inheriting,
827 flags,
828 fd_flags,
829 Fd::READ | Fd::WRITE,
830 cur_inode,
831 )
832 .map_err(fs_error_from_wasi_err)
833 }
834
835 #[allow(dead_code, clippy::too_many_arguments)]
840 pub fn open_file_at(
841 &mut self,
842 inodes: &WasiInodes,
843 base: WasiFd,
844 file: Box<dyn VirtualFile + Send + Sync + 'static>,
845 open_flags: u16,
846 name: String,
847 rights: Rights,
848 rights_inheriting: Rights,
849 flags: Fdflags,
850 fd_flags: Fdflagsext,
851 ) -> Result<WasiFd, FsError> {
852 let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
855
856 let guard = base_inode.read();
857 match guard.deref() {
858 Kind::Dir { entries, .. } | Kind::Root { entries } => {
859 if let Some(_entry) = entries.get(&name) {
860 return Err(FsError::AlreadyExists);
862 }
863
864 let kind = Kind::File {
865 handle: Some(Arc::new(RwLock::new(file))),
866 path: PathBuf::from(""),
867 fd: None,
868 };
869
870 drop(guard);
871 let inode = self
872 .create_inode(inodes, kind, false, name.clone())
873 .map_err(|_| FsError::IOError)?;
874
875 {
876 let mut guard = base_inode.write();
877 match guard.deref_mut() {
878 Kind::Dir { entries, .. } | Kind::Root { entries } => {
879 entries.insert(name, inode.clone());
880 }
881 _ => unreachable!("Dir or Root became not Dir or Root"),
882 }
883 }
884
885 let real_fd = self
887 .create_fd(
888 rights,
889 rights_inheriting,
890 flags,
891 fd_flags,
892 open_flags,
893 inode.clone(),
894 )
895 .map_err(fs_error_from_wasi_err)?;
896
897 {
898 let mut guard = inode.kind.write().unwrap();
899 match guard.deref_mut() {
900 Kind::File { fd, .. } => {
901 *fd = Some(real_fd);
902 }
903 _ => unreachable!("We just created a Kind::File"),
904 }
905 }
906
907 Ok(real_fd)
908 }
909 _ => Err(FsError::BaseNotDirectory),
910 }
911 }
912
913 #[allow(dead_code)]
917 pub fn swap_file(
918 &self,
919 fd: WasiFd,
920 mut file: Box<dyn VirtualFile + Send + Sync + 'static>,
921 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
922 match fd {
923 __WASI_STDIN_FILENO => {
924 let mut target = WasiInodes::stdin_mut(&self.fd_map)?;
925 Ok(Some(target.swap(file)))
926 }
927 __WASI_STDOUT_FILENO => {
928 let mut target = WasiInodes::stdout_mut(&self.fd_map)?;
929 Ok(Some(target.swap(file)))
930 }
931 __WASI_STDERR_FILENO => {
932 let mut target = WasiInodes::stderr_mut(&self.fd_map)?;
933 Ok(Some(target.swap(file)))
934 }
935 _ => {
936 let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?;
937 {
938 let guard = base_inode.read();
940 match guard.deref() {
941 Kind::File { handle, .. } => {
942 if let Some(handle) = handle {
943 let mut handle = handle.write().unwrap();
944 std::mem::swap(handle.deref_mut(), &mut file);
945 return Ok(Some(file));
946 }
947 }
948 _ => return Err(FsError::NotAFile),
949 }
950 }
951 let mut guard = base_inode.write();
953 match guard.deref_mut() {
954 Kind::File { handle, .. } => {
955 if let Some(handle) = handle {
956 let mut handle = handle.write().unwrap();
957 std::mem::swap(handle.deref_mut(), &mut file);
958 Ok(Some(file))
959 } else {
960 handle.replace(Arc::new(RwLock::new(file)));
961 Ok(None)
962 }
963 }
964 _ => Err(FsError::NotAFile),
965 }
966 }
967 }
968 }
969
970 pub fn filestat_resync_size(&self, fd: WasiFd) -> Result<Filesize, Errno> {
972 let inode = self.get_fd_inode(fd)?;
973 let mut guard = inode.write();
974 match guard.deref_mut() {
975 Kind::File { handle, .. } => {
976 if let Some(h) = handle {
977 let h = h.read().unwrap();
978 let new_size = h.size();
979 drop(h);
980 drop(guard);
981
982 inode.stat.write().unwrap().st_size = new_size;
983 Ok(new_size as Filesize)
984 } else {
985 Err(Errno::Badf)
986 }
987 }
988 Kind::Dir { .. } | Kind::Root { .. } => Err(Errno::Isdir),
989 _ => Err(Errno::Inval),
990 }
991 }
992
993 pub fn set_current_dir(&self, path: &str) {
995 let mut guard = self.current_dir.lock().unwrap();
996 *guard = path.to_string();
997 }
998
999 pub fn get_current_dir(
1001 &self,
1002 inodes: &WasiInodes,
1003 base: WasiFd,
1004 ) -> Result<(InodeGuard, String), Errno> {
1005 self.get_current_dir_inner(inodes, base, 0)
1006 }
1007
1008 pub(crate) fn get_current_dir_inner(
1009 &self,
1010 inodes: &WasiInodes,
1011 base: WasiFd,
1012 symlink_count: u32,
1013 ) -> Result<(InodeGuard, String), Errno> {
1014 let current_dir = {
1015 let guard = self.current_dir.lock().unwrap();
1016 guard.clone()
1017 };
1018 let cur_inode = self.get_fd_inode(base)?;
1019 let inode = self.get_inode_at_path_inner(
1020 inodes,
1021 cur_inode,
1022 current_dir.as_str(),
1023 symlink_count,
1024 true,
1025 )?;
1026 Ok((inode, current_dir))
1027 }
1028
1029 fn get_inode_at_path_inner(
1043 &self,
1044 inodes: &WasiInodes,
1045 mut cur_inode: InodeGuard,
1046 path_str: &str,
1047 mut symlink_count: u32,
1048 follow_symlinks: bool,
1049 ) -> Result<InodeGuard, Errno> {
1050 if symlink_count > MAX_SYMLINKS {
1051 return Err(Errno::Mlink);
1052 }
1053
1054 let path: &Path = Path::new(path_str);
1055 let n_components = path.components().count();
1056
1057 'path_iter: for (i, component) in path.components().enumerate() {
1059 if matches!(component, Component::RootDir) {
1063 continue;
1064 }
1065
1066 let last_component = i + 1 == n_components;
1068 'symlink_resolution: while symlink_count < MAX_SYMLINKS {
1071 let processing_cur_inode = cur_inode.clone();
1072 let mut guard = processing_cur_inode.write();
1073 match guard.deref_mut() {
1074 Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"),
1075 Kind::Dir {
1076 entries,
1077 path,
1078 parent,
1079 ..
1080 } => {
1081 match component.as_os_str().to_string_lossy().borrow() {
1082 ".." => {
1083 if let Some(p) = parent.upgrade() {
1084 cur_inode = p;
1085 continue 'path_iter;
1086 } else {
1087 return Err(Errno::Access);
1088 }
1089 }
1090 "." => continue 'path_iter,
1091 _ => (),
1092 }
1093 let mut loop_for_symlink = false;
1095 if let Some(entry) =
1096 entries.get(component.as_os_str().to_string_lossy().as_ref())
1097 {
1098 cur_inode = entry.clone();
1099 } else {
1100 let file = {
1101 let mut cd = path.clone();
1102 cd.push(component);
1103 cd
1104 };
1105 let metadata = self
1106 .root_fs
1107 .symlink_metadata(&file)
1108 .ok()
1109 .ok_or(Errno::Noent)?;
1110 let file_type = metadata.file_type();
1111 let should_insert;
1114
1115 let kind = if file_type.is_dir() {
1116 should_insert = true;
1117 Kind::Dir {
1119 parent: cur_inode.downgrade(),
1120 path: file.clone(),
1121 entries: Default::default(),
1122 }
1123 } else if file_type.is_file() {
1124 should_insert = true;
1125 Kind::File {
1127 handle: None,
1128 path: file.clone(),
1129 fd: None,
1130 }
1131 } else if file_type.is_symlink() {
1132 should_insert = false;
1133 let link_value =
1134 self.root_fs.readlink(&file).ok().ok_or(Errno::Noent)?;
1135 debug!("attempting to decompose path {:?}", link_value);
1136
1137 let (pre_open_dir_fd, relative_path) = if link_value.is_relative() {
1138 self.path_into_pre_open_and_relative_path(&file)?
1139 } else {
1140 tracing::error!("Absolute symlinks are not yet supported");
1141 return Err(Errno::Notsup);
1142 };
1143 loop_for_symlink = true;
1144 symlink_count += 1;
1145 Kind::Symlink {
1146 base_po_dir: pre_open_dir_fd,
1147 path_to_symlink: relative_path.to_owned(),
1148 relative_path: link_value,
1149 }
1150 } else {
1151 #[cfg(unix)]
1152 {
1153 let file_type: Filetype = if file_type.is_char_device() {
1155 Filetype::CharacterDevice
1156 } else if file_type.is_block_device() {
1157 Filetype::BlockDevice
1158 } else if file_type.is_fifo() {
1159 Filetype::Unknown
1161 } else if file_type.is_socket() {
1162 Filetype::SocketStream
1165 } else {
1166 unimplemented!(
1167 "state::get_inode_at_path unknown file type: not file, directory, symlink, char device, block device, fifo, or socket"
1168 );
1169 };
1170
1171 let kind = Kind::File {
1172 handle: None,
1173 path: file.clone(),
1174 fd: None,
1175 };
1176 drop(guard);
1177 let new_inode = self.create_inode_with_stat(
1178 inodes,
1179 kind,
1180 false,
1181 file.to_string_lossy().to_string().into(),
1182 Filestat {
1183 st_filetype: file_type,
1184 st_ino: Inode::from_path(path_str).as_u64(),
1185 st_size: metadata.len(),
1186 st_ctim: metadata.created(),
1187 st_mtim: metadata.modified(),
1188 st_atim: metadata.accessed(),
1189 ..Filestat::default()
1190 },
1191 );
1192
1193 let mut guard = cur_inode.write();
1194 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1195 entries.insert(
1196 component.as_os_str().to_string_lossy().to_string(),
1197 new_inode.clone(),
1198 );
1199 } else {
1200 unreachable!(
1201 "Attempted to insert special device into non-directory"
1202 );
1203 }
1204 return Ok(new_inode);
1206 }
1207 #[cfg(not(unix))]
1208 unimplemented!(
1209 "state::get_inode_at_path unknown file type: not file, directory, or symlink"
1210 );
1211 };
1212 drop(guard);
1213
1214 let new_inode = self.create_inode(
1215 inodes,
1216 kind,
1217 false,
1218 file.to_string_lossy().to_string(),
1219 )?;
1220 if should_insert {
1221 let mut guard = processing_cur_inode.write();
1222 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1223 entries.insert(
1224 component.as_os_str().to_string_lossy().to_string(),
1225 new_inode.clone(),
1226 );
1227 }
1228 }
1229 cur_inode = new_inode;
1230
1231 if loop_for_symlink && follow_symlinks {
1232 debug!("Following symlink to {:?}", cur_inode);
1233 continue 'symlink_resolution;
1234 }
1235 }
1236 }
1237 Kind::Root { entries } => {
1238 match component {
1239 Component::ParentDir => continue 'path_iter,
1241 Component::CurDir => continue 'path_iter,
1243 _ => {}
1244 }
1245
1246 let component = component.as_os_str().to_string_lossy();
1247
1248 if let Some(entry) = entries.get(component.as_ref()) {
1249 cur_inode = entry.clone();
1250 } else if let Some(root) = entries.get(&"/".to_string()) {
1251 cur_inode = root.clone();
1252 continue 'symlink_resolution;
1253 } else {
1254 return Err(Errno::Notcapable);
1256 }
1257 }
1258 Kind::File { .. }
1259 | Kind::Socket { .. }
1260 | Kind::PipeRx { .. }
1261 | Kind::PipeTx { .. }
1262 | Kind::DuplexPipe { .. }
1263 | Kind::EventNotifications { .. }
1264 | Kind::Epoll { .. } => {
1265 return Err(Errno::Notdir);
1266 }
1267 Kind::Symlink {
1268 base_po_dir,
1269 path_to_symlink,
1270 relative_path,
1271 } => {
1272 let new_base_dir = *base_po_dir;
1273 let new_base_inode = self.get_fd_inode(new_base_dir)?;
1274
1275 let new_path = {
1277 let mut base = path_to_symlink.clone();
1281 base.pop();
1284 base.push(relative_path);
1285 base.to_string_lossy().to_string()
1286 };
1287 debug!("Following symlink recursively");
1288 drop(guard);
1289 let symlink_inode = self.get_inode_at_path_inner(
1290 inodes,
1291 new_base_inode,
1292 &new_path,
1293 symlink_count + 1,
1294 follow_symlinks,
1295 )?;
1296 cur_inode = symlink_inode;
1297 let guard = cur_inode.read();
1300 if let Kind::File { .. } = guard.deref() {
1301 if last_component {
1303 break 'symlink_resolution;
1304 }
1305 }
1306 continue 'symlink_resolution;
1307 }
1308 }
1309 break 'symlink_resolution;
1310 }
1311 }
1312
1313 Ok(cur_inode)
1314 }
1315
1316 fn path_into_pre_open_and_relative_path<'path>(
1326 &self,
1327 path: &'path Path,
1328 ) -> Result<(WasiFd, &'path Path), Errno> {
1329 enum BaseFdAndRelPath<'a> {
1330 None,
1331 BestMatch {
1332 fd: WasiFd,
1333 rel_path: &'a Path,
1334 max_seen: usize,
1335 },
1336 }
1337
1338 impl BaseFdAndRelPath<'_> {
1339 const fn max_seen(&self) -> usize {
1340 match self {
1341 Self::None => 0,
1342 Self::BestMatch { max_seen, .. } => *max_seen,
1343 }
1344 }
1345 }
1346 let mut res = BaseFdAndRelPath::None;
1347 let preopen_fds = self.preopen_fds.read().unwrap();
1349 for po_fd in preopen_fds.deref() {
1350 let po_inode = self
1351 .fd_map
1352 .read()
1353 .unwrap()
1354 .get(*po_fd)
1355 .unwrap()
1356 .inode
1357 .clone();
1358 let guard = po_inode.read();
1359 let po_path = match guard.deref() {
1360 Kind::Dir { path, .. } => &**path,
1361 Kind::Root { .. } => Path::new("/"),
1362 _ => unreachable!("Preopened FD that's not a directory or the root"),
1363 };
1364 if let Ok(stripped_path) = path.strip_prefix(po_path) {
1366 let new_prefix_len = po_path.as_os_str().len();
1368 if new_prefix_len >= res.max_seen() {
1371 res = BaseFdAndRelPath::BestMatch {
1372 fd: *po_fd,
1373 rel_path: stripped_path,
1374 max_seen: new_prefix_len,
1375 };
1376 }
1377 }
1378 }
1379 match res {
1380 BaseFdAndRelPath::None => Err(Errno::Inval),
1382 BaseFdAndRelPath::BestMatch { fd, rel_path, .. } => Ok((fd, rel_path)),
1383 }
1384 }
1385
1386 pub(crate) fn path_depth_from_fd(&self, fd: WasiFd, inode: InodeGuard) -> Result<usize, Errno> {
1389 let mut counter = 0;
1390 let base_inode = self.get_fd_inode(fd)?;
1391 let mut cur_inode = inode;
1392
1393 while cur_inode.ino() != base_inode.ino() {
1394 counter += 1;
1395
1396 let processing_cur_inode = cur_inode.clone();
1397 let guard = processing_cur_inode.read();
1398
1399 match guard.deref() {
1400 Kind::Dir { parent, .. } => {
1401 if let Some(p) = parent.upgrade() {
1402 cur_inode = p;
1403 }
1404 }
1405 _ => return Err(Errno::Inval),
1406 }
1407 }
1408
1409 Ok(counter)
1410 }
1411
1412 pub(crate) fn get_inode_at_path(
1419 &self,
1420 inodes: &WasiInodes,
1421 base: WasiFd,
1422 path: &str,
1423 follow_symlinks: bool,
1424 ) -> Result<InodeGuard, Errno> {
1425 let base_inode = self.get_fd_inode(base)?;
1426 self.get_inode_at_path_inner(inodes, base_inode, path, 0, follow_symlinks)
1427 }
1428
1429 pub(crate) fn get_parent_inode_at_path(
1432 &self,
1433 inodes: &WasiInodes,
1434 base: WasiFd,
1435 path: &Path,
1436 follow_symlinks: bool,
1437 ) -> Result<(InodeGuard, String), Errno> {
1438 let mut parent_dir = std::path::PathBuf::new();
1439 let mut components = path.components().rev();
1440 let new_entity_name = components
1441 .next()
1442 .ok_or(Errno::Inval)?
1443 .as_os_str()
1444 .to_string_lossy()
1445 .to_string();
1446 for comp in components.rev() {
1447 parent_dir.push(comp);
1448 }
1449 self.get_inode_at_path(inodes, base, &parent_dir.to_string_lossy(), follow_symlinks)
1450 .map(|v| (v, new_entity_name))
1451 }
1452
1453 pub fn get_fd(&self, fd: WasiFd) -> Result<Fd, Errno> {
1454 let ret = self
1455 .fd_map
1456 .read()
1457 .unwrap()
1458 .get(fd)
1459 .ok_or(Errno::Badf)
1460 .cloned();
1461
1462 if ret.is_err() && fd == VIRTUAL_ROOT_FD {
1463 Ok(Fd {
1464 inner: FdInner {
1465 rights: ALL_RIGHTS,
1466 rights_inheriting: ALL_RIGHTS,
1467 flags: Fdflags::empty(),
1468 offset: Arc::new(AtomicU64::new(0)),
1469 fd_flags: Fdflagsext::empty(),
1470 },
1471 open_flags: 0,
1472 inode: self.root_inode.clone(),
1473 is_stdio: false,
1474 })
1475 } else {
1476 ret
1477 }
1478 }
1479
1480 pub fn get_fd_inode(&self, fd: WasiFd) -> Result<InodeGuard, Errno> {
1481 if fd == VIRTUAL_ROOT_FD {
1483 return Ok(self.root_inode.clone());
1484 }
1485 self.fd_map
1486 .read()
1487 .unwrap()
1488 .get(fd)
1489 .ok_or(Errno::Badf)
1490 .map(|a| a.inode.clone())
1491 }
1492
1493 pub fn filestat_fd(&self, fd: WasiFd) -> Result<Filestat, Errno> {
1494 let inode = self.get_fd_inode(fd)?;
1495 let guard = inode.stat.read().unwrap();
1496 Ok(*guard.deref())
1497 }
1498
1499 pub fn fdstat(&self, fd: WasiFd) -> Result<Fdstat, Errno> {
1500 match fd {
1501 __WASI_STDIN_FILENO => {
1502 return Ok(Fdstat {
1503 fs_filetype: Filetype::CharacterDevice,
1504 fs_flags: Fdflags::empty(),
1505 fs_rights_base: STDIN_DEFAULT_RIGHTS,
1506 fs_rights_inheriting: Rights::empty(),
1507 });
1508 }
1509 __WASI_STDOUT_FILENO => {
1510 return Ok(Fdstat {
1511 fs_filetype: Filetype::CharacterDevice,
1512 fs_flags: Fdflags::APPEND,
1513 fs_rights_base: STDOUT_DEFAULT_RIGHTS,
1514 fs_rights_inheriting: Rights::empty(),
1515 });
1516 }
1517 __WASI_STDERR_FILENO => {
1518 return Ok(Fdstat {
1519 fs_filetype: Filetype::CharacterDevice,
1520 fs_flags: Fdflags::APPEND,
1521 fs_rights_base: STDERR_DEFAULT_RIGHTS,
1522 fs_rights_inheriting: Rights::empty(),
1523 });
1524 }
1525 VIRTUAL_ROOT_FD => {
1526 return Ok(Fdstat {
1527 fs_filetype: Filetype::Directory,
1528 fs_flags: Fdflags::empty(),
1529 fs_rights_base: ALL_RIGHTS,
1531 fs_rights_inheriting: ALL_RIGHTS,
1532 });
1533 }
1534 _ => (),
1535 }
1536 let fd = self.get_fd(fd)?;
1537
1538 let guard = fd.inode.read();
1539 let deref = guard.deref();
1540 Ok(Fdstat {
1541 fs_filetype: match deref {
1542 Kind::File { .. } => Filetype::RegularFile,
1543 Kind::Dir { .. } => Filetype::Directory,
1544 Kind::Symlink { .. } => Filetype::SymbolicLink,
1545 Kind::Socket { socket } => match &socket.inner.protected.read().unwrap().kind {
1546 InodeSocketKind::TcpStream { .. } => Filetype::SocketStream,
1547 InodeSocketKind::Raw { .. } => Filetype::SocketRaw,
1548 InodeSocketKind::PreSocket { props, .. } => match props.ty {
1549 Socktype::Stream => Filetype::SocketStream,
1550 Socktype::Dgram => Filetype::SocketDgram,
1551 Socktype::Raw => Filetype::SocketRaw,
1552 Socktype::Seqpacket => Filetype::SocketSeqpacket,
1553 _ => Filetype::Unknown,
1554 },
1555 _ => Filetype::Unknown,
1556 },
1557 _ => Filetype::Unknown,
1558 },
1559 fs_flags: fd.inner.flags,
1560 fs_rights_base: fd.inner.rights,
1561 fs_rights_inheriting: fd.inner.rights_inheriting, })
1563 }
1564
1565 pub fn prestat_fd(&self, fd: WasiFd) -> Result<Prestat, Errno> {
1566 let inode = self.get_fd_inode(fd)?;
1567 if inode.is_preopened {
1570 Ok(self.prestat_fd_inner(inode.deref()))
1571 } else {
1572 Err(Errno::Badf)
1573 }
1574 }
1575
1576 pub(crate) fn prestat_fd_inner(&self, inode_val: &InodeVal) -> Prestat {
1577 Prestat {
1578 pr_type: Preopentype::Dir,
1579 u: PrestatEnum::Dir {
1580 pr_name_len: inode_val.name.read().unwrap().len() as u32,
1582 }
1583 .untagged(),
1584 }
1585 }
1586
1587 #[allow(clippy::await_holding_lock)]
1588 pub async fn flush(&self, fd: WasiFd) -> Result<(), Errno> {
1589 match fd {
1590 __WASI_STDIN_FILENO => (),
1591 __WASI_STDOUT_FILENO => {
1592 let mut file =
1593 WasiInodes::stdout_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1594 file.flush().await.map_err(map_io_err)?
1595 }
1596 __WASI_STDERR_FILENO => {
1597 let mut file =
1598 WasiInodes::stderr_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1599 file.flush().await.map_err(map_io_err)?
1600 }
1601 _ => {
1602 let fd = self.get_fd(fd)?;
1603 if !fd.inner.rights.contains(Rights::FD_DATASYNC) {
1604 return Err(Errno::Access);
1605 }
1606
1607 let file = {
1608 let guard = fd.inode.read();
1609 match guard.deref() {
1610 Kind::File {
1611 handle: Some(file), ..
1612 } => file.clone(),
1613 Kind::Dir { .. } => return Err(Errno::Isdir),
1615 Kind::Buffer { .. } => return Ok(()),
1616 _ => return Err(Errno::Io),
1617 }
1618 };
1619 drop(fd);
1620
1621 struct FlushPoller {
1622 file: Arc<RwLock<Box<dyn VirtualFile + Send + Sync>>>,
1623 }
1624 impl Future for FlushPoller {
1625 type Output = Result<(), Errno>;
1626 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1627 let mut file = self.file.write().unwrap();
1628 Pin::new(file.as_mut())
1629 .poll_flush(cx)
1630 .map_err(|_| Errno::Io)
1631 }
1632 }
1633 FlushPoller { file }.await?;
1634 }
1635 }
1636 Ok(())
1637 }
1638
1639 pub(crate) fn create_inode(
1641 &self,
1642 inodes: &WasiInodes,
1643 kind: Kind,
1644 is_preopened: bool,
1645 name: String,
1646 ) -> Result<InodeGuard, Errno> {
1647 let stat = self.get_stat_for_kind(&kind)?;
1648 Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name.into(), stat))
1649 }
1650
1651 pub(crate) fn create_inode_with_default_stat(
1653 &self,
1654 inodes: &WasiInodes,
1655 kind: Kind,
1656 is_preopened: bool,
1657 name: Cow<'static, str>,
1658 ) -> InodeGuard {
1659 let stat = Filestat::default();
1660 self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)
1661 }
1662
1663 pub(crate) fn create_inode_with_stat(
1665 &self,
1666 inodes: &WasiInodes,
1667 kind: Kind,
1668 is_preopened: bool,
1669 name: Cow<'static, str>,
1670 mut stat: Filestat,
1671 ) -> InodeGuard {
1672 match &kind {
1673 Kind::File {
1674 handle: Some(handle),
1675 ..
1676 } => {
1677 let guard = handle.read().unwrap();
1678 stat.st_size = guard.size();
1679 }
1680 Kind::Buffer { buffer } => {
1681 stat.st_size = buffer.len() as u64;
1682 }
1683 _ => {}
1684 }
1685
1686 let st_ino = Inode::from_path(&name);
1687 stat.st_ino = st_ino.as_u64();
1688
1689 inodes.add_inode_val(InodeVal {
1690 stat: RwLock::new(stat),
1691 is_preopened,
1692 name: RwLock::new(name),
1693 kind: RwLock::new(kind),
1694 })
1695 }
1696
1697 pub fn create_fd(
1698 &self,
1699 rights: Rights,
1700 rights_inheriting: Rights,
1701 fs_flags: Fdflags,
1702 fd_flags: Fdflagsext,
1703 open_flags: u16,
1704 inode: InodeGuard,
1705 ) -> Result<WasiFd, Errno> {
1706 self.create_fd_ext(
1707 rights,
1708 rights_inheriting,
1709 fs_flags,
1710 fd_flags,
1711 open_flags,
1712 inode,
1713 None,
1714 false,
1715 )
1716 }
1717
1718 #[allow(clippy::too_many_arguments)]
1719 pub fn with_fd(
1720 &self,
1721 rights: Rights,
1722 rights_inheriting: Rights,
1723 fs_flags: Fdflags,
1724 fd_flags: Fdflagsext,
1725 open_flags: u16,
1726 inode: InodeGuard,
1727 idx: WasiFd,
1728 ) -> Result<(), Errno> {
1729 self.create_fd_ext(
1730 rights,
1731 rights_inheriting,
1732 fs_flags,
1733 fd_flags,
1734 open_flags,
1735 inode,
1736 Some(idx),
1737 true,
1738 )?;
1739 Ok(())
1740 }
1741
1742 #[allow(clippy::too_many_arguments)]
1743 pub fn create_fd_ext(
1744 &self,
1745 rights: Rights,
1746 rights_inheriting: Rights,
1747 fs_flags: Fdflags,
1748 fd_flags: Fdflagsext,
1749 open_flags: u16,
1750 inode: InodeGuard,
1751 idx: Option<WasiFd>,
1752 exclusive: bool,
1753 ) -> Result<WasiFd, Errno> {
1754 let is_stdio = matches!(
1755 idx,
1756 Some(__WASI_STDIN_FILENO) | Some(__WASI_STDOUT_FILENO) | Some(__WASI_STDERR_FILENO)
1757 );
1758 let fd = Fd {
1759 inner: FdInner {
1760 rights,
1761 rights_inheriting,
1762 flags: fs_flags,
1763 offset: Arc::new(AtomicU64::new(0)),
1764 fd_flags,
1765 },
1766 open_flags,
1767 inode,
1768 is_stdio,
1769 };
1770
1771 let mut guard = self.fd_map.write().unwrap();
1772
1773 match idx {
1774 Some(idx) => {
1775 if guard.insert(exclusive, idx, fd) {
1776 Ok(idx)
1777 } else {
1778 Err(Errno::Exist)
1779 }
1780 }
1781 None => Ok(guard.insert_first_free(fd)),
1782 }
1783 }
1784
1785 pub fn clone_fd(&self, fd: WasiFd) -> Result<WasiFd, Errno> {
1786 self.clone_fd_ext(fd, 0, None)
1787 }
1788
1789 pub fn clone_fd_ext(
1790 &self,
1791 fd: WasiFd,
1792 min_result_fd: WasiFd,
1793 cloexec: Option<bool>,
1794 ) -> Result<WasiFd, Errno> {
1795 let fd = self.get_fd(fd)?;
1796 Ok(self.fd_map.write().unwrap().insert_first_free_after(
1797 Fd {
1798 inner: FdInner {
1799 rights: fd.inner.rights,
1800 rights_inheriting: fd.inner.rights_inheriting,
1801 flags: fd.inner.flags,
1802 offset: fd.inner.offset.clone(),
1803 fd_flags: match cloexec {
1804 None => fd.inner.fd_flags,
1805 Some(cloexec) => {
1806 let mut f = fd.inner.fd_flags;
1807 f.set(Fdflagsext::CLOEXEC, cloexec);
1808 f
1809 }
1810 },
1811 },
1812 open_flags: fd.open_flags,
1813 inode: fd.inode,
1814 is_stdio: fd.is_stdio,
1815 },
1816 min_result_fd,
1817 ))
1818 }
1819
1820 pub unsafe fn remove_inode(&self, inodes: &WasiInodes, ino: Inode) -> Option<Arc<InodeVal>> {
1829 let mut guard = inodes.protected.write().unwrap();
1830 guard.lookup.remove(&ino).and_then(|a| Weak::upgrade(&a))
1831 }
1832
1833 pub(crate) fn create_stdout(&self, inodes: &WasiInodes) {
1834 self.create_std_dev_inner(
1835 inodes,
1836 Box::<Stdout>::default(),
1837 "stdout",
1838 __WASI_STDOUT_FILENO,
1839 STDOUT_DEFAULT_RIGHTS,
1840 Fdflags::APPEND,
1841 FS_STDOUT_INO,
1842 );
1843 }
1844
1845 pub(crate) fn create_stdin(&self, inodes: &WasiInodes) {
1846 self.create_std_dev_inner(
1847 inodes,
1848 Box::<Stdin>::default(),
1849 "stdin",
1850 __WASI_STDIN_FILENO,
1851 STDIN_DEFAULT_RIGHTS,
1852 Fdflags::empty(),
1853 FS_STDIN_INO,
1854 );
1855 }
1856
1857 pub(crate) fn create_stderr(&self, inodes: &WasiInodes) {
1858 self.create_std_dev_inner(
1859 inodes,
1860 Box::<Stderr>::default(),
1861 "stderr",
1862 __WASI_STDERR_FILENO,
1863 STDERR_DEFAULT_RIGHTS,
1864 Fdflags::APPEND,
1865 FS_STDERR_INO,
1866 );
1867 }
1868
1869 pub(crate) fn create_rootfd(&self) -> Result<(), String> {
1870 let all_rights = ALL_RIGHTS;
1872 let root_rights = all_rights
1875 ;
1891 let fd = self
1892 .create_fd(
1893 root_rights,
1894 root_rights,
1895 Fdflags::empty(),
1896 Fdflagsext::empty(),
1897 Fd::READ,
1898 self.root_inode.clone(),
1899 )
1900 .map_err(|e| format!("Could not create root fd: {e}"))?;
1901 self.preopen_fds.write().unwrap().push(fd);
1902 Ok(())
1903 }
1904
1905 pub(crate) fn create_preopens(
1906 &self,
1907 inodes: &WasiInodes,
1908 ignore_duplicates: bool,
1909 ) -> Result<(), String> {
1910 for preopen_name in self.init_vfs_preopens.iter() {
1911 let kind = Kind::Dir {
1912 parent: self.root_inode.downgrade(),
1913 path: PathBuf::from(preopen_name),
1914 entries: Default::default(),
1915 };
1916 let rights = Rights::FD_ADVISE
1917 | Rights::FD_TELL
1918 | Rights::FD_SEEK
1919 | Rights::FD_READ
1920 | Rights::PATH_OPEN
1921 | Rights::FD_READDIR
1922 | Rights::PATH_READLINK
1923 | Rights::PATH_FILESTAT_GET
1924 | Rights::FD_FILESTAT_GET
1925 | Rights::PATH_LINK_SOURCE
1926 | Rights::PATH_RENAME_SOURCE
1927 | Rights::POLL_FD_READWRITE
1928 | Rights::SOCK_SHUTDOWN;
1929 let inode = self
1930 .create_inode(inodes, kind, true, preopen_name.clone())
1931 .map_err(|e| {
1932 format!(
1933 "Failed to create inode for preopened dir (name `{preopen_name}`): WASI error code: {e}",
1934 )
1935 })?;
1936 let fd_flags = Fd::READ;
1937 let fd = self
1938 .create_fd(
1939 rights,
1940 rights,
1941 Fdflags::empty(),
1942 Fdflagsext::empty(),
1943 fd_flags,
1944 inode.clone(),
1945 )
1946 .map_err(|e| format!("Could not open fd for file {preopen_name:?}: {e}"))?;
1947 {
1948 let mut guard = self.root_inode.write();
1949 if let Kind::Root { entries } = guard.deref_mut() {
1950 let existing_entry = entries.insert(preopen_name.clone(), inode);
1951 if existing_entry.is_some() && !ignore_duplicates {
1952 return Err(format!("Found duplicate entry for alias `{preopen_name}`"));
1953 }
1954 }
1955 }
1956 self.preopen_fds.write().unwrap().push(fd);
1957 }
1958
1959 for PreopenedDir {
1960 path,
1961 alias,
1962 read,
1963 write,
1964 create,
1965 } in self.init_preopens.iter()
1966 {
1967 debug!(
1968 "Attempting to preopen {} with alias {:?}",
1969 &path.to_string_lossy(),
1970 &alias
1971 );
1972 let cur_dir_metadata = self
1973 .root_fs
1974 .metadata(path)
1975 .map_err(|e| format!("Could not get metadata for file {path:?}: {e}"))?;
1976
1977 let kind = if cur_dir_metadata.is_dir() {
1978 Kind::Dir {
1979 parent: self.root_inode.downgrade(),
1980 path: path.clone(),
1981 entries: Default::default(),
1982 }
1983 } else {
1984 return Err(format!(
1985 "WASI only supports pre-opened directories right now; found \"{}\"",
1986 &path.to_string_lossy()
1987 ));
1988 };
1989
1990 let rights = {
1991 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
1993 if *read {
1994 rights |= Rights::FD_READ
1995 | Rights::PATH_OPEN
1996 | Rights::FD_READDIR
1997 | Rights::PATH_READLINK
1998 | Rights::PATH_FILESTAT_GET
1999 | Rights::FD_FILESTAT_GET
2000 | Rights::PATH_LINK_SOURCE
2001 | Rights::PATH_RENAME_SOURCE
2002 | Rights::POLL_FD_READWRITE
2003 | Rights::SOCK_SHUTDOWN;
2004 }
2005 if *write {
2006 rights |= Rights::FD_DATASYNC
2007 | Rights::FD_FDSTAT_SET_FLAGS
2008 | Rights::FD_WRITE
2009 | Rights::FD_SYNC
2010 | Rights::FD_ALLOCATE
2011 | Rights::PATH_OPEN
2012 | Rights::PATH_RENAME_TARGET
2013 | Rights::PATH_FILESTAT_SET_SIZE
2014 | Rights::PATH_FILESTAT_SET_TIMES
2015 | Rights::FD_FILESTAT_SET_SIZE
2016 | Rights::FD_FILESTAT_SET_TIMES
2017 | Rights::PATH_REMOVE_DIRECTORY
2018 | Rights::PATH_UNLINK_FILE
2019 | Rights::POLL_FD_READWRITE
2020 | Rights::SOCK_SHUTDOWN;
2021 }
2022 if *create {
2023 rights |= Rights::PATH_CREATE_DIRECTORY
2024 | Rights::PATH_CREATE_FILE
2025 | Rights::PATH_LINK_TARGET
2026 | Rights::PATH_OPEN
2027 | Rights::PATH_RENAME_TARGET
2028 | Rights::PATH_SYMLINK;
2029 }
2030
2031 rights
2032 };
2033 let inode = if let Some(alias) = &alias {
2034 self.create_inode(inodes, kind, true, alias.clone())
2035 } else {
2036 self.create_inode(inodes, kind, true, path.to_string_lossy().into_owned())
2037 }
2038 .map_err(|e| {
2039 format!("Failed to create inode for preopened dir: WASI error code: {e}")
2040 })?;
2041 let fd_flags = {
2042 let mut fd_flags = 0;
2043 if *read {
2044 fd_flags |= Fd::READ;
2045 }
2046 if *write {
2047 fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE;
2049 }
2050 if *create {
2051 fd_flags |= Fd::CREATE;
2052 }
2053 fd_flags
2054 };
2055 let fd = self
2056 .create_fd(
2057 rights,
2058 rights,
2059 Fdflags::empty(),
2060 Fdflagsext::empty(),
2061 fd_flags,
2062 inode.clone(),
2063 )
2064 .map_err(|e| format!("Could not open fd for file {path:?}: {e}"))?;
2065 {
2066 let mut guard = self.root_inode.write();
2067 if let Kind::Root { entries } = guard.deref_mut() {
2068 let key = if let Some(alias) = &alias {
2069 alias.clone()
2070 } else {
2071 path.to_string_lossy().into_owned()
2072 };
2073 let existing_entry = entries.insert(key.clone(), inode);
2074 if existing_entry.is_some() && !ignore_duplicates {
2075 return Err(format!("Found duplicate entry for alias `{key}`"));
2076 }
2077 }
2078 }
2079 self.preopen_fds.write().unwrap().push(fd);
2080 }
2081
2082 Ok(())
2083 }
2084
2085 #[allow(clippy::too_many_arguments)]
2086 pub(crate) fn create_std_dev_inner(
2087 &self,
2088 inodes: &WasiInodes,
2089 handle: Box<dyn VirtualFile + Send + Sync + 'static>,
2090 name: &'static str,
2091 raw_fd: WasiFd,
2092 rights: Rights,
2093 fd_flags: Fdflags,
2094 st_ino: Inode,
2095 ) {
2096 let inode = {
2097 let stat = Filestat {
2098 st_filetype: Filetype::CharacterDevice,
2099 st_ino: st_ino.as_u64(),
2100 ..Filestat::default()
2101 };
2102 let kind = Kind::File {
2103 fd: Some(raw_fd),
2104 handle: Some(Arc::new(RwLock::new(handle))),
2105 path: "".into(),
2106 };
2107 inodes.add_inode_val(InodeVal {
2108 stat: RwLock::new(stat),
2109 is_preopened: true,
2110 name: RwLock::new(name.to_string().into()),
2111 kind: RwLock::new(kind),
2112 })
2113 };
2114 self.fd_map.write().unwrap().insert(
2115 false,
2116 raw_fd,
2117 Fd {
2118 inner: FdInner {
2119 rights,
2120 rights_inheriting: Rights::empty(),
2121 flags: fd_flags,
2122 offset: Arc::new(AtomicU64::new(0)),
2123 fd_flags: Fdflagsext::empty(),
2124 },
2125 open_flags: 0,
2127 inode,
2128 is_stdio: true,
2129 },
2130 );
2131 }
2132
2133 pub fn get_stat_for_kind(&self, kind: &Kind) -> Result<Filestat, Errno> {
2134 let md = match kind {
2135 Kind::File { handle, path, .. } => match handle {
2136 Some(wf) => {
2137 let wf = wf.read().unwrap();
2138 return Ok(Filestat {
2139 st_filetype: Filetype::RegularFile,
2140 st_ino: Inode::from_path(path.to_string_lossy().as_ref()).as_u64(),
2141 st_size: wf.size(),
2142 st_atim: wf.last_accessed(),
2143 st_mtim: wf.last_modified(),
2144 st_ctim: wf.created_time(),
2145
2146 ..Filestat::default()
2147 });
2148 }
2149 None => self
2150 .root_fs
2151 .metadata(path)
2152 .map_err(fs_error_into_wasi_err)?,
2153 },
2154 Kind::Dir { path, .. } => self
2155 .root_fs
2156 .metadata(path)
2157 .map_err(fs_error_into_wasi_err)?,
2158 Kind::Symlink {
2159 base_po_dir,
2160 path_to_symlink,
2161 ..
2162 } => {
2163 let guard = self.fd_map.read().unwrap();
2164 let base_po_inode = &guard.get(*base_po_dir).unwrap().inode;
2165 let guard = base_po_inode.read();
2166 match guard.deref() {
2167 Kind::Root { .. } => self
2168 .root_fs
2169 .symlink_metadata(path_to_symlink)
2170 .map_err(fs_error_into_wasi_err)?,
2171 Kind::Dir { path, .. } => {
2172 let mut real_path = path.clone();
2173 real_path.push(path_to_symlink);
2180 self.root_fs
2181 .symlink_metadata(&real_path)
2182 .map_err(fs_error_into_wasi_err)?
2183 }
2184 _ => unreachable!(
2186 "Symlink pointing to something that's not a directory as its base preopened directory"
2187 ),
2188 }
2189 }
2190 _ => return Err(Errno::Io),
2191 };
2192 Ok(Filestat {
2193 st_filetype: virtual_file_type_to_wasi_file_type(md.file_type()),
2194 st_size: md.len(),
2195 st_atim: md.accessed(),
2196 st_mtim: md.modified(),
2197 st_ctim: md.created(),
2198 ..Filestat::default()
2199 })
2200 }
2201
2202 pub(crate) fn close_fd(&self, fd: WasiFd) -> Result<(), Errno> {
2204 let mut fd_map = self.fd_map.write().unwrap();
2205
2206 let pfd = fd_map.remove(fd).ok_or(Errno::Badf);
2207 match pfd {
2208 Ok(fd_ref) => {
2209 let inode = fd_ref.inode.ino().as_u64();
2210 let ref_cnt = fd_ref.inode.ref_cnt();
2211 if ref_cnt == 1 {
2212 trace!(%fd, %inode, %ref_cnt, "closing file descriptor");
2213 } else {
2214 trace!(%fd, %inode, %ref_cnt, "weakening file descriptor");
2215 }
2216 }
2217 Err(err) => {
2218 trace!(%fd, "closing file descriptor failed - {}", err);
2219 }
2220 }
2221 Ok(())
2222 }
2223}
2224
2225impl std::fmt::Debug for WasiFs {
2226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2227 if let Ok(guard) = self.current_dir.try_lock() {
2228 write!(f, "current_dir={} ", guard.as_str())?;
2229 } else {
2230 write!(f, "current_dir=(locked) ")?;
2231 }
2232 if let Ok(guard) = self.fd_map.read() {
2233 write!(
2234 f,
2235 "next_fd={} max_fd={:?} ",
2236 guard.next_free_fd(),
2237 guard.last_fd()
2238 )?;
2239 } else {
2240 write!(f, "next_fd=(locked) max_fd=(locked) ")?;
2241 }
2242 write!(f, "{:?}", self.root_fs)
2243 }
2244}
2245
2246pub fn default_fs_backing() -> Arc<dyn virtual_fs::FileSystem + Send + Sync> {
2248 cfg_if::cfg_if! {
2249 if #[cfg(feature = "host-fs")] {
2250 Arc::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
2251 } else if #[cfg(not(feature = "host-fs"))] {
2252 Arc::<virtual_fs::mem_fs::FileSystem>::default()
2253 } else {
2254 Arc::<FallbackFileSystem>::default()
2255 }
2256 }
2257}
2258
2259#[derive(Debug, Default)]
2260pub struct FallbackFileSystem;
2261
2262impl FallbackFileSystem {
2263 fn fail() -> ! {
2264 panic!(
2265 "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`"
2266 );
2267 }
2268}
2269
2270impl FileSystem for FallbackFileSystem {
2271 fn readlink(&self, _path: &Path) -> virtual_fs::Result<PathBuf> {
2272 Self::fail()
2273 }
2274 fn read_dir(&self, _path: &Path) -> Result<virtual_fs::ReadDir, FsError> {
2275 Self::fail();
2276 }
2277 fn create_dir(&self, _path: &Path) -> Result<(), FsError> {
2278 Self::fail();
2279 }
2280 fn remove_dir(&self, _path: &Path) -> Result<(), FsError> {
2281 Self::fail();
2282 }
2283 fn rename<'a>(&'a self, _from: &Path, _to: &Path) -> BoxFuture<'a, Result<(), FsError>> {
2284 Self::fail();
2285 }
2286 fn metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2287 Self::fail();
2288 }
2289 fn symlink_metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2290 Self::fail();
2291 }
2292 fn remove_file(&self, _path: &Path) -> Result<(), FsError> {
2293 Self::fail();
2294 }
2295 fn new_open_options(&self) -> virtual_fs::OpenOptions<'_> {
2296 Self::fail();
2297 }
2298 fn mount(
2299 &self,
2300 _name: String,
2301 _path: &Path,
2302 _fs: Box<dyn FileSystem + Send + Sync>,
2303 ) -> virtual_fs::Result<()> {
2304 Self::fail()
2305 }
2306}
2307
2308pub fn virtual_file_type_to_wasi_file_type(file_type: virtual_fs::FileType) -> Filetype {
2309 if file_type.is_dir() {
2311 Filetype::Directory
2312 } else if file_type.is_file() {
2313 Filetype::RegularFile
2314 } else if file_type.is_symlink() {
2315 Filetype::SymbolicLink
2316 } else {
2317 Filetype::Unknown
2318 }
2319}
2320
2321pub fn fs_error_from_wasi_err(err: Errno) -> FsError {
2322 match err {
2323 Errno::Badf => FsError::InvalidFd,
2324 Errno::Exist => FsError::AlreadyExists,
2325 Errno::Io => FsError::IOError,
2326 Errno::Addrinuse => FsError::AddressInUse,
2327 Errno::Addrnotavail => FsError::AddressNotAvailable,
2328 Errno::Pipe => FsError::BrokenPipe,
2329 Errno::Connaborted => FsError::ConnectionAborted,
2330 Errno::Connrefused => FsError::ConnectionRefused,
2331 Errno::Connreset => FsError::ConnectionReset,
2332 Errno::Intr => FsError::Interrupted,
2333 Errno::Inval => FsError::InvalidInput,
2334 Errno::Notconn => FsError::NotConnected,
2335 Errno::Nodev => FsError::NoDevice,
2336 Errno::Noent => FsError::EntryNotFound,
2337 Errno::Perm => FsError::PermissionDenied,
2338 Errno::Timedout => FsError::TimedOut,
2339 Errno::Proto => FsError::UnexpectedEof,
2340 Errno::Again => FsError::WouldBlock,
2341 Errno::Nospc => FsError::WriteZero,
2342 Errno::Notempty => FsError::DirectoryNotEmpty,
2343 _ => FsError::UnknownError,
2344 }
2345}
2346
2347pub fn fs_error_into_wasi_err(fs_error: FsError) -> Errno {
2348 match fs_error {
2349 FsError::AlreadyExists => Errno::Exist,
2350 FsError::AddressInUse => Errno::Addrinuse,
2351 FsError::AddressNotAvailable => Errno::Addrnotavail,
2352 FsError::BaseNotDirectory => Errno::Notdir,
2353 FsError::BrokenPipe => Errno::Pipe,
2354 FsError::ConnectionAborted => Errno::Connaborted,
2355 FsError::ConnectionRefused => Errno::Connrefused,
2356 FsError::ConnectionReset => Errno::Connreset,
2357 FsError::Interrupted => Errno::Intr,
2358 FsError::InvalidData => Errno::Io,
2359 FsError::InvalidFd => Errno::Badf,
2360 FsError::InvalidInput => Errno::Inval,
2361 FsError::IOError => Errno::Io,
2362 FsError::NoDevice => Errno::Nodev,
2363 FsError::NotAFile => Errno::Inval,
2364 FsError::NotConnected => Errno::Notconn,
2365 FsError::EntryNotFound => Errno::Noent,
2366 FsError::PermissionDenied => Errno::Perm,
2367 FsError::TimedOut => Errno::Timedout,
2368 FsError::UnexpectedEof => Errno::Proto,
2369 FsError::WouldBlock => Errno::Again,
2370 FsError::WriteZero => Errno::Nospc,
2371 FsError::DirectoryNotEmpty => Errno::Notempty,
2372 FsError::StorageFull => Errno::Overflow,
2373 FsError::Lock | FsError::UnknownError => Errno::Io,
2374 FsError::Unsupported => Errno::Notsup,
2375 }
2376}
2377
2378#[cfg(test)]
2379mod tests {
2380 use super::*;
2381
2382 #[tokio::test]
2383 async fn test_relative_path_to_absolute() {
2384 let inodes = WasiInodes::new();
2385 let fs_backing = WasiFsRoot::Sandbox(TmpFileSystem::new());
2386 let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap();
2387
2388 assert_eq!(
2390 wasi_fs.relative_path_to_absolute("/foo/bar".to_string()),
2391 "/foo/bar"
2392 );
2393 assert_eq!(wasi_fs.relative_path_to_absolute("/".to_string()), "/");
2394
2395 assert_eq!(
2397 wasi_fs.relative_path_to_absolute("//foo//bar//".to_string()),
2398 "//foo//bar//"
2399 );
2400 assert_eq!(
2401 wasi_fs.relative_path_to_absolute("/a/b/./c".to_string()),
2402 "/a/b/./c"
2403 );
2404 assert_eq!(
2405 wasi_fs.relative_path_to_absolute("/a/b/../c".to_string()),
2406 "/a/b/../c"
2407 );
2408
2409 assert_eq!(
2411 wasi_fs.relative_path_to_absolute("foo/bar".to_string()),
2412 "/foo/bar"
2413 );
2414 assert_eq!(wasi_fs.relative_path_to_absolute("foo".to_string()), "/foo");
2415
2416 wasi_fs.set_current_dir("/home/user");
2418 assert_eq!(
2419 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2420 "/home/user/file.txt"
2421 );
2422 assert_eq!(
2423 wasi_fs.relative_path_to_absolute("dir/file.txt".to_string()),
2424 "/home/user/dir/file.txt"
2425 );
2426
2427 wasi_fs.set_current_dir("/a/b/c");
2429 assert_eq!(
2430 wasi_fs.relative_path_to_absolute("./file.txt".to_string()),
2431 "/a/b/c/./file.txt"
2432 );
2433 assert_eq!(
2434 wasi_fs.relative_path_to_absolute("../file.txt".to_string()),
2435 "/a/b/c/../file.txt"
2436 );
2437 assert_eq!(
2438 wasi_fs.relative_path_to_absolute("../../file.txt".to_string()),
2439 "/a/b/c/../../file.txt"
2440 );
2441
2442 assert_eq!(
2444 wasi_fs.relative_path_to_absolute(".".to_string()),
2445 "/a/b/c/."
2446 );
2447 assert_eq!(
2448 wasi_fs.relative_path_to_absolute("..".to_string()),
2449 "/a/b/c/.."
2450 );
2451 assert_eq!(wasi_fs.relative_path_to_absolute("".to_string()), "/a/b/c/");
2452
2453 wasi_fs.set_current_dir("/home/user/");
2455 assert_eq!(
2456 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2457 "/home/user/file.txt"
2458 );
2459
2460 wasi_fs.set_current_dir("/home/user");
2462 assert_eq!(
2463 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2464 "/home/user/file.txt"
2465 );
2466 }
2467}