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::{Fd, FdInner, InodeVal, Kind};
51pub(crate) use self::inode_guard::{
52 InodeValFilePollGuard, InodeValFilePollGuardJoin, InodeValFilePollGuardMode,
53 InodeValFileReadGuard, InodeValFileWriteGuard, 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 EphemeralSymlinkEntry {
244 base_po_dir: WasiFd,
245 path_to_symlink: PathBuf,
246 relative_path: PathBuf,
247}
248
249#[derive(Debug)]
250#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
251struct WasiInodesProtected {
252 lookup: HashMap<Inode, Weak<InodeVal>>,
253}
254
255#[derive(Clone, Debug)]
256#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
257pub struct WasiInodes {
258 protected: Arc<RwLock<WasiInodesProtected>>,
259}
260
261fn normalize_virtual_symlink_key(path: &Path) -> PathBuf {
262 let mut normalized = PathBuf::new();
263 for component in path.components() {
264 match component {
265 Component::RootDir => normalized.push("/"),
266 Component::CurDir => {}
267 Component::ParentDir => {
268 if !normalized.as_os_str().is_empty() && normalized.as_os_str() != "/" {
269 normalized.pop();
270 }
271 }
272 Component::Normal(seg) => normalized.push(seg),
273 Component::Prefix(_) => {}
274 }
275 }
276 if normalized.as_os_str().is_empty() {
277 PathBuf::from("/")
278 } else {
279 normalized
280 }
281}
282
283impl WasiInodes {
284 pub fn new() -> Self {
285 Self {
286 protected: Arc::new(RwLock::new(WasiInodesProtected {
287 lookup: Default::default(),
288 })),
289 }
290 }
291
292 pub fn add_inode_val(&self, val: InodeVal) -> InodeGuard {
294 let val = Arc::new(val);
295 let st_ino = {
296 let guard = val.stat.read().unwrap();
297 guard.st_ino
298 };
299
300 let mut guard = self.protected.write().unwrap();
301 let ino = Inode(st_ino);
302 guard.lookup.insert(ino, Arc::downgrade(&val));
303
304 if guard.lookup.len() % 100 == 1 {
306 guard.lookup.retain(|_, v| Weak::strong_count(v) > 0);
307 }
308
309 let open_handles = Arc::new(AtomicI32::new(0));
310
311 InodeGuard {
312 ino,
313 open_handles,
314 inner: val,
315 }
316 }
317
318 pub(crate) fn stdout(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
320 Self::std_dev_get(fd_map, __WASI_STDOUT_FILENO)
321 }
322 pub(crate) fn stdout_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
324 Self::std_dev_get_mut(fd_map, __WASI_STDOUT_FILENO)
325 }
326
327 pub(crate) fn stderr(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
329 Self::std_dev_get(fd_map, __WASI_STDERR_FILENO)
330 }
331 pub(crate) fn stderr_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
333 Self::std_dev_get_mut(fd_map, __WASI_STDERR_FILENO)
334 }
335
336 #[allow(dead_code)]
339 pub(crate) fn stdin(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
340 Self::std_dev_get(fd_map, __WASI_STDIN_FILENO)
341 }
342 pub(crate) fn stdin_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
344 Self::std_dev_get_mut(fd_map, __WASI_STDIN_FILENO)
345 }
346
347 fn std_dev_get(fd_map: &RwLock<FdList>, fd: WasiFd) -> Result<InodeValFileReadGuard, FsError> {
350 if let Some(fd) = fd_map.read().unwrap().get(fd) {
351 let guard = fd.inode.read();
352 if let Kind::File {
353 handle: Some(handle),
354 ..
355 } = guard.deref()
356 {
357 Ok(InodeValFileReadGuard::new(handle))
358 } else {
359 Err(FsError::NotAFile)
361 }
362 } else {
363 Err(FsError::NoDevice)
365 }
366 }
367 fn std_dev_get_mut(
370 fd_map: &RwLock<FdList>,
371 fd: WasiFd,
372 ) -> Result<InodeValFileWriteGuard, FsError> {
373 if let Some(fd) = fd_map.read().unwrap().get(fd) {
374 let guard = fd.inode.read();
375 if let Kind::File {
376 handle: Some(handle),
377 ..
378 } = guard.deref()
379 {
380 Ok(InodeValFileWriteGuard::new(handle))
381 } else {
382 Err(FsError::NotAFile)
384 }
385 } else {
386 Err(FsError::NoDevice)
388 }
389 }
390}
391
392impl Default for WasiInodes {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398#[derive(Debug, Clone)]
399pub enum WasiFsRoot {
400 Sandbox(TmpFileSystem),
401 Overlay(
411 Arc<
412 virtual_fs::OverlayFileSystem<
413 TmpFileSystem,
414 [RelativeOrAbsolutePathHack<UnionFileSystem>; 1],
415 >,
416 >,
417 ),
418 Backing(Arc<dyn FileSystem + Send + Sync>),
419}
420
421impl FileSystem for WasiFsRoot {
422 fn readlink(&self, path: &Path) -> virtual_fs::Result<PathBuf> {
423 match self {
424 Self::Sandbox(fs) => fs.readlink(path),
425 Self::Overlay(overlay) => overlay.readlink(path),
426 Self::Backing(fs) => fs.readlink(path),
427 }
428 }
429
430 fn read_dir(&self, path: &Path) -> virtual_fs::Result<virtual_fs::ReadDir> {
431 match self {
432 Self::Sandbox(fs) => fs.read_dir(path),
433 Self::Overlay(overlay) => overlay.read_dir(path),
434 Self::Backing(fs) => fs.read_dir(path),
435 }
436 }
437
438 fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> {
439 match self {
440 Self::Sandbox(fs) => fs.create_dir(path),
441 Self::Overlay(overlay) => overlay.create_dir(path),
442 Self::Backing(fs) => fs.create_dir(path),
443 }
444 }
445
446 fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> {
447 match self {
448 Self::Sandbox(fs) => fs.remove_dir(path),
449 Self::Overlay(overlay) => overlay.remove_dir(path),
450 Self::Backing(fs) => fs.remove_dir(path),
451 }
452 }
453
454 fn rename<'a>(&'a self, from: &Path, to: &Path) -> BoxFuture<'a, virtual_fs::Result<()>> {
455 let from = from.to_owned();
456 let to = to.to_owned();
457 let this = self.clone();
458 Box::pin(async move {
459 match this {
460 Self::Sandbox(fs) => fs.rename(&from, &to).await,
461 Self::Overlay(overlay) => overlay.rename(&from, &to).await,
462 Self::Backing(fs) => fs.rename(&from, &to).await,
463 }
464 })
465 }
466
467 fn metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
468 match self {
469 Self::Sandbox(fs) => fs.metadata(path),
470 Self::Overlay(overlay) => overlay.metadata(path),
471 Self::Backing(fs) => fs.metadata(path),
472 }
473 }
474
475 fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
476 match self {
477 Self::Sandbox(fs) => fs.symlink_metadata(path),
478 Self::Overlay(overlay) => overlay.symlink_metadata(path),
479 Self::Backing(fs) => fs.symlink_metadata(path),
480 }
481 }
482
483 fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> {
484 match self {
485 Self::Sandbox(fs) => fs.remove_file(path),
486 Self::Overlay(overlay) => overlay.remove_file(path),
487 Self::Backing(fs) => fs.remove_file(path),
488 }
489 }
490
491 fn new_open_options(&self) -> OpenOptions<'_> {
492 match self {
493 Self::Sandbox(fs) => fs.new_open_options(),
494 Self::Overlay(overlay) => overlay.new_open_options(),
495 Self::Backing(fs) => fs.new_open_options(),
496 }
497 }
498
499 fn mount(
500 &self,
501 name: String,
502 path: &Path,
503 fs: Box<dyn FileSystem + Send + Sync>,
504 ) -> virtual_fs::Result<()> {
505 match self {
506 Self::Sandbox(root) => FileSystem::mount(root, name, path, fs),
507 Self::Overlay(overlay) => FileSystem::mount(overlay.primary(), name, path, fs),
508 Self::Backing(f) => f.mount(name, path, fs),
509 }
510 }
511}
512
513#[tracing::instrument(level = "trace", skip_all)]
520async fn merge_filesystems(
521 source: &dyn FileSystem,
522 destination: &dyn FileSystem,
523) -> Result<(), virtual_fs::FsError> {
524 tracing::warn!("Falling back to a recursive copy to merge filesystems");
525 let files = futures::stream::FuturesUnordered::new();
526
527 let mut to_check = VecDeque::new();
528 to_check.push_back(PathBuf::from("/"));
529
530 while let Some(path) = to_check.pop_front() {
531 let metadata = match source.metadata(&path) {
532 Ok(m) => m,
533 Err(err) => {
534 tracing::debug!(path=%path.display(), source_fs=?source, ?err, "failed to get metadata for path while merging file systems");
535 return Err(err);
536 }
537 };
538
539 if metadata.is_dir() {
540 create_dir_all(destination, &path)?;
541
542 for entry in source.read_dir(&path)? {
543 let entry = entry?;
544 to_check.push_back(entry.path);
545 }
546 } else if metadata.is_file() {
547 files.push(async move {
548 copy_reference(source, destination, &path)
549 .await
550 .map_err(virtual_fs::FsError::from)
551 });
552 } else {
553 tracing::debug!(
554 path=%path.display(),
555 ?metadata,
556 "Skipping unknown file type while merging"
557 );
558 }
559 }
560
561 files.try_collect().await
562}
563
564fn create_dir_all(fs: &dyn FileSystem, path: &Path) -> Result<(), virtual_fs::FsError> {
565 if fs.metadata(path).is_ok() {
566 return Ok(());
567 }
568
569 if let Some(parent) = path.parent() {
570 create_dir_all(fs, parent)?;
571 }
572
573 fs.create_dir(path)?;
574
575 Ok(())
576}
577
578#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
581pub struct WasiFs {
582 pub preopen_fds: RwLock<Vec<u32>>,
584 pub fd_map: RwLock<FdList>,
585 pub current_dir: Mutex<String>,
586 #[cfg_attr(feature = "enable-serde", serde(skip, default))]
587 pub root_fs: WasiFsRoot,
588 pub root_inode: InodeGuard,
589 pub has_unioned: Mutex<HashSet<PackageId>>,
590 ephemeral_symlinks: Arc<RwLock<HashMap<PathBuf, EphemeralSymlinkEntry>>>,
591
592 is_wasix: AtomicBool,
597
598 pub(crate) init_preopens: Vec<PreopenedDir>,
600 pub(crate) init_vfs_preopens: Vec<String>,
602}
603
604impl WasiFs {
605 pub fn is_wasix(&self) -> bool {
606 self.is_wasix.load(Ordering::Relaxed)
609 }
610
611 pub fn set_is_wasix(&self, is_wasix: bool) {
612 self.is_wasix.store(is_wasix, Ordering::SeqCst);
613 }
614
615 pub(crate) fn register_ephemeral_symlink(
616 &self,
617 full_path: PathBuf,
618 base_po_dir: WasiFd,
619 path_to_symlink: PathBuf,
620 relative_path: PathBuf,
621 ) {
622 let mut guard = self.ephemeral_symlinks.write().unwrap();
623 guard.insert(
624 normalize_virtual_symlink_key(&full_path),
625 EphemeralSymlinkEntry {
626 base_po_dir,
627 path_to_symlink: normalize_virtual_symlink_key(&path_to_symlink),
628 relative_path,
629 },
630 );
631 }
632
633 pub(crate) fn ephemeral_symlink_at(
634 &self,
635 full_path: &Path,
636 ) -> Option<(WasiFd, PathBuf, PathBuf)> {
637 let guard = self.ephemeral_symlinks.read().unwrap();
638 let entry = guard.get(&normalize_virtual_symlink_key(full_path))?;
639 Some((
640 entry.base_po_dir,
641 entry.path_to_symlink.clone(),
642 entry.relative_path.clone(),
643 ))
644 }
645
646 pub(crate) fn unregister_ephemeral_symlink(&self, full_path: &Path) {
647 let mut guard = self.ephemeral_symlinks.write().unwrap();
648 guard.remove(&normalize_virtual_symlink_key(full_path));
649 }
650
651 pub(crate) fn move_ephemeral_symlink(
652 &self,
653 old_full_path: &Path,
654 new_full_path: &Path,
655 base_po_dir: WasiFd,
656 path_to_symlink: PathBuf,
657 relative_path: PathBuf,
658 ) {
659 let old_key = normalize_virtual_symlink_key(old_full_path);
660 let new_key = normalize_virtual_symlink_key(new_full_path);
661
662 let mut guard = self.ephemeral_symlinks.write().unwrap();
663 guard.remove(&old_key);
664 guard.insert(
665 new_key,
666 EphemeralSymlinkEntry {
667 base_po_dir,
668 path_to_symlink: normalize_virtual_symlink_key(&path_to_symlink),
669 relative_path,
670 },
671 );
672 }
673
674 pub fn fork(&self) -> Self {
676 Self {
677 preopen_fds: RwLock::new(self.preopen_fds.read().unwrap().clone()),
678 fd_map: RwLock::new(self.fd_map.read().unwrap().clone()),
679 current_dir: Mutex::new(self.current_dir.lock().unwrap().clone()),
680 is_wasix: AtomicBool::new(self.is_wasix.load(Ordering::Acquire)),
681 root_fs: self.root_fs.clone(),
682 root_inode: self.root_inode.clone(),
683 has_unioned: Mutex::new(self.has_unioned.lock().unwrap().clone()),
684 ephemeral_symlinks: self.ephemeral_symlinks.clone(),
685 init_preopens: self.init_preopens.clone(),
686 init_vfs_preopens: self.init_vfs_preopens.clone(),
687 }
688 }
689
690 pub async fn close_cloexec_fds(&self) {
692 let to_close = {
693 if let Ok(map) = self.fd_map.read() {
694 map.iter()
695 .filter_map(|(k, v)| {
696 if v.inner.fd_flags.contains(Fdflagsext::CLOEXEC)
697 && !v.is_stdio
698 && !v.inode.is_preopened
699 {
700 tracing::trace!(fd = %k, "Closing FD due to CLOEXEC flag");
701 Some(k)
702 } else {
703 None
704 }
705 })
706 .collect::<HashSet<_>>()
707 } else {
708 HashSet::new()
709 }
710 };
711
712 let _ = tokio::join!(async {
713 for fd in &to_close {
714 self.flush(*fd).await.ok();
715 self.close_fd(*fd).ok();
716 }
717 });
718
719 if let Ok(mut map) = self.fd_map.write() {
720 for fd in &to_close {
721 map.remove(*fd);
722 }
723 }
724 }
725
726 pub async fn close_all(&self) {
728 let mut to_close = {
729 if let Ok(map) = self.fd_map.read() {
730 map.keys().collect::<HashSet<_>>()
731 } else {
732 HashSet::new()
733 }
734 };
735 to_close.insert(__WASI_STDOUT_FILENO);
736 to_close.insert(__WASI_STDERR_FILENO);
737
738 let _ = tokio::join!(async {
739 for fd in to_close {
740 self.flush(fd).await.ok();
741 self.close_fd(fd).ok();
742 }
743 });
744
745 if let Ok(mut map) = self.fd_map.write() {
746 map.clear();
747 }
748 }
749
750 pub async fn conditional_union(
753 &self,
754 binary: &BinaryPackage,
755 ) -> Result<(), virtual_fs::FsError> {
756 let Some(webc_fs) = &binary.webc_fs else {
757 return Ok(());
758 };
759
760 let needs_to_be_unioned = self.has_unioned.lock().unwrap().insert(binary.id.clone());
761 if !needs_to_be_unioned {
762 return Ok(());
763 }
764
765 match &self.root_fs {
766 WasiFsRoot::Sandbox(fs) => {
767 let fdyn: Arc<dyn FileSystem + Send + Sync> = webc_fs.clone();
769 fs.union(&fdyn);
770 Ok(())
771 }
772 WasiFsRoot::Overlay(overlay) => {
773 let union = &overlay.secondaries()[0];
774 union.0.merge(webc_fs, virtual_fs::UnionMergeMode::Skip)
775 }
776 WasiFsRoot::Backing(backing) => merge_filesystems(webc_fs, backing).await,
777 }
778 }
779
780 pub(crate) fn new_with_preopen(
782 inodes: &WasiInodes,
783 preopens: &[PreopenedDir],
784 vfs_preopens: &[String],
785 fs_backing: WasiFsRoot,
786 ) -> Result<Self, String> {
787 let mut wasi_fs = Self::new_init(fs_backing, inodes, FS_ROOT_INO)?;
788 wasi_fs.init_preopens = preopens.to_vec();
789 wasi_fs.init_vfs_preopens = vfs_preopens.to_vec();
790 wasi_fs.create_preopens(inodes, false)?;
791 Ok(wasi_fs)
792 }
793
794 pub(crate) fn relative_path_to_absolute(&self, path: String) -> String {
796 if path.starts_with('/') {
797 return path;
798 }
799
800 let current_dir = self.current_dir.lock().unwrap();
801 format!("{}/{}", current_dir.trim_end_matches('/'), path)
802 }
803
804 fn new_init(
807 fs_backing: WasiFsRoot,
808 inodes: &WasiInodes,
809 st_ino: Inode,
810 ) -> Result<Self, String> {
811 debug!("Initializing WASI filesystem");
812
813 let stat = Filestat {
814 st_filetype: Filetype::Directory,
815 st_ino: st_ino.as_u64(),
816 ..Filestat::default()
817 };
818 let root_kind = Kind::Root {
819 entries: HashMap::new(),
820 };
821 let root_inode = inodes.add_inode_val(InodeVal {
822 stat: RwLock::new(stat),
823 is_preopened: true,
824 name: RwLock::new("/".into()),
825 kind: RwLock::new(root_kind),
826 });
827
828 let wasi_fs = Self {
829 preopen_fds: RwLock::new(vec![]),
830 fd_map: RwLock::new(FdList::new()),
831 current_dir: Mutex::new("/".to_string()),
832 is_wasix: AtomicBool::new(false),
833 root_fs: fs_backing,
834 root_inode,
835 has_unioned: Mutex::new(HashSet::new()),
836 ephemeral_symlinks: Arc::new(RwLock::new(HashMap::new())),
837 init_preopens: Default::default(),
838 init_vfs_preopens: Default::default(),
839 };
840 wasi_fs.create_stdin(inodes);
841 wasi_fs.create_stdout(inodes);
842 wasi_fs.create_stderr(inodes);
843 wasi_fs.create_rootfd()?;
844
845 Ok(wasi_fs)
846 }
847
848 #[allow(dead_code)]
858 #[allow(clippy::too_many_arguments)]
859 pub unsafe fn open_dir_all(
860 &mut self,
861 inodes: &WasiInodes,
862 base: WasiFd,
863 name: String,
864 rights: Rights,
865 rights_inheriting: Rights,
866 flags: Fdflags,
867 fd_flags: Fdflagsext,
868 ) -> Result<WasiFd, FsError> {
869 let mut cur_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
872
873 let path: &Path = Path::new(&name);
874 for c in path.components() {
876 let segment_name = c.as_os_str().to_string_lossy().to_string();
877 let guard = cur_inode.read();
878 match guard.deref() {
879 Kind::Dir { entries, .. } | Kind::Root { entries } => {
880 if let Some(_entry) = entries.get(&segment_name) {
881 return Err(FsError::AlreadyExists);
883 }
884
885 let kind = Kind::Dir {
886 parent: cur_inode.downgrade(),
887 path: PathBuf::from(""),
888 entries: HashMap::new(),
889 };
890
891 drop(guard);
892 let inode = self.create_inode_with_default_stat(
893 inodes,
894 kind,
895 false,
896 segment_name.clone().into(),
897 );
898
899 {
901 let mut guard = cur_inode.write();
902 match guard.deref_mut() {
903 Kind::Dir { entries, .. } | Kind::Root { entries } => {
904 entries.insert(segment_name, inode.clone());
905 }
906 _ => unreachable!("Dir or Root became not Dir or Root"),
907 }
908 }
909 cur_inode = inode;
910 }
911 _ => return Err(FsError::BaseNotDirectory),
912 }
913 }
914
915 self.create_fd(
917 rights,
918 rights_inheriting,
919 flags,
920 fd_flags,
921 Fd::READ | Fd::WRITE,
922 cur_inode,
923 )
924 .map_err(fs_error_from_wasi_err)
925 }
926
927 #[allow(dead_code, clippy::too_many_arguments)]
932 pub fn open_file_at(
933 &mut self,
934 inodes: &WasiInodes,
935 base: WasiFd,
936 file: Box<dyn VirtualFile + Send + Sync + 'static>,
937 open_flags: u16,
938 name: String,
939 rights: Rights,
940 rights_inheriting: Rights,
941 flags: Fdflags,
942 fd_flags: Fdflagsext,
943 ) -> Result<WasiFd, FsError> {
944 let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
947
948 let guard = base_inode.read();
949 match guard.deref() {
950 Kind::Dir { entries, .. } | Kind::Root { entries } => {
951 if let Some(_entry) = entries.get(&name) {
952 return Err(FsError::AlreadyExists);
954 }
955
956 let kind = Kind::File {
957 handle: Some(Arc::new(RwLock::new(file))),
958 path: PathBuf::from(""),
959 fd: None,
960 };
961
962 drop(guard);
963 let inode = self
964 .create_inode(inodes, kind, false, name.clone())
965 .map_err(|_| FsError::IOError)?;
966
967 {
968 let mut guard = base_inode.write();
969 match guard.deref_mut() {
970 Kind::Dir { entries, .. } | Kind::Root { entries } => {
971 entries.insert(name, inode.clone());
972 }
973 _ => unreachable!("Dir or Root became not Dir or Root"),
974 }
975 }
976
977 let real_fd = self
979 .create_fd(
980 rights,
981 rights_inheriting,
982 flags,
983 fd_flags,
984 open_flags,
985 inode.clone(),
986 )
987 .map_err(fs_error_from_wasi_err)?;
988
989 {
990 let mut guard = inode.kind.write().unwrap();
991 match guard.deref_mut() {
992 Kind::File { fd, .. } => {
993 *fd = Some(real_fd);
994 }
995 _ => unreachable!("We just created a Kind::File"),
996 }
997 }
998
999 Ok(real_fd)
1000 }
1001 _ => Err(FsError::BaseNotDirectory),
1002 }
1003 }
1004
1005 #[allow(dead_code)]
1009 pub fn swap_file(
1010 &self,
1011 fd: WasiFd,
1012 mut file: Box<dyn VirtualFile + Send + Sync + 'static>,
1013 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1014 match fd {
1015 __WASI_STDIN_FILENO => {
1016 let mut target = WasiInodes::stdin_mut(&self.fd_map)?;
1017 Ok(Some(target.swap(file)))
1018 }
1019 __WASI_STDOUT_FILENO => {
1020 let mut target = WasiInodes::stdout_mut(&self.fd_map)?;
1021 Ok(Some(target.swap(file)))
1022 }
1023 __WASI_STDERR_FILENO => {
1024 let mut target = WasiInodes::stderr_mut(&self.fd_map)?;
1025 Ok(Some(target.swap(file)))
1026 }
1027 _ => {
1028 let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?;
1029 {
1030 let guard = base_inode.read();
1032 match guard.deref() {
1033 Kind::File { handle, .. } => {
1034 if let Some(handle) = handle {
1035 let mut handle = handle.write().unwrap();
1036 std::mem::swap(handle.deref_mut(), &mut file);
1037 return Ok(Some(file));
1038 }
1039 }
1040 _ => return Err(FsError::NotAFile),
1041 }
1042 }
1043 let mut guard = base_inode.write();
1045 match guard.deref_mut() {
1046 Kind::File { handle, .. } => {
1047 if let Some(handle) = handle {
1048 let mut handle = handle.write().unwrap();
1049 std::mem::swap(handle.deref_mut(), &mut file);
1050 Ok(Some(file))
1051 } else {
1052 handle.replace(Arc::new(RwLock::new(file)));
1053 Ok(None)
1054 }
1055 }
1056 _ => Err(FsError::NotAFile),
1057 }
1058 }
1059 }
1060 }
1061
1062 pub fn filestat_resync_size(&self, fd: WasiFd) -> Result<Filesize, Errno> {
1064 let inode = self.get_fd_inode(fd)?;
1065 let mut guard = inode.write();
1066 match guard.deref_mut() {
1067 Kind::File { handle, .. } => {
1068 if let Some(h) = handle {
1069 let h = h.read().unwrap();
1070 let new_size = h.size();
1071 drop(h);
1072 drop(guard);
1073
1074 inode.stat.write().unwrap().st_size = new_size;
1075 Ok(new_size as Filesize)
1076 } else {
1077 Err(Errno::Badf)
1078 }
1079 }
1080 Kind::Dir { .. } | Kind::Root { .. } => Err(Errno::Isdir),
1081 _ => Err(Errno::Inval),
1082 }
1083 }
1084
1085 pub fn set_current_dir(&self, path: &str) {
1087 let mut guard = self.current_dir.lock().unwrap();
1088 *guard = path.to_string();
1089 }
1090
1091 pub fn get_current_dir(
1093 &self,
1094 inodes: &WasiInodes,
1095 base: WasiFd,
1096 ) -> Result<(InodeGuard, String), Errno> {
1097 self.get_current_dir_inner(inodes, base, 0)
1098 }
1099
1100 pub(crate) fn get_current_dir_inner(
1101 &self,
1102 inodes: &WasiInodes,
1103 base: WasiFd,
1104 symlink_count: u32,
1105 ) -> Result<(InodeGuard, String), Errno> {
1106 let current_dir = {
1107 let guard = self.current_dir.lock().unwrap();
1108 guard.clone()
1109 };
1110 let cur_inode = self.get_fd_inode(base)?;
1111 let inode = self.get_inode_at_path_inner(
1112 inodes,
1113 cur_inode,
1114 current_dir.as_str(),
1115 symlink_count,
1116 true,
1117 )?;
1118 Ok((inode, current_dir))
1119 }
1120
1121 fn get_inode_at_path_inner(
1135 &self,
1136 inodes: &WasiInodes,
1137 mut cur_inode: InodeGuard,
1138 path_str: &str,
1139 mut symlink_count: u32,
1140 follow_symlinks: bool,
1141 ) -> Result<InodeGuard, Errno> {
1142 if symlink_count > MAX_SYMLINKS {
1143 return Err(Errno::Mlink);
1144 }
1145
1146 if path_str == "/" {
1149 let guard = cur_inode.read();
1150 if let Kind::Root { entries } = guard.deref()
1151 && let Some(root_entry) = entries.get("/")
1152 {
1153 return Ok(root_entry.clone());
1154 }
1155 }
1156
1157 let path: &Path = Path::new(path_str);
1158 let n_components = path.components().count();
1159
1160 'path_iter: for (i, component) in path.components().enumerate() {
1162 if matches!(component, Component::RootDir) {
1166 continue;
1167 }
1168
1169 let last_component = i + 1 == n_components;
1171 'symlink_resolution: while symlink_count < MAX_SYMLINKS {
1174 let processing_cur_inode = cur_inode.clone();
1175 let mut guard = processing_cur_inode.write();
1176 match guard.deref_mut() {
1177 Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"),
1178 Kind::Dir {
1179 entries,
1180 path,
1181 parent,
1182 ..
1183 } => {
1184 match component.as_os_str().to_string_lossy().borrow() {
1185 ".." => {
1186 if let Some(p) = parent.upgrade() {
1187 cur_inode = p;
1188 continue 'path_iter;
1189 } else {
1190 return Err(Errno::Access);
1191 }
1192 }
1193 "." => continue 'path_iter,
1194 _ => (),
1195 }
1196 let mut loop_for_symlink = false;
1198 if let Some(entry) =
1199 entries.get(component.as_os_str().to_string_lossy().as_ref())
1200 {
1201 cur_inode = entry.clone();
1202 } else {
1203 let file = {
1204 let mut cd = path.clone();
1205 cd.push(component);
1206 cd
1207 };
1208 let should_insert;
1211
1212 let kind = if let Some((base_po_dir, path_to_symlink, relative_path)) =
1213 self.ephemeral_symlink_at(&file)
1214 {
1215 should_insert = false;
1218 loop_for_symlink = true;
1219 symlink_count += 1;
1220 Kind::Symlink {
1221 base_po_dir,
1222 path_to_symlink,
1223 relative_path,
1224 }
1225 } else {
1226 let metadata = self
1227 .root_fs
1228 .symlink_metadata(&file)
1229 .ok()
1230 .ok_or(Errno::Noent)?;
1231 let file_type = metadata.file_type();
1232 if file_type.is_dir() {
1233 should_insert = true;
1234 Kind::Dir {
1236 parent: cur_inode.downgrade(),
1237 path: file.clone(),
1238 entries: Default::default(),
1239 }
1240 } else if file_type.is_file() {
1241 should_insert = true;
1242 Kind::File {
1244 handle: None,
1245 path: file.clone(),
1246 fd: None,
1247 }
1248 } else if file_type.is_symlink() {
1249 should_insert = false;
1250 let link_value =
1251 self.root_fs.readlink(&file).ok().ok_or(Errno::Noent)?;
1252 debug!("attempting to decompose path {:?}", link_value);
1253 let (pre_open_dir_fd, path_to_symlink) =
1254 self.path_into_pre_open_and_relative_path(&file)?;
1255 loop_for_symlink = true;
1256 symlink_count += 1;
1257 Kind::Symlink {
1258 base_po_dir: pre_open_dir_fd,
1259 path_to_symlink: path_to_symlink.to_owned(),
1260 relative_path: link_value,
1261 }
1262 } else {
1263 #[cfg(unix)]
1264 {
1265 let file_type: Filetype = if file_type.is_char_device() {
1267 Filetype::CharacterDevice
1268 } else if file_type.is_block_device() {
1269 Filetype::BlockDevice
1270 } else if file_type.is_fifo() {
1271 Filetype::Unknown
1273 } else if file_type.is_socket() {
1274 Filetype::SocketStream
1277 } else {
1278 unimplemented!(
1279 "state::get_inode_at_path unknown file type: not file, directory, symlink, char device, block device, fifo, or socket"
1280 );
1281 };
1282
1283 let kind = Kind::File {
1284 handle: None,
1285 path: file.clone(),
1286 fd: None,
1287 };
1288 drop(guard);
1289 let new_inode = self.create_inode_with_stat(
1290 inodes,
1291 kind,
1292 false,
1293 file.to_string_lossy().to_string().into(),
1294 Filestat {
1295 st_filetype: file_type,
1296 st_ino: Inode::from_path(path_str).as_u64(),
1297 st_size: metadata.len(),
1298 st_ctim: metadata.created(),
1299 st_mtim: metadata.modified(),
1300 st_atim: metadata.accessed(),
1301 ..Filestat::default()
1302 },
1303 );
1304
1305 let mut guard = cur_inode.write();
1306 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1307 entries.insert(
1308 component.as_os_str().to_string_lossy().to_string(),
1309 new_inode.clone(),
1310 );
1311 } else {
1312 unreachable!(
1313 "Attempted to insert special device into non-directory"
1314 );
1315 }
1316 return Ok(new_inode);
1318 }
1319 #[cfg(not(unix))]
1320 unimplemented!(
1321 "state::get_inode_at_path unknown file type: not file, directory, or symlink"
1322 );
1323 }
1324 };
1325 drop(guard);
1326
1327 let new_inode = self.create_inode(
1328 inodes,
1329 kind,
1330 false,
1331 file.to_string_lossy().to_string(),
1332 )?;
1333 if should_insert {
1334 let mut guard = processing_cur_inode.write();
1335 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1336 entries.insert(
1337 component.as_os_str().to_string_lossy().to_string(),
1338 new_inode.clone(),
1339 );
1340 }
1341 }
1342 cur_inode = new_inode;
1343
1344 if loop_for_symlink && follow_symlinks {
1345 debug!("Following symlink to {:?}", cur_inode);
1346 continue 'symlink_resolution;
1347 }
1348 }
1349 }
1350 Kind::Root { entries } => {
1351 match component {
1352 Component::ParentDir => continue 'path_iter,
1354 Component::CurDir => continue 'path_iter,
1356 _ => {}
1357 }
1358
1359 let component = component.as_os_str().to_string_lossy();
1360
1361 if let Some(entry) = entries.get(component.as_ref()) {
1362 cur_inode = entry.clone();
1363 } else if let Some(root) = entries.get(&"/".to_string()) {
1364 cur_inode = root.clone();
1365 continue 'symlink_resolution;
1366 } else {
1367 return Err(Errno::Notcapable);
1369 }
1370 }
1371 Kind::File { .. }
1372 | Kind::Socket { .. }
1373 | Kind::PipeRx { .. }
1374 | Kind::PipeTx { .. }
1375 | Kind::DuplexPipe { .. }
1376 | Kind::EventNotifications { .. }
1377 | Kind::Epoll { .. } => {
1378 return Err(Errno::Notdir);
1379 }
1380 Kind::Symlink {
1381 base_po_dir,
1382 path_to_symlink,
1383 relative_path,
1384 } => {
1385 let (new_base_inode, new_path) = if relative_path.is_absolute() {
1386 (
1389 self.get_fd_inode(VIRTUAL_ROOT_FD)?,
1390 relative_path.to_string_lossy().to_string(),
1391 )
1392 } else {
1393 let new_base_dir = *base_po_dir;
1394 let new_base_inode = self.get_fd_inode(new_base_dir)?;
1395 let new_path = {
1397 let mut base = path_to_symlink.clone();
1401 base.pop();
1404 base.push(relative_path);
1405 base.to_string_lossy().to_string()
1406 };
1407 (new_base_inode, new_path)
1408 };
1409 debug!("Following symlink recursively");
1410 drop(guard);
1411 let symlink_inode = self.get_inode_at_path_inner(
1412 inodes,
1413 new_base_inode,
1414 &new_path,
1415 symlink_count + 1,
1416 follow_symlinks,
1417 )?;
1418 cur_inode = symlink_inode;
1419 let guard = cur_inode.read();
1422 if let Kind::File { .. } = guard.deref() {
1423 if last_component {
1425 break 'symlink_resolution;
1426 }
1427 }
1428 continue 'symlink_resolution;
1429 }
1430 }
1431 break 'symlink_resolution;
1432 }
1433 }
1434
1435 Ok(cur_inode)
1436 }
1437
1438 fn path_into_pre_open_and_relative_path<'path>(
1448 &self,
1449 path: &'path Path,
1450 ) -> Result<(WasiFd, &'path Path), Errno> {
1451 enum BaseFdAndRelPath<'a> {
1452 None,
1453 BestMatch {
1454 fd: WasiFd,
1455 rel_path: &'a Path,
1456 max_seen: usize,
1457 },
1458 }
1459
1460 impl BaseFdAndRelPath<'_> {
1461 const fn max_seen(&self) -> usize {
1462 match self {
1463 Self::None => 0,
1464 Self::BestMatch { max_seen, .. } => *max_seen,
1465 }
1466 }
1467 }
1468 let mut res = BaseFdAndRelPath::None;
1469 let preopen_fds = self.preopen_fds.read().unwrap();
1471 for po_fd in preopen_fds.deref() {
1472 let po_inode = self
1473 .fd_map
1474 .read()
1475 .unwrap()
1476 .get(*po_fd)
1477 .unwrap()
1478 .inode
1479 .clone();
1480 let guard = po_inode.read();
1481 let po_path = match guard.deref() {
1482 Kind::Dir { path, .. } => &**path,
1483 Kind::Root { .. } => Path::new("/"),
1484 _ => unreachable!("Preopened FD that's not a directory or the root"),
1485 };
1486 if let Ok(stripped_path) = path.strip_prefix(po_path) {
1488 let new_prefix_len = po_path.as_os_str().len();
1490 if new_prefix_len >= res.max_seen() {
1493 res = BaseFdAndRelPath::BestMatch {
1494 fd: *po_fd,
1495 rel_path: stripped_path,
1496 max_seen: new_prefix_len,
1497 };
1498 }
1499 }
1500 }
1501 match res {
1502 BaseFdAndRelPath::None => Err(Errno::Inval),
1504 BaseFdAndRelPath::BestMatch { fd, rel_path, .. } => Ok((fd, rel_path)),
1505 }
1506 }
1507
1508 pub(crate) fn path_into_pre_open_and_relative_path_owned(
1509 &self,
1510 path: &Path,
1511 ) -> Result<(WasiFd, PathBuf), Errno> {
1512 let (fd, rel_path) = self.path_into_pre_open_and_relative_path(path)?;
1513 Ok((fd, rel_path.to_owned()))
1514 }
1515
1516 pub(crate) fn get_inode_at_path(
1523 &self,
1524 inodes: &WasiInodes,
1525 base: WasiFd,
1526 path: &str,
1527 follow_symlinks: bool,
1528 ) -> Result<InodeGuard, Errno> {
1529 let base_inode = self.get_fd_inode(base)?;
1530 self.get_inode_at_path_inner(inodes, base_inode, path, 0, follow_symlinks)
1531 }
1532
1533 pub(crate) fn get_parent_inode_at_path(
1536 &self,
1537 inodes: &WasiInodes,
1538 base: WasiFd,
1539 path: &Path,
1540 follow_symlinks: bool,
1541 ) -> Result<(InodeGuard, String), Errno> {
1542 let mut parent_dir = std::path::PathBuf::new();
1543 let mut components = path.components().rev();
1544 let new_entity_name = components
1545 .next()
1546 .ok_or(Errno::Inval)?
1547 .as_os_str()
1548 .to_string_lossy()
1549 .to_string();
1550 for comp in components.rev() {
1551 parent_dir.push(comp);
1552 }
1553 self.get_inode_at_path(inodes, base, &parent_dir.to_string_lossy(), follow_symlinks)
1554 .map(|v| (v, new_entity_name))
1555 }
1556
1557 pub fn get_fd(&self, fd: WasiFd) -> Result<Fd, Errno> {
1558 let ret = self
1559 .fd_map
1560 .read()
1561 .unwrap()
1562 .get(fd)
1563 .ok_or(Errno::Badf)
1564 .cloned();
1565
1566 if ret.is_err() && fd == VIRTUAL_ROOT_FD {
1567 Ok(Fd {
1568 inner: FdInner {
1569 rights: ALL_RIGHTS,
1570 rights_inheriting: ALL_RIGHTS,
1571 flags: Fdflags::empty(),
1572 offset: Arc::new(AtomicU64::new(0)),
1573 fd_flags: Fdflagsext::empty(),
1574 },
1575 open_flags: 0,
1576 inode: self.root_inode.clone(),
1577 is_stdio: false,
1578 })
1579 } else {
1580 ret
1581 }
1582 }
1583
1584 pub fn get_fd_inode(&self, fd: WasiFd) -> Result<InodeGuard, Errno> {
1585 if fd == VIRTUAL_ROOT_FD {
1587 return Ok(self.root_inode.clone());
1588 }
1589 self.fd_map
1590 .read()
1591 .unwrap()
1592 .get(fd)
1593 .ok_or(Errno::Badf)
1594 .map(|a| a.inode.clone())
1595 }
1596
1597 pub fn filestat_fd(&self, fd: WasiFd) -> Result<Filestat, Errno> {
1598 let inode = self.get_fd_inode(fd)?;
1599 let guard = inode.stat.read().unwrap();
1600 Ok(*guard.deref())
1601 }
1602
1603 pub fn fdstat(&self, fd: WasiFd) -> Result<Fdstat, Errno> {
1604 match fd {
1605 __WASI_STDIN_FILENO => {
1606 return Ok(Fdstat {
1607 fs_filetype: Filetype::CharacterDevice,
1608 fs_flags: Fdflags::empty(),
1609 fs_rights_base: STDIN_DEFAULT_RIGHTS,
1610 fs_rights_inheriting: Rights::empty(),
1611 });
1612 }
1613 __WASI_STDOUT_FILENO => {
1614 return Ok(Fdstat {
1615 fs_filetype: Filetype::CharacterDevice,
1616 fs_flags: Fdflags::APPEND,
1617 fs_rights_base: STDOUT_DEFAULT_RIGHTS,
1618 fs_rights_inheriting: Rights::empty(),
1619 });
1620 }
1621 __WASI_STDERR_FILENO => {
1622 return Ok(Fdstat {
1623 fs_filetype: Filetype::CharacterDevice,
1624 fs_flags: Fdflags::APPEND,
1625 fs_rights_base: STDERR_DEFAULT_RIGHTS,
1626 fs_rights_inheriting: Rights::empty(),
1627 });
1628 }
1629 VIRTUAL_ROOT_FD => {
1630 return Ok(Fdstat {
1631 fs_filetype: Filetype::Directory,
1632 fs_flags: Fdflags::empty(),
1633 fs_rights_base: ALL_RIGHTS,
1635 fs_rights_inheriting: ALL_RIGHTS,
1636 });
1637 }
1638 _ => (),
1639 }
1640 let fd = self.get_fd(fd)?;
1641
1642 let guard = fd.inode.read();
1643 let deref = guard.deref();
1644 Ok(Fdstat {
1645 fs_filetype: match deref {
1646 Kind::File { .. } => Filetype::RegularFile,
1647 Kind::Dir { .. } => Filetype::Directory,
1648 Kind::Symlink { .. } => Filetype::SymbolicLink,
1649 Kind::Socket { socket } => match &socket.inner.protected.read().unwrap().kind {
1650 InodeSocketKind::TcpStream { .. } => Filetype::SocketStream,
1651 InodeSocketKind::Raw { .. } => Filetype::SocketRaw,
1652 InodeSocketKind::PreSocket { props, .. } => match props.ty {
1653 Socktype::Stream => Filetype::SocketStream,
1654 Socktype::Dgram => Filetype::SocketDgram,
1655 Socktype::Raw => Filetype::SocketRaw,
1656 Socktype::Seqpacket => Filetype::SocketSeqpacket,
1657 _ => Filetype::Unknown,
1658 },
1659 _ => Filetype::Unknown,
1660 },
1661 _ => Filetype::Unknown,
1662 },
1663 fs_flags: fd.inner.flags,
1664 fs_rights_base: fd.inner.rights,
1665 fs_rights_inheriting: fd.inner.rights_inheriting, })
1667 }
1668
1669 pub fn prestat_fd(&self, fd: WasiFd) -> Result<Prestat, Errno> {
1670 let inode = self.get_fd_inode(fd)?;
1671 if inode.is_preopened {
1674 Ok(self.prestat_fd_inner(inode.deref()))
1675 } else {
1676 Err(Errno::Badf)
1677 }
1678 }
1679
1680 pub(crate) fn prestat_fd_inner(&self, inode_val: &InodeVal) -> Prestat {
1681 Prestat {
1682 pr_type: Preopentype::Dir,
1683 u: PrestatEnum::Dir {
1684 pr_name_len: inode_val.name.read().unwrap().len() as u32,
1686 }
1687 .untagged(),
1688 }
1689 }
1690
1691 #[allow(clippy::await_holding_lock)]
1692 pub async fn flush(&self, fd: WasiFd) -> Result<(), Errno> {
1693 match fd {
1694 __WASI_STDIN_FILENO => (),
1695 __WASI_STDOUT_FILENO => {
1696 let mut file =
1697 WasiInodes::stdout_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1698 file.flush().await.map_err(map_io_err)?
1699 }
1700 __WASI_STDERR_FILENO => {
1701 let mut file =
1702 WasiInodes::stderr_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1703 file.flush().await.map_err(map_io_err)?
1704 }
1705 _ => {
1706 let fd = self.get_fd(fd)?;
1707 if !fd.inner.rights.contains(Rights::FD_DATASYNC) {
1708 return Err(Errno::Access);
1709 }
1710
1711 let file = {
1712 let guard = fd.inode.read();
1713 match guard.deref() {
1714 Kind::File {
1715 handle: Some(file), ..
1716 } => file.clone(),
1717 Kind::Dir { .. } => return Err(Errno::Isdir),
1719 Kind::Buffer { .. } => return Ok(()),
1720 _ => return Err(Errno::Io),
1721 }
1722 };
1723 drop(fd);
1724
1725 struct FlushPoller {
1726 file: Arc<RwLock<Box<dyn VirtualFile + Send + Sync>>>,
1727 }
1728 impl Future for FlushPoller {
1729 type Output = Result<(), Errno>;
1730 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1731 let mut file = self.file.write().unwrap();
1732 Pin::new(file.as_mut())
1733 .poll_flush(cx)
1734 .map_err(|_| Errno::Io)
1735 }
1736 }
1737 FlushPoller { file }.await?;
1738 }
1739 }
1740 Ok(())
1741 }
1742
1743 pub(crate) fn create_inode(
1745 &self,
1746 inodes: &WasiInodes,
1747 kind: Kind,
1748 is_preopened: bool,
1749 name: String,
1750 ) -> Result<InodeGuard, Errno> {
1751 let stat = self.get_stat_for_kind(&kind)?;
1752 Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name.into(), stat))
1753 }
1754
1755 pub(crate) fn create_inode_with_default_stat(
1757 &self,
1758 inodes: &WasiInodes,
1759 kind: Kind,
1760 is_preopened: bool,
1761 name: Cow<'static, str>,
1762 ) -> InodeGuard {
1763 let stat = Filestat::default();
1764 self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)
1765 }
1766
1767 pub(crate) fn create_inode_with_stat(
1769 &self,
1770 inodes: &WasiInodes,
1771 kind: Kind,
1772 is_preopened: bool,
1773 name: Cow<'static, str>,
1774 mut stat: Filestat,
1775 ) -> InodeGuard {
1776 match &kind {
1777 Kind::File {
1778 handle: Some(handle),
1779 ..
1780 } => {
1781 let guard = handle.read().unwrap();
1782 stat.st_size = guard.size();
1783 }
1784 Kind::Buffer { buffer } => {
1785 stat.st_size = buffer.len() as u64;
1786 }
1787 _ => {}
1788 }
1789
1790 let inode_key: Cow<'_, str> = match &kind {
1791 Kind::File { path, .. } | Kind::Dir { path, .. } => {
1792 let path_str = path.to_string_lossy();
1793 if path_str.is_empty() {
1794 Cow::Borrowed(name.as_ref())
1795 } else {
1796 path_str
1797 }
1798 }
1799 Kind::Symlink {
1800 base_po_dir,
1801 path_to_symlink,
1802 ..
1803 } => {
1804 let path_str = path_to_symlink.to_string_lossy();
1805 if path_str.is_empty() {
1806 Cow::Owned(format!("{base_po_dir}:{}", name.as_ref()))
1807 } else {
1808 Cow::Owned(format!("{base_po_dir}:{path_str}"))
1809 }
1810 }
1811 _ => Cow::Borrowed(name.as_ref()),
1812 };
1813
1814 let st_ino = Inode::from_path(&inode_key);
1815 stat.st_ino = st_ino.as_u64();
1816
1817 inodes.add_inode_val(InodeVal {
1818 stat: RwLock::new(stat),
1819 is_preopened,
1820 name: RwLock::new(name),
1821 kind: RwLock::new(kind),
1822 })
1823 }
1824
1825 pub fn create_fd(
1826 &self,
1827 rights: Rights,
1828 rights_inheriting: Rights,
1829 fs_flags: Fdflags,
1830 fd_flags: Fdflagsext,
1831 open_flags: u16,
1832 inode: InodeGuard,
1833 ) -> Result<WasiFd, Errno> {
1834 self.create_fd_ext(
1835 rights,
1836 rights_inheriting,
1837 fs_flags,
1838 fd_flags,
1839 open_flags,
1840 inode,
1841 None,
1842 false,
1843 )
1844 }
1845
1846 #[allow(clippy::too_many_arguments)]
1847 pub fn with_fd(
1848 &self,
1849 rights: Rights,
1850 rights_inheriting: Rights,
1851 fs_flags: Fdflags,
1852 fd_flags: Fdflagsext,
1853 open_flags: u16,
1854 inode: InodeGuard,
1855 idx: WasiFd,
1856 ) -> Result<(), Errno> {
1857 self.create_fd_ext(
1858 rights,
1859 rights_inheriting,
1860 fs_flags,
1861 fd_flags,
1862 open_flags,
1863 inode,
1864 Some(idx),
1865 true,
1866 )?;
1867 Ok(())
1868 }
1869
1870 #[allow(clippy::too_many_arguments)]
1871 pub fn create_fd_ext(
1872 &self,
1873 rights: Rights,
1874 rights_inheriting: Rights,
1875 fs_flags: Fdflags,
1876 fd_flags: Fdflagsext,
1877 open_flags: u16,
1878 inode: InodeGuard,
1879 idx: Option<WasiFd>,
1880 exclusive: bool,
1881 ) -> Result<WasiFd, Errno> {
1882 let is_stdio = matches!(
1883 idx,
1884 Some(__WASI_STDIN_FILENO) | Some(__WASI_STDOUT_FILENO) | Some(__WASI_STDERR_FILENO)
1885 );
1886 let fd = Fd {
1887 inner: FdInner {
1888 rights,
1889 rights_inheriting,
1890 flags: fs_flags,
1891 offset: Arc::new(AtomicU64::new(0)),
1892 fd_flags,
1893 },
1894 open_flags,
1895 inode,
1896 is_stdio,
1897 };
1898
1899 let mut guard = self.fd_map.write().unwrap();
1900
1901 match idx {
1902 Some(idx) => {
1903 if guard.insert(exclusive, idx, fd) {
1904 Ok(idx)
1905 } else {
1906 Err(Errno::Exist)
1907 }
1908 }
1909 None => Ok(guard.insert_first_free(fd)),
1910 }
1911 }
1912
1913 pub fn clone_fd(&self, fd: WasiFd) -> Result<WasiFd, Errno> {
1914 self.clone_fd_ext(fd, 0, None)
1915 }
1916
1917 pub fn clone_fd_ext(
1918 &self,
1919 fd: WasiFd,
1920 min_result_fd: WasiFd,
1921 cloexec: Option<bool>,
1922 ) -> Result<WasiFd, Errno> {
1923 let fd = self.get_fd(fd)?;
1924 Ok(self.fd_map.write().unwrap().insert_first_free_after(
1925 Fd {
1926 inner: FdInner {
1927 rights: fd.inner.rights,
1928 rights_inheriting: fd.inner.rights_inheriting,
1929 flags: fd.inner.flags,
1930 offset: fd.inner.offset.clone(),
1931 fd_flags: match cloexec {
1932 None => fd.inner.fd_flags,
1933 Some(cloexec) => {
1934 let mut f = fd.inner.fd_flags;
1935 f.set(Fdflagsext::CLOEXEC, cloexec);
1936 f
1937 }
1938 },
1939 },
1940 open_flags: fd.open_flags,
1941 inode: fd.inode,
1942 is_stdio: fd.is_stdio,
1943 },
1944 min_result_fd,
1945 ))
1946 }
1947
1948 pub unsafe fn remove_inode(&self, inodes: &WasiInodes, ino: Inode) -> Option<Arc<InodeVal>> {
1957 let mut guard = inodes.protected.write().unwrap();
1958 guard.lookup.remove(&ino).and_then(|a| Weak::upgrade(&a))
1959 }
1960
1961 pub(crate) fn create_stdout(&self, inodes: &WasiInodes) {
1962 self.create_std_dev_inner(
1963 inodes,
1964 Box::<Stdout>::default(),
1965 "stdout",
1966 __WASI_STDOUT_FILENO,
1967 STDOUT_DEFAULT_RIGHTS,
1968 Fdflags::APPEND,
1969 FS_STDOUT_INO,
1970 );
1971 }
1972
1973 pub(crate) fn create_stdin(&self, inodes: &WasiInodes) {
1974 self.create_std_dev_inner(
1975 inodes,
1976 Box::<Stdin>::default(),
1977 "stdin",
1978 __WASI_STDIN_FILENO,
1979 STDIN_DEFAULT_RIGHTS,
1980 Fdflags::empty(),
1981 FS_STDIN_INO,
1982 );
1983 }
1984
1985 pub(crate) fn create_stderr(&self, inodes: &WasiInodes) {
1986 self.create_std_dev_inner(
1987 inodes,
1988 Box::<Stderr>::default(),
1989 "stderr",
1990 __WASI_STDERR_FILENO,
1991 STDERR_DEFAULT_RIGHTS,
1992 Fdflags::APPEND,
1993 FS_STDERR_INO,
1994 );
1995 }
1996
1997 pub(crate) fn create_rootfd(&self) -> Result<(), String> {
1998 let all_rights = ALL_RIGHTS;
2000 let root_rights = all_rights
2003 ;
2019 let fd = self
2020 .create_fd(
2021 root_rights,
2022 root_rights,
2023 Fdflags::empty(),
2024 Fdflagsext::empty(),
2025 Fd::READ,
2026 self.root_inode.clone(),
2027 )
2028 .map_err(|e| format!("Could not create root fd: {e}"))?;
2029 self.preopen_fds.write().unwrap().push(fd);
2030 Ok(())
2031 }
2032
2033 pub(crate) fn create_preopens(
2034 &self,
2035 inodes: &WasiInodes,
2036 ignore_duplicates: bool,
2037 ) -> Result<(), String> {
2038 for preopen_name in self.init_vfs_preopens.iter() {
2039 let kind = Kind::Dir {
2040 parent: self.root_inode.downgrade(),
2041 path: PathBuf::from(preopen_name),
2042 entries: Default::default(),
2043 };
2044 let rights = Rights::FD_ADVISE
2045 | Rights::FD_TELL
2046 | Rights::FD_SEEK
2047 | Rights::FD_READ
2048 | Rights::PATH_OPEN
2049 | Rights::FD_READDIR
2050 | Rights::PATH_READLINK
2051 | Rights::PATH_FILESTAT_GET
2052 | Rights::FD_FILESTAT_GET
2053 | Rights::PATH_LINK_SOURCE
2054 | Rights::PATH_RENAME_SOURCE
2055 | Rights::POLL_FD_READWRITE
2056 | Rights::SOCK_SHUTDOWN;
2057 let inode = self
2058 .create_inode(inodes, kind, true, preopen_name.clone())
2059 .map_err(|e| {
2060 format!(
2061 "Failed to create inode for preopened dir (name `{preopen_name}`): WASI error code: {e}",
2062 )
2063 })?;
2064 let fd_flags = Fd::READ;
2065 let fd = self
2066 .create_fd(
2067 rights,
2068 rights,
2069 Fdflags::empty(),
2070 Fdflagsext::empty(),
2071 fd_flags,
2072 inode.clone(),
2073 )
2074 .map_err(|e| format!("Could not open fd for file {preopen_name:?}: {e}"))?;
2075 {
2076 let mut guard = self.root_inode.write();
2077 if let Kind::Root { entries } = guard.deref_mut() {
2078 let existing_entry = entries.insert(preopen_name.clone(), inode);
2079 if existing_entry.is_some() && !ignore_duplicates {
2080 return Err(format!("Found duplicate entry for alias `{preopen_name}`"));
2081 }
2082 }
2083 }
2084 self.preopen_fds.write().unwrap().push(fd);
2085 }
2086
2087 for PreopenedDir {
2088 path,
2089 alias,
2090 read,
2091 write,
2092 create,
2093 } in self.init_preopens.iter()
2094 {
2095 debug!(
2096 "Attempting to preopen {} with alias {:?}",
2097 &path.to_string_lossy(),
2098 &alias
2099 );
2100 let cur_dir_metadata = self
2101 .root_fs
2102 .metadata(path)
2103 .map_err(|e| format!("Could not get metadata for file {path:?}: {e}"))?;
2104
2105 let kind = if cur_dir_metadata.is_dir() {
2106 Kind::Dir {
2107 parent: self.root_inode.downgrade(),
2108 path: path.clone(),
2109 entries: Default::default(),
2110 }
2111 } else {
2112 return Err(format!(
2113 "WASI only supports pre-opened directories right now; found \"{}\"",
2114 &path.to_string_lossy()
2115 ));
2116 };
2117
2118 let rights = {
2119 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
2121 if *read {
2122 rights |= Rights::FD_READ
2123 | Rights::PATH_OPEN
2124 | Rights::FD_READDIR
2125 | Rights::PATH_READLINK
2126 | Rights::PATH_FILESTAT_GET
2127 | Rights::FD_FILESTAT_GET
2128 | Rights::PATH_LINK_SOURCE
2129 | Rights::PATH_RENAME_SOURCE
2130 | Rights::POLL_FD_READWRITE
2131 | Rights::SOCK_SHUTDOWN;
2132 }
2133 if *write {
2134 rights |= Rights::FD_DATASYNC
2135 | Rights::FD_FDSTAT_SET_FLAGS
2136 | Rights::FD_WRITE
2137 | Rights::FD_SYNC
2138 | Rights::FD_ALLOCATE
2139 | Rights::PATH_OPEN
2140 | Rights::PATH_RENAME_TARGET
2141 | Rights::PATH_FILESTAT_SET_SIZE
2142 | Rights::PATH_FILESTAT_SET_TIMES
2143 | Rights::FD_FILESTAT_SET_SIZE
2144 | Rights::FD_FILESTAT_SET_TIMES
2145 | Rights::PATH_REMOVE_DIRECTORY
2146 | Rights::PATH_UNLINK_FILE
2147 | Rights::POLL_FD_READWRITE
2148 | Rights::SOCK_SHUTDOWN;
2149 }
2150 if *create {
2151 rights |= Rights::PATH_CREATE_DIRECTORY
2152 | Rights::PATH_CREATE_FILE
2153 | Rights::PATH_LINK_TARGET
2154 | Rights::PATH_OPEN
2155 | Rights::PATH_RENAME_TARGET
2156 | Rights::PATH_SYMLINK;
2157 }
2158
2159 rights
2160 };
2161 let inode = if let Some(alias) = &alias {
2162 self.create_inode(inodes, kind, true, alias.clone())
2163 } else {
2164 self.create_inode(inodes, kind, true, path.to_string_lossy().into_owned())
2165 }
2166 .map_err(|e| {
2167 format!("Failed to create inode for preopened dir: WASI error code: {e}")
2168 })?;
2169 let fd_flags = {
2170 let mut fd_flags = 0;
2171 if *read {
2172 fd_flags |= Fd::READ;
2173 }
2174 if *write {
2175 fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE;
2177 }
2178 if *create {
2179 fd_flags |= Fd::CREATE;
2180 }
2181 fd_flags
2182 };
2183 let fd = self
2184 .create_fd(
2185 rights,
2186 rights,
2187 Fdflags::empty(),
2188 Fdflagsext::empty(),
2189 fd_flags,
2190 inode.clone(),
2191 )
2192 .map_err(|e| format!("Could not open fd for file {path:?}: {e}"))?;
2193 {
2194 let mut guard = self.root_inode.write();
2195 if let Kind::Root { entries } = guard.deref_mut() {
2196 let key = if let Some(alias) = &alias {
2197 alias.clone()
2198 } else {
2199 path.to_string_lossy().into_owned()
2200 };
2201 let existing_entry = entries.insert(key.clone(), inode);
2202 if existing_entry.is_some() && !ignore_duplicates {
2203 return Err(format!("Found duplicate entry for alias `{key}`"));
2204 }
2205 }
2206 }
2207 self.preopen_fds.write().unwrap().push(fd);
2208 }
2209
2210 Ok(())
2211 }
2212
2213 #[allow(clippy::too_many_arguments)]
2214 pub(crate) fn create_std_dev_inner(
2215 &self,
2216 inodes: &WasiInodes,
2217 handle: Box<dyn VirtualFile + Send + Sync + 'static>,
2218 name: &'static str,
2219 raw_fd: WasiFd,
2220 rights: Rights,
2221 fd_flags: Fdflags,
2222 st_ino: Inode,
2223 ) {
2224 let inode = {
2225 let stat = Filestat {
2226 st_filetype: Filetype::CharacterDevice,
2227 st_ino: st_ino.as_u64(),
2228 ..Filestat::default()
2229 };
2230 let kind = Kind::File {
2231 fd: Some(raw_fd),
2232 handle: Some(Arc::new(RwLock::new(handle))),
2233 path: "".into(),
2234 };
2235 inodes.add_inode_val(InodeVal {
2236 stat: RwLock::new(stat),
2237 is_preopened: true,
2238 name: RwLock::new(name.to_string().into()),
2239 kind: RwLock::new(kind),
2240 })
2241 };
2242 self.fd_map.write().unwrap().insert(
2243 false,
2244 raw_fd,
2245 Fd {
2246 inner: FdInner {
2247 rights,
2248 rights_inheriting: Rights::empty(),
2249 flags: fd_flags,
2250 offset: Arc::new(AtomicU64::new(0)),
2251 fd_flags: Fdflagsext::empty(),
2252 },
2253 open_flags: 0,
2255 inode,
2256 is_stdio: true,
2257 },
2258 );
2259 }
2260
2261 pub fn get_stat_for_kind(&self, kind: &Kind) -> Result<Filestat, Errno> {
2262 let md = match kind {
2263 Kind::File { handle, path, .. } => match handle {
2264 Some(wf) => {
2265 let wf = wf.read().unwrap();
2266 return Ok(Filestat {
2267 st_filetype: Filetype::RegularFile,
2268 st_ino: Inode::from_path(path.to_string_lossy().as_ref()).as_u64(),
2269 st_size: wf.size(),
2270 st_atim: wf.last_accessed(),
2271 st_mtim: wf.last_modified(),
2272 st_ctim: wf.created_time(),
2273
2274 ..Filestat::default()
2275 });
2276 }
2277 None => self
2278 .root_fs
2279 .metadata(path)
2280 .map_err(fs_error_into_wasi_err)?,
2281 },
2282 Kind::Dir { path, .. } => self
2283 .root_fs
2284 .metadata(path)
2285 .map_err(fs_error_into_wasi_err)?,
2286 Kind::Symlink {
2287 base_po_dir,
2288 path_to_symlink,
2289 ..
2290 } => {
2291 let guard = self.fd_map.read().unwrap();
2292 let base_po_inode = &guard.get(*base_po_dir).unwrap().inode;
2293 let guard = base_po_inode.read();
2294 match guard.deref() {
2295 Kind::Root { .. } => self
2296 .root_fs
2297 .symlink_metadata(path_to_symlink)
2298 .map_err(fs_error_into_wasi_err)?,
2299 Kind::Dir { path, .. } => {
2300 let mut real_path = path.clone();
2301 real_path.push(path_to_symlink);
2308 self.root_fs
2309 .symlink_metadata(&real_path)
2310 .map_err(fs_error_into_wasi_err)?
2311 }
2312 _ => unreachable!(
2314 "Symlink pointing to something that's not a directory as its base preopened directory"
2315 ),
2316 }
2317 }
2318 _ => return Err(Errno::Io),
2319 };
2320 Ok(Filestat {
2321 st_filetype: virtual_file_type_to_wasi_file_type(md.file_type()),
2322 st_size: md.len(),
2323 st_atim: md.accessed(),
2324 st_mtim: md.modified(),
2325 st_ctim: md.created(),
2326 ..Filestat::default()
2327 })
2328 }
2329
2330 pub(crate) fn close_fd(&self, fd: WasiFd) -> Result<(), Errno> {
2332 let mut fd_map = self.fd_map.write().unwrap();
2333
2334 let pfd = fd_map.remove(fd).ok_or(Errno::Badf);
2335 match pfd {
2336 Ok(fd_ref) => {
2337 let inode = fd_ref.inode.ino().as_u64();
2338 let ref_cnt = fd_ref.inode.ref_cnt();
2339 if ref_cnt == 1 {
2340 trace!(%fd, %inode, %ref_cnt, "closing file descriptor");
2341 } else {
2342 trace!(%fd, %inode, %ref_cnt, "weakening file descriptor");
2343 }
2344 }
2345 Err(err) => {
2346 trace!(%fd, "closing file descriptor failed - {}", err);
2347 }
2348 }
2349 Ok(())
2350 }
2351}
2352
2353impl std::fmt::Debug for WasiFs {
2354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2355 if let Ok(guard) = self.current_dir.try_lock() {
2356 write!(f, "current_dir={} ", guard.as_str())?;
2357 } else {
2358 write!(f, "current_dir=(locked) ")?;
2359 }
2360 if let Ok(guard) = self.fd_map.read() {
2361 write!(
2362 f,
2363 "next_fd={} max_fd={:?} ",
2364 guard.next_free_fd(),
2365 guard.last_fd()
2366 )?;
2367 } else {
2368 write!(f, "next_fd=(locked) max_fd=(locked) ")?;
2369 }
2370 write!(f, "{:?}", self.root_fs)
2371 }
2372}
2373
2374pub fn default_fs_backing() -> Arc<dyn virtual_fs::FileSystem + Send + Sync> {
2376 cfg_if::cfg_if! {
2377 if #[cfg(feature = "host-fs")] {
2378 Arc::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
2379 } else if #[cfg(not(feature = "host-fs"))] {
2380 Arc::<virtual_fs::mem_fs::FileSystem>::default()
2381 } else {
2382 Arc::<FallbackFileSystem>::default()
2383 }
2384 }
2385}
2386
2387#[derive(Debug, Default)]
2388pub struct FallbackFileSystem;
2389
2390impl FallbackFileSystem {
2391 fn fail() -> ! {
2392 panic!(
2393 "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`"
2394 );
2395 }
2396}
2397
2398impl FileSystem for FallbackFileSystem {
2399 fn readlink(&self, _path: &Path) -> virtual_fs::Result<PathBuf> {
2400 Self::fail()
2401 }
2402 fn read_dir(&self, _path: &Path) -> Result<virtual_fs::ReadDir, FsError> {
2403 Self::fail();
2404 }
2405 fn create_dir(&self, _path: &Path) -> Result<(), FsError> {
2406 Self::fail();
2407 }
2408 fn remove_dir(&self, _path: &Path) -> Result<(), FsError> {
2409 Self::fail();
2410 }
2411 fn rename<'a>(&'a self, _from: &Path, _to: &Path) -> BoxFuture<'a, Result<(), FsError>> {
2412 Self::fail();
2413 }
2414 fn metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2415 Self::fail();
2416 }
2417 fn symlink_metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2418 Self::fail();
2419 }
2420 fn remove_file(&self, _path: &Path) -> Result<(), FsError> {
2421 Self::fail();
2422 }
2423 fn new_open_options(&self) -> virtual_fs::OpenOptions<'_> {
2424 Self::fail();
2425 }
2426 fn mount(
2427 &self,
2428 _name: String,
2429 _path: &Path,
2430 _fs: Box<dyn FileSystem + Send + Sync>,
2431 ) -> virtual_fs::Result<()> {
2432 Self::fail()
2433 }
2434}
2435
2436pub fn virtual_file_type_to_wasi_file_type(file_type: virtual_fs::FileType) -> Filetype {
2437 if file_type.is_dir() {
2439 Filetype::Directory
2440 } else if file_type.is_file() {
2441 Filetype::RegularFile
2442 } else if file_type.is_symlink() {
2443 Filetype::SymbolicLink
2444 } else {
2445 Filetype::Unknown
2446 }
2447}
2448
2449pub fn fs_error_from_wasi_err(err: Errno) -> FsError {
2450 match err {
2451 Errno::Badf => FsError::InvalidFd,
2452 Errno::Exist => FsError::AlreadyExists,
2453 Errno::Io => FsError::IOError,
2454 Errno::Addrinuse => FsError::AddressInUse,
2455 Errno::Addrnotavail => FsError::AddressNotAvailable,
2456 Errno::Pipe => FsError::BrokenPipe,
2457 Errno::Connaborted => FsError::ConnectionAborted,
2458 Errno::Connrefused => FsError::ConnectionRefused,
2459 Errno::Connreset => FsError::ConnectionReset,
2460 Errno::Intr => FsError::Interrupted,
2461 Errno::Inval => FsError::InvalidInput,
2462 Errno::Notconn => FsError::NotConnected,
2463 Errno::Nodev => FsError::NoDevice,
2464 Errno::Noent => FsError::EntryNotFound,
2465 Errno::Perm => FsError::PermissionDenied,
2466 Errno::Timedout => FsError::TimedOut,
2467 Errno::Proto => FsError::UnexpectedEof,
2468 Errno::Again => FsError::WouldBlock,
2469 Errno::Nospc => FsError::WriteZero,
2470 Errno::Notempty => FsError::DirectoryNotEmpty,
2471 _ => FsError::UnknownError,
2472 }
2473}
2474
2475pub fn fs_error_into_wasi_err(fs_error: FsError) -> Errno {
2476 match fs_error {
2477 FsError::AlreadyExists => Errno::Exist,
2478 FsError::AddressInUse => Errno::Addrinuse,
2479 FsError::AddressNotAvailable => Errno::Addrnotavail,
2480 FsError::BaseNotDirectory => Errno::Notdir,
2481 FsError::BrokenPipe => Errno::Pipe,
2482 FsError::ConnectionAborted => Errno::Connaborted,
2483 FsError::ConnectionRefused => Errno::Connrefused,
2484 FsError::ConnectionReset => Errno::Connreset,
2485 FsError::Interrupted => Errno::Intr,
2486 FsError::InvalidData => Errno::Io,
2487 FsError::InvalidFd => Errno::Badf,
2488 FsError::InvalidInput => Errno::Inval,
2489 FsError::IOError => Errno::Io,
2490 FsError::NoDevice => Errno::Nodev,
2491 FsError::NotAFile => Errno::Inval,
2492 FsError::NotConnected => Errno::Notconn,
2493 FsError::EntryNotFound => Errno::Noent,
2494 FsError::PermissionDenied => Errno::Perm,
2495 FsError::TimedOut => Errno::Timedout,
2496 FsError::UnexpectedEof => Errno::Proto,
2497 FsError::WouldBlock => Errno::Again,
2498 FsError::WriteZero => Errno::Nospc,
2499 FsError::DirectoryNotEmpty => Errno::Notempty,
2500 FsError::StorageFull => Errno::Overflow,
2501 FsError::Lock | FsError::UnknownError => Errno::Io,
2502 FsError::Unsupported => Errno::Notsup,
2503 }
2504}
2505
2506#[cfg(test)]
2507mod tests {
2508 use super::*;
2509
2510 #[tokio::test]
2511 async fn test_relative_path_to_absolute() {
2512 let inodes = WasiInodes::new();
2513 let fs_backing = WasiFsRoot::Sandbox(TmpFileSystem::new());
2514 let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap();
2515
2516 assert_eq!(
2518 wasi_fs.relative_path_to_absolute("/foo/bar".to_string()),
2519 "/foo/bar"
2520 );
2521 assert_eq!(wasi_fs.relative_path_to_absolute("/".to_string()), "/");
2522
2523 assert_eq!(
2525 wasi_fs.relative_path_to_absolute("//foo//bar//".to_string()),
2526 "//foo//bar//"
2527 );
2528 assert_eq!(
2529 wasi_fs.relative_path_to_absolute("/a/b/./c".to_string()),
2530 "/a/b/./c"
2531 );
2532 assert_eq!(
2533 wasi_fs.relative_path_to_absolute("/a/b/../c".to_string()),
2534 "/a/b/../c"
2535 );
2536
2537 assert_eq!(
2539 wasi_fs.relative_path_to_absolute("foo/bar".to_string()),
2540 "/foo/bar"
2541 );
2542 assert_eq!(wasi_fs.relative_path_to_absolute("foo".to_string()), "/foo");
2543
2544 wasi_fs.set_current_dir("/home/user");
2546 assert_eq!(
2547 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2548 "/home/user/file.txt"
2549 );
2550 assert_eq!(
2551 wasi_fs.relative_path_to_absolute("dir/file.txt".to_string()),
2552 "/home/user/dir/file.txt"
2553 );
2554
2555 wasi_fs.set_current_dir("/a/b/c");
2557 assert_eq!(
2558 wasi_fs.relative_path_to_absolute("./file.txt".to_string()),
2559 "/a/b/c/./file.txt"
2560 );
2561 assert_eq!(
2562 wasi_fs.relative_path_to_absolute("../file.txt".to_string()),
2563 "/a/b/c/../file.txt"
2564 );
2565 assert_eq!(
2566 wasi_fs.relative_path_to_absolute("../../file.txt".to_string()),
2567 "/a/b/c/../../file.txt"
2568 );
2569
2570 assert_eq!(
2572 wasi_fs.relative_path_to_absolute(".".to_string()),
2573 "/a/b/c/."
2574 );
2575 assert_eq!(
2576 wasi_fs.relative_path_to_absolute("..".to_string()),
2577 "/a/b/c/.."
2578 );
2579 assert_eq!(wasi_fs.relative_path_to_absolute("".to_string()), "/a/b/c/");
2580
2581 wasi_fs.set_current_dir("/home/user/");
2583 assert_eq!(
2584 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2585 "/home/user/file.txt"
2586 );
2587
2588 wasi_fs.set_current_dir("/home/user");
2590 assert_eq!(
2591 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2592 "/home/user/file.txt"
2593 );
2594 }
2595}