1mod fd;
9mod fd_list;
10mod inode_guard;
11mod notification;
12
13use std::{
14 borrow::{Borrow, Cow},
15 collections::{HashMap, HashSet},
16 ops::{Deref, DerefMut},
17 path::{Component, Path, PathBuf},
18 pin::Pin,
19 sync::{
20 Arc, Mutex, RwLock, Weak,
21 atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering},
22 },
23 task::{Context, Poll},
24};
25
26use self::fd_list::FdList;
27use crate::{
28 net::socket::InodeSocketKind,
29 state::{Stderr, Stdin, Stdout},
30};
31use futures::{Future, future::BoxFuture};
32#[cfg(feature = "enable-serde")]
33use serde_derive::{Deserialize, Serialize};
34use tokio::io::AsyncWriteExt;
35use tracing::{debug, trace};
36use virtual_fs::{
37 ArcFileSystem, FileSystem, FsError, MountFileSystem, OpenOptions, OverlayFileSystem,
38 TmpFileSystem, VirtualFile, limiter::DynFsMemoryLimiter,
39};
40use wasmer_config::package::PackageId;
41use wasmer_wasix_types::{
42 types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO},
43 wasi::{
44 Errno, Fd as WasiFd, Fdflags, Fdflagsext, Fdstat, Filesize, Filestat, Filetype,
45 Preopentype, Prestat, PrestatEnum, Rights, Socktype,
46 },
47};
48
49pub use self::fd::{Fd, FdInner, InodeVal, Kind};
50pub(crate) use self::inode_guard::{
51 InodeValFilePollGuard, InodeValFilePollGuardJoin, InodeValFilePollGuardMode,
52 InodeValFileReadGuard, InodeValFileWriteGuard, WasiStateFileGuard,
53};
54pub use self::notification::NotificationInner;
55use crate::syscalls::map_io_err;
56use crate::{ALL_RIGHTS, bin_factory::BinaryPackage, state::PreopenedDir};
57
58pub(crate) const MAX_FD: WasiFd = (64 * 1024) - 1;
62
63pub const VIRTUAL_ROOT_FD: WasiFd = 3;
77
78pub const FS_STDIN_INO: Inode = Inode(10);
81pub const FS_STDOUT_INO: Inode = Inode(11);
82pub const FS_STDERR_INO: Inode = Inode(12);
83pub const FS_ROOT_INO: Inode = Inode(13);
84
85const STDIN_DEFAULT_RIGHTS: Rights = {
86 Rights::from_bits_truncate(
89 Rights::FD_DATASYNC.bits()
90 | Rights::FD_READ.bits()
91 | Rights::FD_SYNC.bits()
92 | Rights::FD_ADVISE.bits()
93 | Rights::FD_FILESTAT_GET.bits()
94 | Rights::FD_FDSTAT_SET_FLAGS.bits()
95 | Rights::POLL_FD_READWRITE.bits(),
96 )
97};
98const STDOUT_DEFAULT_RIGHTS: Rights = {
99 Rights::from_bits_truncate(
102 Rights::FD_DATASYNC.bits()
103 | Rights::FD_SYNC.bits()
104 | Rights::FD_WRITE.bits()
105 | Rights::FD_ADVISE.bits()
106 | Rights::FD_FILESTAT_GET.bits()
107 | Rights::FD_FDSTAT_SET_FLAGS.bits()
108 | Rights::POLL_FD_READWRITE.bits(),
109 )
110};
111const STDERR_DEFAULT_RIGHTS: Rights = STDOUT_DEFAULT_RIGHTS;
112
113pub const MAX_SYMLINKS: u32 = 128;
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
119pub struct Inode(u64);
120
121impl Inode {
122 pub fn as_u64(&self) -> u64 {
123 self.0
124 }
125
126 pub fn from_path(str: &str) -> Self {
127 Inode(xxhash_rust::xxh64::xxh64(str.as_bytes(), 0))
128 }
129}
130
131#[derive(Debug, Clone)]
132pub struct InodeGuard {
133 ino: Inode,
134 inner: Arc<InodeVal>,
135
136 open_handles: Arc<AtomicI32>,
141}
142impl InodeGuard {
143 pub fn ino(&self) -> Inode {
144 self.ino
145 }
146
147 pub fn downgrade(&self) -> InodeWeakGuard {
148 InodeWeakGuard {
149 ino: self.ino,
150 open_handles: self.open_handles.clone(),
151 inner: Arc::downgrade(&self.inner),
152 }
153 }
154
155 pub fn ref_cnt(&self) -> usize {
156 Arc::strong_count(&self.inner)
157 }
158
159 pub fn handle_count(&self) -> u32 {
160 self.open_handles.load(Ordering::SeqCst) as u32
161 }
162
163 pub fn acquire_handle(&self) {
164 let prev_handles = self.open_handles.fetch_add(1, Ordering::SeqCst);
165 trace!(ino = %self.ino.0, new_count = %(prev_handles + 1), "acquiring handle for InodeGuard");
166 }
167
168 pub fn drop_one_handle(&self) {
169 let prev_handles = self.open_handles.fetch_sub(1, Ordering::SeqCst);
170
171 trace!(ino = %self.ino.0, %prev_handles, "dropping handle for InodeGuard");
172
173 if prev_handles > 1 {
175 return;
176 }
177
178 let mut guard = self.inner.write();
180
181 if prev_handles != 1 {
186 panic!("InodeGuard handle dropped too many times");
187 }
188
189 if self.open_handles.load(Ordering::SeqCst) != 0 {
191 return;
192 }
193
194 let ino = self.ino.0;
195 trace!(%ino, "InodeGuard has no more open handles");
196
197 match guard.deref_mut() {
198 Kind::File { handle, .. } if handle.is_some() => {
199 let file_ref_count = Arc::strong_count(handle.as_ref().unwrap());
200 trace!(%file_ref_count, %ino, "dropping file handle");
201 drop(handle.take().unwrap());
202 }
203 Kind::PipeRx { rx } => {
204 trace!(%ino, "closing pipe rx");
205 rx.close();
206 }
207 Kind::PipeTx { tx } => {
208 trace!(%ino, "closing pipe tx");
209 tx.close();
210 }
211 _ => (),
212 }
213 }
214}
215impl std::ops::Deref for InodeGuard {
216 type Target = InodeVal;
217 fn deref(&self) -> &Self::Target {
218 self.inner.deref()
219 }
220}
221
222#[derive(Debug, Clone)]
223pub struct InodeWeakGuard {
224 ino: Inode,
225 open_handles: Arc<AtomicI32>,
229 inner: Weak<InodeVal>,
230}
231impl InodeWeakGuard {
232 pub fn ino(&self) -> Inode {
233 self.ino
234 }
235 pub fn upgrade(&self) -> Option<InodeGuard> {
236 Weak::upgrade(&self.inner).map(|inner| InodeGuard {
237 ino: self.ino,
238 open_handles: self.open_handles.clone(),
239 inner,
240 })
241 }
242}
243
244#[derive(Debug)]
245#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
246struct EphemeralSymlinkEntry {
247 base_po_dir: WasiFd,
248 path_to_symlink: PathBuf,
249 relative_path: PathBuf,
250}
251
252#[derive(Debug)]
253#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
254struct WasiInodesProtected {
255 lookup: HashMap<Inode, Weak<InodeVal>>,
256}
257
258#[derive(Clone, Debug)]
259#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
260pub struct WasiInodes {
261 protected: Arc<RwLock<WasiInodesProtected>>,
262}
263
264fn normalize_virtual_symlink_key(path: &Path) -> PathBuf {
265 let mut normalized = PathBuf::new();
266 for component in path.components() {
267 match component {
268 Component::RootDir => normalized.push("/"),
269 Component::CurDir => {}
270 Component::ParentDir => {
271 if !normalized.as_os_str().is_empty() && normalized.as_os_str() != "/" {
272 normalized.pop();
273 }
274 }
275 Component::Normal(seg) => normalized.push(seg),
276 Component::Prefix(_) => {}
277 }
278 }
279 if normalized.as_os_str().is_empty() {
280 PathBuf::from("/")
281 } else {
282 normalized
283 }
284}
285
286impl WasiInodes {
287 pub fn new() -> Self {
288 Self {
289 protected: Arc::new(RwLock::new(WasiInodesProtected {
290 lookup: Default::default(),
291 })),
292 }
293 }
294
295 pub fn add_inode_val(&self, val: InodeVal) -> InodeGuard {
297 let val = Arc::new(val);
298 let st_ino = {
299 let guard = val.stat.read().unwrap();
300 guard.st_ino
301 };
302
303 let mut guard = self.protected.write().unwrap();
304 let ino = Inode(st_ino);
305 guard.lookup.insert(ino, Arc::downgrade(&val));
306
307 if guard.lookup.len() % 100 == 1 {
309 guard.lookup.retain(|_, v| Weak::strong_count(v) > 0);
310 }
311
312 let open_handles = Arc::new(AtomicI32::new(0));
313
314 InodeGuard {
315 ino,
316 open_handles,
317 inner: val,
318 }
319 }
320
321 pub(crate) fn stdout(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
323 Self::std_dev_get(fd_map, __WASI_STDOUT_FILENO)
324 }
325 pub(crate) fn stdout_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
327 Self::std_dev_get_mut(fd_map, __WASI_STDOUT_FILENO)
328 }
329
330 pub(crate) fn stderr(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
332 Self::std_dev_get(fd_map, __WASI_STDERR_FILENO)
333 }
334 pub(crate) fn stderr_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
336 Self::std_dev_get_mut(fd_map, __WASI_STDERR_FILENO)
337 }
338
339 #[allow(dead_code)]
342 pub(crate) fn stdin(fd_map: &RwLock<FdList>) -> Result<InodeValFileReadGuard, FsError> {
343 Self::std_dev_get(fd_map, __WASI_STDIN_FILENO)
344 }
345 pub(crate) fn stdin_mut(fd_map: &RwLock<FdList>) -> Result<InodeValFileWriteGuard, FsError> {
347 Self::std_dev_get_mut(fd_map, __WASI_STDIN_FILENO)
348 }
349
350 fn std_dev_get(fd_map: &RwLock<FdList>, fd: WasiFd) -> Result<InodeValFileReadGuard, FsError> {
353 if let Some(fd) = fd_map.read().unwrap().get(fd) {
354 let guard = fd.inode.read();
355 if let Kind::File {
356 handle: Some(handle),
357 ..
358 } = guard.deref()
359 {
360 Ok(InodeValFileReadGuard::new(handle))
361 } else {
362 Err(FsError::NotAFile)
364 }
365 } else {
366 Err(FsError::NoDevice)
368 }
369 }
370 fn std_dev_get_mut(
373 fd_map: &RwLock<FdList>,
374 fd: WasiFd,
375 ) -> Result<InodeValFileWriteGuard, FsError> {
376 if let Some(fd) = fd_map.read().unwrap().get(fd) {
377 let guard = fd.inode.read();
378 if let Kind::File {
379 handle: Some(handle),
380 ..
381 } = guard.deref()
382 {
383 Ok(InodeValFileWriteGuard::new(handle))
384 } else {
385 Err(FsError::NotAFile)
387 }
388 } else {
389 Err(FsError::NoDevice)
391 }
392 }
393}
394
395impl Default for WasiInodes {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401#[derive(Debug, Clone)]
402pub struct WasiFsRoot {
403 root: Arc<MountFileSystem>,
404 memory_limiter: Option<DynFsMemoryLimiter>,
405}
406
407impl WasiFsRoot {
408 pub fn from_mount_fs(root: MountFileSystem) -> Self {
409 Self {
410 root: Arc::new(root),
411 memory_limiter: None,
412 }
413 }
414
415 pub fn from_filesystem(fs: Arc<dyn FileSystem + Send + Sync>) -> Self {
416 let root = MountFileSystem::new();
417 root.mount(Path::new("/"), fs)
418 .expect("mounting the root fs on an empty mount fs should succeed");
419
420 Self {
421 root: Arc::new(root),
422 memory_limiter: None,
423 }
424 }
425
426 pub fn with_memory_limiter_opt(mut self, limiter: Option<DynFsMemoryLimiter>) -> Self {
427 self.memory_limiter = limiter;
428 self
429 }
430
431 pub(crate) fn memory_limiter(&self) -> Option<&DynFsMemoryLimiter> {
432 self.memory_limiter.as_ref()
433 }
434
435 pub(crate) fn root(&self) -> &Arc<MountFileSystem> {
436 &self.root
437 }
438
439 pub(crate) fn writable_root(&self) -> Option<TmpFileSystem> {
440 let root = self.root.filesystem_at(Path::new("/"))?;
441 find_writable_root(root.as_ref())
442 }
443
444 pub(crate) fn stack_root_filesystem(
445 &self,
446 lower: Arc<dyn FileSystem + Send + Sync>,
447 ) -> Result<(), FsError> {
448 let current = self
449 .root
450 .filesystem_at(Path::new("/"))
451 .ok_or(FsError::EntryNotFound)?;
452 let overlay =
453 OverlayFileSystem::new(ArcFileSystem::new(current), [ArcFileSystem::new(lower)]);
454 self.root.set_mount(Path::new("/"), Arc::new(overlay))
455 }
456}
457
458fn find_writable_root(fs: &(dyn FileSystem + Send + Sync)) -> Option<TmpFileSystem> {
459 if let Some(tmp) = fs.upcast_any_ref().downcast_ref::<TmpFileSystem>() {
460 return Some(tmp.clone());
461 }
462
463 if let Some(arc_fs) = fs.upcast_any_ref().downcast_ref::<ArcFileSystem>() {
464 return find_writable_root(arc_fs.inner().as_ref());
465 }
466
467 if let Some(overlay) = fs
468 .upcast_any_ref()
469 .downcast_ref::<OverlayFileSystem<ArcFileSystem, Vec<Arc<dyn FileSystem + Send + Sync>>>>()
470 {
471 return find_writable_root(overlay.primary());
472 }
473
474 if let Some(overlay) = fs
475 .upcast_any_ref()
476 .downcast_ref::<OverlayFileSystem<ArcFileSystem, [ArcFileSystem; 1]>>()
477 {
478 return find_writable_root(overlay.primary());
479 }
480
481 None
482}
483
484impl FileSystem for WasiFsRoot {
485 fn readlink(&self, path: &Path) -> virtual_fs::Result<PathBuf> {
486 self.root.readlink(path)
487 }
488
489 fn read_dir(&self, path: &Path) -> virtual_fs::Result<virtual_fs::ReadDir> {
490 self.root.read_dir(path)
491 }
492
493 fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> {
494 self.root.create_dir(path)
495 }
496
497 fn create_symlink(&self, source: &Path, target: &Path) -> virtual_fs::Result<()> {
498 self.root.create_symlink(source, target)
499 }
500
501 fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> {
502 self.root.remove_dir(path)
503 }
504
505 fn rename<'a>(&'a self, from: &Path, to: &Path) -> BoxFuture<'a, virtual_fs::Result<()>> {
506 let from = from.to_owned();
507 let to = to.to_owned();
508 let this = self.clone();
509 Box::pin(async move { this.root.rename(&from, &to).await })
510 }
511
512 fn metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
513 self.root.metadata(path)
514 }
515
516 fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
517 self.root.symlink_metadata(path)
518 }
519
520 fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> {
521 self.root.remove_file(path)
522 }
523
524 fn new_open_options(&self) -> OpenOptions<'_> {
525 self.root.new_open_options()
526 }
527}
528
529#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
532pub struct WasiFs {
533 pub preopen_fds: RwLock<Vec<u32>>,
535 pub fd_map: RwLock<FdList>,
536 pub current_dir: Mutex<String>,
537 #[cfg_attr(feature = "enable-serde", serde(skip, default))]
538 pub root_fs: WasiFsRoot,
539 pub root_inode: InodeGuard,
540 pub has_unioned: Mutex<HashSet<PackageId>>,
541 ephemeral_symlinks: Arc<RwLock<HashMap<PathBuf, EphemeralSymlinkEntry>>>,
542
543 is_wasix: AtomicBool,
548
549 pub(crate) init_preopens: Vec<PreopenedDir>,
551 pub(crate) init_vfs_preopens: Vec<String>,
553}
554
555impl WasiFs {
556 fn writable_package_mount(
557 fs: Arc<dyn FileSystem + Send + Sync>,
558 limiter: Option<&DynFsMemoryLimiter>,
559 ) -> Arc<dyn FileSystem + Send + Sync> {
560 let upper = TmpFileSystem::new();
561 if let Some(limiter) = limiter {
562 upper.set_memory_limiter(limiter.clone());
563 }
564
565 Arc::new(OverlayFileSystem::new(upper, [ArcFileSystem::new(fs)]))
566 }
567
568 pub fn is_wasix(&self) -> bool {
569 self.is_wasix.load(Ordering::Relaxed)
572 }
573
574 pub fn set_is_wasix(&self, is_wasix: bool) {
575 self.is_wasix.store(is_wasix, Ordering::SeqCst);
576 }
577
578 pub(crate) fn register_ephemeral_symlink(
579 &self,
580 full_path: PathBuf,
581 base_po_dir: WasiFd,
582 path_to_symlink: PathBuf,
583 relative_path: PathBuf,
584 ) {
585 let mut guard = self.ephemeral_symlinks.write().unwrap();
586 guard.insert(
587 normalize_virtual_symlink_key(&full_path),
588 EphemeralSymlinkEntry {
589 base_po_dir,
590 path_to_symlink: normalize_virtual_symlink_key(&path_to_symlink),
591 relative_path,
592 },
593 );
594 }
595
596 pub(crate) fn ephemeral_symlink_at(
597 &self,
598 full_path: &Path,
599 ) -> Option<(WasiFd, PathBuf, PathBuf)> {
600 let guard = self.ephemeral_symlinks.read().unwrap();
601 let entry = guard.get(&normalize_virtual_symlink_key(full_path))?;
602 Some((
603 entry.base_po_dir,
604 entry.path_to_symlink.clone(),
605 entry.relative_path.clone(),
606 ))
607 }
608
609 pub(crate) fn unregister_ephemeral_symlink(&self, full_path: &Path) {
610 let mut guard = self.ephemeral_symlinks.write().unwrap();
611 guard.remove(&normalize_virtual_symlink_key(full_path));
612 }
613
614 pub(crate) fn move_ephemeral_symlink(
615 &self,
616 old_full_path: &Path,
617 new_full_path: &Path,
618 base_po_dir: WasiFd,
619 path_to_symlink: PathBuf,
620 relative_path: PathBuf,
621 ) {
622 let old_key = normalize_virtual_symlink_key(old_full_path);
623 let new_key = normalize_virtual_symlink_key(new_full_path);
624
625 let mut guard = self.ephemeral_symlinks.write().unwrap();
626 guard.remove(&old_key);
627 guard.insert(
628 new_key,
629 EphemeralSymlinkEntry {
630 base_po_dir,
631 path_to_symlink: normalize_virtual_symlink_key(&path_to_symlink),
632 relative_path,
633 },
634 );
635 }
636
637 pub fn fork(&self) -> Self {
639 Self {
640 preopen_fds: RwLock::new(self.preopen_fds.read().unwrap().clone()),
641 fd_map: RwLock::new(self.fd_map.read().unwrap().clone()),
642 current_dir: Mutex::new(self.current_dir.lock().unwrap().clone()),
643 is_wasix: AtomicBool::new(self.is_wasix.load(Ordering::Acquire)),
644 root_fs: self.root_fs.clone(),
645 root_inode: self.root_inode.clone(),
646 has_unioned: Mutex::new(self.has_unioned.lock().unwrap().clone()),
647 ephemeral_symlinks: self.ephemeral_symlinks.clone(),
648 init_preopens: self.init_preopens.clone(),
649 init_vfs_preopens: self.init_vfs_preopens.clone(),
650 }
651 }
652
653 pub async fn close_cloexec_fds(&self) {
655 let to_close = {
656 if let Ok(map) = self.fd_map.read() {
657 map.iter()
658 .filter_map(|(k, v)| {
659 if v.inner.fd_flags.contains(Fdflagsext::CLOEXEC)
660 && !v.is_stdio
661 && !v.inode.is_preopened
662 {
663 tracing::trace!(fd = %k, "Closing FD due to CLOEXEC flag");
664 Some(k)
665 } else {
666 None
667 }
668 })
669 .collect::<HashSet<_>>()
670 } else {
671 HashSet::new()
672 }
673 };
674
675 let _ = tokio::join!(async {
676 for fd in &to_close {
677 self.flush(*fd).await.ok();
678 self.close_fd(*fd).ok();
679 }
680 });
681
682 if let Ok(mut map) = self.fd_map.write() {
683 for fd in &to_close {
684 map.remove(*fd);
685 }
686 }
687 }
688
689 pub async fn close_all(&self) {
691 let mut to_close = {
692 if let Ok(map) = self.fd_map.read() {
693 map.keys().collect::<HashSet<_>>()
694 } else {
695 HashSet::new()
696 }
697 };
698 to_close.insert(__WASI_STDOUT_FILENO);
699 to_close.insert(__WASI_STDERR_FILENO);
700
701 let _ = tokio::join!(async {
702 for fd in to_close {
703 self.flush(fd).await.ok();
704 self.close_fd(fd).ok();
705 }
706 });
707
708 if let Ok(mut map) = self.fd_map.write() {
709 map.clear();
710 }
711 }
712
713 pub async fn conditional_union(
716 &self,
717 binary: &BinaryPackage,
718 ) -> Result<(), virtual_fs::FsError> {
719 let Some(package_mounts) = &binary.package_mounts else {
720 return Ok(());
721 };
722
723 let needs_to_be_unioned = self.has_unioned.lock().unwrap().insert(binary.id.clone());
724 if !needs_to_be_unioned {
725 return Ok(());
726 }
727
728 if let Some(root_layer) = &package_mounts.root_layer {
729 self.root_fs
730 .stack_root_filesystem(Self::writable_package_mount(
731 root_layer.clone(),
732 self.root_fs.memory_limiter(),
733 ))?;
734 }
735
736 for mount in &package_mounts.mounts {
737 self.root_fs.root().mount_with_source(
738 &mount.guest_path,
739 &mount.source_path,
740 Self::writable_package_mount(mount.fs.clone(), self.root_fs.memory_limiter()),
741 )?;
742 }
743
744 Ok(())
745 }
746
747 pub(crate) fn new_with_preopen(
749 inodes: &WasiInodes,
750 preopens: &[PreopenedDir],
751 vfs_preopens: &[String],
752 fs_backing: WasiFsRoot,
753 ) -> Result<Self, String> {
754 let mut wasi_fs = Self::new_init(fs_backing, inodes, FS_ROOT_INO)?;
755 wasi_fs.init_preopens = preopens.to_vec();
756 wasi_fs.init_vfs_preopens = vfs_preopens.to_vec();
757 wasi_fs.create_preopens(inodes, false)?;
758 Ok(wasi_fs)
759 }
760
761 pub(crate) fn relative_path_to_absolute(&self, path: String) -> String {
763 if path.starts_with('/') {
764 return path;
765 }
766
767 let current_dir = self.current_dir.lock().unwrap();
768 format!("{}/{}", current_dir.trim_end_matches('/'), path)
769 }
770
771 fn new_init(
774 fs_backing: WasiFsRoot,
775 inodes: &WasiInodes,
776 st_ino: Inode,
777 ) -> Result<Self, String> {
778 debug!("Initializing WASI filesystem");
779
780 let stat = Filestat {
781 st_filetype: Filetype::Directory,
782 st_ino: st_ino.as_u64(),
783 ..Filestat::default()
784 };
785 let root_kind = Kind::Root {
786 entries: HashMap::new(),
787 };
788 let root_inode = inodes.add_inode_val(InodeVal {
789 stat: RwLock::new(stat),
790 is_preopened: true,
791 name: RwLock::new("/".into()),
792 kind: RwLock::new(root_kind),
793 });
794
795 let wasi_fs = Self {
796 preopen_fds: RwLock::new(vec![]),
797 fd_map: RwLock::new(FdList::new()),
798 current_dir: Mutex::new("/".to_string()),
799 is_wasix: AtomicBool::new(false),
800 root_fs: fs_backing,
801 root_inode,
802 has_unioned: Mutex::new(HashSet::new()),
803 ephemeral_symlinks: Arc::new(RwLock::new(HashMap::new())),
804 init_preopens: Default::default(),
805 init_vfs_preopens: Default::default(),
806 };
807 wasi_fs.create_stdin(inodes);
808 wasi_fs.create_stdout(inodes);
809 wasi_fs.create_stderr(inodes);
810 wasi_fs.create_rootfd()?;
811
812 Ok(wasi_fs)
813 }
814
815 #[allow(dead_code)]
825 #[allow(clippy::too_many_arguments)]
826 pub unsafe fn open_dir_all(
827 &mut self,
828 inodes: &WasiInodes,
829 base: WasiFd,
830 name: String,
831 rights: Rights,
832 rights_inheriting: Rights,
833 flags: Fdflags,
834 fd_flags: Fdflagsext,
835 ) -> Result<WasiFd, FsError> {
836 let mut cur_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
839
840 let path: &Path = Path::new(&name);
841 for c in path.components() {
843 let segment_name = c.as_os_str().to_string_lossy().to_string();
844 let guard = cur_inode.read();
845 match guard.deref() {
846 Kind::Dir { entries, .. } | Kind::Root { entries } => {
847 if let Some(_entry) = entries.get(&segment_name) {
848 return Err(FsError::AlreadyExists);
850 }
851
852 let kind = Kind::Dir {
853 parent: cur_inode.downgrade(),
854 path: PathBuf::from(""),
855 entries: HashMap::new(),
856 };
857
858 drop(guard);
859 let inode = self.create_inode_with_default_stat(
860 inodes,
861 kind,
862 false,
863 segment_name.clone().into(),
864 );
865
866 {
868 let mut guard = cur_inode.write();
869 match guard.deref_mut() {
870 Kind::Dir { entries, .. } | Kind::Root { entries } => {
871 entries.insert(segment_name, inode.clone());
872 }
873 _ => unreachable!("Dir or Root became not Dir or Root"),
874 }
875 }
876 cur_inode = inode;
877 }
878 _ => return Err(FsError::BaseNotDirectory),
879 }
880 }
881
882 self.create_fd(
884 rights,
885 rights_inheriting,
886 flags,
887 fd_flags,
888 Fd::READ | Fd::WRITE,
889 cur_inode,
890 )
891 .map_err(fs_error_from_wasi_err)
892 }
893
894 #[allow(dead_code, clippy::too_many_arguments)]
899 pub fn open_file_at(
900 &mut self,
901 inodes: &WasiInodes,
902 base: WasiFd,
903 file: Box<dyn VirtualFile + Send + Sync + 'static>,
904 open_flags: u16,
905 name: String,
906 rights: Rights,
907 rights_inheriting: Rights,
908 flags: Fdflags,
909 fd_flags: Fdflagsext,
910 ) -> Result<WasiFd, FsError> {
911 let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?;
914
915 let guard = base_inode.read();
916 match guard.deref() {
917 Kind::Dir { entries, .. } | Kind::Root { entries } => {
918 if let Some(_entry) = entries.get(&name) {
919 return Err(FsError::AlreadyExists);
921 }
922
923 let kind = Kind::File {
924 handle: Some(Arc::new(RwLock::new(file))),
925 path: PathBuf::from(""),
926 fd: None,
927 };
928
929 drop(guard);
930 let inode = self
931 .create_inode(inodes, kind, false, name.clone())
932 .map_err(|_| FsError::IOError)?;
933
934 {
935 let mut guard = base_inode.write();
936 match guard.deref_mut() {
937 Kind::Dir { entries, .. } | Kind::Root { entries } => {
938 entries.insert(name, inode.clone());
939 }
940 _ => unreachable!("Dir or Root became not Dir or Root"),
941 }
942 }
943
944 let real_fd = self
946 .create_fd(
947 rights,
948 rights_inheriting,
949 flags,
950 fd_flags,
951 open_flags,
952 inode.clone(),
953 )
954 .map_err(fs_error_from_wasi_err)?;
955
956 {
957 let mut guard = inode.kind.write().unwrap();
958 match guard.deref_mut() {
959 Kind::File { fd, .. } => {
960 *fd = Some(real_fd);
961 }
962 _ => unreachable!("We just created a Kind::File"),
963 }
964 }
965
966 Ok(real_fd)
967 }
968 _ => Err(FsError::BaseNotDirectory),
969 }
970 }
971
972 #[allow(dead_code)]
976 pub fn swap_file(
977 &self,
978 fd: WasiFd,
979 mut file: Box<dyn VirtualFile + Send + Sync + 'static>,
980 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
981 match fd {
982 __WASI_STDIN_FILENO => {
983 let mut target = WasiInodes::stdin_mut(&self.fd_map)?;
984 Ok(Some(target.swap(file)))
985 }
986 __WASI_STDOUT_FILENO => {
987 let mut target = WasiInodes::stdout_mut(&self.fd_map)?;
988 Ok(Some(target.swap(file)))
989 }
990 __WASI_STDERR_FILENO => {
991 let mut target = WasiInodes::stderr_mut(&self.fd_map)?;
992 Ok(Some(target.swap(file)))
993 }
994 _ => {
995 let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?;
996 {
997 let guard = base_inode.read();
999 match guard.deref() {
1000 Kind::File { handle, .. } => {
1001 if let Some(handle) = handle {
1002 let mut handle = handle.write().unwrap();
1003 std::mem::swap(handle.deref_mut(), &mut file);
1004 return Ok(Some(file));
1005 }
1006 }
1007 _ => return Err(FsError::NotAFile),
1008 }
1009 }
1010 let mut guard = base_inode.write();
1012 match guard.deref_mut() {
1013 Kind::File { handle, .. } => {
1014 if let Some(handle) = handle {
1015 let mut handle = handle.write().unwrap();
1016 std::mem::swap(handle.deref_mut(), &mut file);
1017 Ok(Some(file))
1018 } else {
1019 handle.replace(Arc::new(RwLock::new(file)));
1020 Ok(None)
1021 }
1022 }
1023 _ => Err(FsError::NotAFile),
1024 }
1025 }
1026 }
1027 }
1028
1029 pub fn filestat_resync_size(&self, fd: WasiFd) -> Result<Filesize, Errno> {
1031 let inode = self.get_fd_inode(fd)?;
1032 let mut guard = inode.write();
1033 match guard.deref_mut() {
1034 Kind::File { handle, .. } => {
1035 if let Some(h) = handle {
1036 let h = h.read().unwrap();
1037 let new_size = h.size();
1038 drop(h);
1039 drop(guard);
1040
1041 inode.stat.write().unwrap().st_size = new_size;
1042 Ok(new_size as Filesize)
1043 } else {
1044 Err(Errno::Badf)
1045 }
1046 }
1047 Kind::Dir { .. } | Kind::Root { .. } => Err(Errno::Isdir),
1048 _ => Err(Errno::Inval),
1049 }
1050 }
1051
1052 pub fn set_current_dir(&self, path: &str) {
1054 let mut guard = self.current_dir.lock().unwrap();
1055 *guard = path.to_string();
1056 }
1057
1058 pub fn get_current_dir(
1060 &self,
1061 inodes: &WasiInodes,
1062 base: WasiFd,
1063 ) -> Result<(InodeGuard, String), Errno> {
1064 self.get_current_dir_inner(inodes, base, 0)
1065 }
1066
1067 pub(crate) fn get_current_dir_inner(
1068 &self,
1069 inodes: &WasiInodes,
1070 base: WasiFd,
1071 symlink_count: u32,
1072 ) -> Result<(InodeGuard, String), Errno> {
1073 let current_dir = {
1074 let guard = self.current_dir.lock().unwrap();
1075 guard.clone()
1076 };
1077 let cur_inode = self.get_fd_inode(base)?;
1078 let inode = self.get_inode_at_path_inner(
1079 inodes,
1080 cur_inode,
1081 current_dir.as_str(),
1082 symlink_count,
1083 true,
1084 )?;
1085 Ok((inode, current_dir))
1086 }
1087
1088 fn get_inode_at_path_inner(
1102 &self,
1103 inodes: &WasiInodes,
1104 mut cur_inode: InodeGuard,
1105 path_str: &str,
1106 mut symlink_count: u32,
1107 follow_symlinks: bool,
1108 ) -> Result<InodeGuard, Errno> {
1109 if symlink_count > MAX_SYMLINKS {
1110 return Err(Errno::Mlink);
1111 }
1112
1113 if path_str == "/" {
1116 let guard = cur_inode.read();
1117 if let Kind::Root { entries } = guard.deref()
1118 && let Some(root_entry) = entries.get("/")
1119 {
1120 return Ok(root_entry.clone());
1121 }
1122 }
1123
1124 let path: &Path = Path::new(path_str);
1125 let n_components = path.components().count();
1126
1127 'path_iter: for (i, component) in path.components().enumerate() {
1129 if matches!(component, Component::RootDir) {
1133 continue;
1134 }
1135
1136 let last_component = i + 1 == n_components;
1138 'symlink_resolution: while symlink_count < MAX_SYMLINKS {
1141 let processing_cur_inode = cur_inode.clone();
1142 let mut guard = processing_cur_inode.write();
1143 match guard.deref_mut() {
1144 Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"),
1145 Kind::Dir {
1146 entries,
1147 path,
1148 parent,
1149 ..
1150 } => {
1151 match component.as_os_str().to_string_lossy().borrow() {
1152 ".." => {
1153 if let Some(p) = parent.upgrade() {
1154 cur_inode = p;
1155 continue 'path_iter;
1156 } else {
1157 return Err(Errno::Access);
1158 }
1159 }
1160 "." => continue 'path_iter,
1161 _ => (),
1162 }
1163 let mut loop_for_symlink = false;
1165 if let Some(entry) =
1166 entries.get(component.as_os_str().to_string_lossy().as_ref())
1167 {
1168 cur_inode = entry.clone();
1169 } else {
1170 let file = {
1171 let mut cd = path.clone();
1172 cd.push(component);
1173 cd
1174 };
1175 let should_insert;
1178
1179 let kind = if let Some((base_po_dir, path_to_symlink, relative_path)) =
1180 self.ephemeral_symlink_at(&file)
1181 {
1182 should_insert = false;
1185 loop_for_symlink = true;
1186 symlink_count += 1;
1187 Kind::Symlink {
1188 base_po_dir,
1189 path_to_symlink,
1190 relative_path,
1191 }
1192 } else {
1193 let metadata = self
1194 .root_fs
1195 .symlink_metadata(&file)
1196 .ok()
1197 .ok_or(Errno::Noent)?;
1198 let file_type = metadata.file_type();
1199 if file_type.is_dir() {
1200 should_insert = true;
1201 Kind::Dir {
1203 parent: cur_inode.downgrade(),
1204 path: file.clone(),
1205 entries: Default::default(),
1206 }
1207 } else if file_type.is_file() {
1208 should_insert = true;
1209 Kind::File {
1211 handle: None,
1212 path: file.clone(),
1213 fd: None,
1214 }
1215 } else if file_type.is_symlink() {
1216 should_insert = false;
1217 let link_value =
1218 self.root_fs.readlink(&file).ok().ok_or(Errno::Noent)?;
1219 debug!("attempting to decompose path {:?}", link_value);
1220 let (pre_open_dir_fd, path_to_symlink) =
1221 self.path_into_pre_open_and_relative_path(&file)?;
1222 loop_for_symlink = true;
1223 symlink_count += 1;
1224 Kind::Symlink {
1225 base_po_dir: pre_open_dir_fd,
1226 path_to_symlink: path_to_symlink.to_owned(),
1227 relative_path: link_value,
1228 }
1229 } else {
1230 #[cfg(unix)]
1231 {
1232 let file_type: Filetype = if file_type.is_char_device() {
1234 Filetype::CharacterDevice
1235 } else if file_type.is_block_device() {
1236 Filetype::BlockDevice
1237 } else if file_type.is_fifo() {
1238 Filetype::Unknown
1240 } else if file_type.is_socket() {
1241 Filetype::SocketStream
1244 } else {
1245 unimplemented!(
1246 "state::get_inode_at_path unknown file type: not file, directory, symlink, char device, block device, fifo, or socket"
1247 );
1248 };
1249
1250 let kind = Kind::File {
1251 handle: None,
1252 path: file.clone(),
1253 fd: None,
1254 };
1255 drop(guard);
1256 let new_inode = self.create_inode_with_stat(
1257 inodes,
1258 kind,
1259 false,
1260 file.to_string_lossy().to_string().into(),
1261 Filestat {
1262 st_filetype: file_type,
1263 st_ino: Inode::from_path(path_str).as_u64(),
1264 st_size: metadata.len(),
1265 st_ctim: metadata.created(),
1266 st_mtim: metadata.modified(),
1267 st_atim: metadata.accessed(),
1268 ..Filestat::default()
1269 },
1270 );
1271
1272 let mut guard = cur_inode.write();
1273 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1274 entries.insert(
1275 component.as_os_str().to_string_lossy().to_string(),
1276 new_inode.clone(),
1277 );
1278 } else {
1279 unreachable!(
1280 "Attempted to insert special device into non-directory"
1281 );
1282 }
1283 return Ok(new_inode);
1285 }
1286 #[cfg(not(unix))]
1287 unimplemented!(
1288 "state::get_inode_at_path unknown file type: not file, directory, or symlink"
1289 );
1290 }
1291 };
1292 drop(guard);
1293
1294 let new_inode = self.create_inode(
1295 inodes,
1296 kind,
1297 false,
1298 file.to_string_lossy().to_string(),
1299 )?;
1300 if should_insert {
1301 let mut guard = processing_cur_inode.write();
1302 if let Kind::Dir { entries, .. } = guard.deref_mut() {
1303 entries.insert(
1304 component.as_os_str().to_string_lossy().to_string(),
1305 new_inode.clone(),
1306 );
1307 }
1308 }
1309 cur_inode = new_inode;
1310
1311 if loop_for_symlink && follow_symlinks {
1312 debug!("Following symlink to {:?}", cur_inode);
1313 continue 'symlink_resolution;
1314 }
1315 }
1316 }
1317 Kind::Root { entries } => {
1318 match component {
1319 Component::ParentDir => continue 'path_iter,
1321 Component::CurDir => continue 'path_iter,
1323 _ => {}
1324 }
1325
1326 let component = component.as_os_str().to_string_lossy();
1327
1328 if let Some(entry) = entries.get(component.as_ref()) {
1329 cur_inode = entry.clone();
1330 } else if let Some(root) = entries.get(&"/".to_string()) {
1331 cur_inode = root.clone();
1332 continue 'symlink_resolution;
1333 } else {
1334 return Err(Errno::Notcapable);
1336 }
1337 }
1338 Kind::File { .. }
1339 | Kind::Socket { .. }
1340 | Kind::PipeRx { .. }
1341 | Kind::PipeTx { .. }
1342 | Kind::DuplexPipe { .. }
1343 | Kind::EventNotifications { .. }
1344 | Kind::Epoll { .. } => {
1345 return Err(Errno::Notdir);
1346 }
1347 Kind::Symlink {
1348 base_po_dir,
1349 path_to_symlink,
1350 relative_path,
1351 } => {
1352 let (new_base_inode, new_path) = if relative_path.is_absolute() {
1353 (
1356 self.get_fd_inode(VIRTUAL_ROOT_FD)?,
1357 relative_path.to_string_lossy().to_string(),
1358 )
1359 } else {
1360 let new_base_dir = *base_po_dir;
1361 let new_base_inode = self.get_fd_inode(new_base_dir)?;
1362 let new_path = {
1364 let mut base = path_to_symlink.clone();
1368 base.pop();
1371 base.push(relative_path);
1372 base.to_string_lossy().to_string()
1373 };
1374 (new_base_inode, new_path)
1375 };
1376 debug!("Following symlink recursively");
1377 drop(guard);
1378 let symlink_inode = self.get_inode_at_path_inner(
1379 inodes,
1380 new_base_inode,
1381 &new_path,
1382 symlink_count + 1,
1383 follow_symlinks,
1384 )?;
1385 cur_inode = symlink_inode;
1386 let guard = cur_inode.read();
1389 if let Kind::File { .. } = guard.deref() {
1390 if last_component {
1392 break 'symlink_resolution;
1393 }
1394 }
1395 continue 'symlink_resolution;
1396 }
1397 }
1398 break 'symlink_resolution;
1399 }
1400 }
1401
1402 Ok(cur_inode)
1403 }
1404
1405 fn path_into_pre_open_and_relative_path<'path>(
1415 &self,
1416 path: &'path Path,
1417 ) -> Result<(WasiFd, &'path Path), Errno> {
1418 enum BaseFdAndRelPath<'a> {
1419 None,
1420 BestMatch {
1421 fd: WasiFd,
1422 rel_path: &'a Path,
1423 max_seen: usize,
1424 },
1425 }
1426
1427 impl BaseFdAndRelPath<'_> {
1428 const fn max_seen(&self) -> usize {
1429 match self {
1430 Self::None => 0,
1431 Self::BestMatch { max_seen, .. } => *max_seen,
1432 }
1433 }
1434 }
1435 let mut res = BaseFdAndRelPath::None;
1436 let preopen_fds = self.preopen_fds.read().unwrap();
1438 for po_fd in preopen_fds.deref() {
1439 let po_inode = self
1440 .fd_map
1441 .read()
1442 .unwrap()
1443 .get(*po_fd)
1444 .unwrap()
1445 .inode
1446 .clone();
1447 let guard = po_inode.read();
1448 let po_path = match guard.deref() {
1449 Kind::Dir { path, .. } => &**path,
1450 Kind::Root { .. } => Path::new("/"),
1451 _ => unreachable!("Preopened FD that's not a directory or the root"),
1452 };
1453 if let Ok(stripped_path) = path.strip_prefix(po_path) {
1455 let new_prefix_len = po_path.as_os_str().len();
1457 if new_prefix_len >= res.max_seen() {
1460 res = BaseFdAndRelPath::BestMatch {
1461 fd: *po_fd,
1462 rel_path: stripped_path,
1463 max_seen: new_prefix_len,
1464 };
1465 }
1466 }
1467 }
1468 match res {
1469 BaseFdAndRelPath::None => Err(Errno::Inval),
1471 BaseFdAndRelPath::BestMatch { fd, rel_path, .. } => Ok((fd, rel_path)),
1472 }
1473 }
1474
1475 pub(crate) fn path_into_pre_open_and_relative_path_owned(
1476 &self,
1477 path: &Path,
1478 ) -> Result<(WasiFd, PathBuf), Errno> {
1479 let (fd, rel_path) = self.path_into_pre_open_and_relative_path(path)?;
1480 Ok((fd, rel_path.to_owned()))
1481 }
1482
1483 pub(crate) fn get_inode_at_path(
1490 &self,
1491 inodes: &WasiInodes,
1492 base: WasiFd,
1493 path: &str,
1494 follow_symlinks: bool,
1495 ) -> Result<InodeGuard, Errno> {
1496 let base_inode = self.get_fd_inode(base)?;
1497 self.get_inode_at_path_inner(inodes, base_inode, path, 0, follow_symlinks)
1498 }
1499
1500 pub(crate) fn get_parent_inode_at_path(
1503 &self,
1504 inodes: &WasiInodes,
1505 base: WasiFd,
1506 path: &Path,
1507 follow_symlinks: bool,
1508 ) -> Result<(InodeGuard, String), Errno> {
1509 let mut parent_dir = std::path::PathBuf::new();
1510 let mut components = path.components().rev();
1511 let new_entity_name = components
1512 .next()
1513 .ok_or(Errno::Inval)?
1514 .as_os_str()
1515 .to_string_lossy()
1516 .to_string();
1517 for comp in components.rev() {
1518 parent_dir.push(comp);
1519 }
1520 self.get_inode_at_path(inodes, base, &parent_dir.to_string_lossy(), follow_symlinks)
1521 .map(|v| (v, new_entity_name))
1522 }
1523
1524 pub fn get_fd(&self, fd: WasiFd) -> Result<Fd, Errno> {
1525 let ret = self
1526 .fd_map
1527 .read()
1528 .unwrap()
1529 .get(fd)
1530 .ok_or(Errno::Badf)
1531 .cloned();
1532
1533 if ret.is_err() && fd == VIRTUAL_ROOT_FD {
1534 Ok(Fd {
1535 inner: FdInner {
1536 rights: ALL_RIGHTS,
1537 rights_inheriting: ALL_RIGHTS,
1538 flags: Fdflags::empty(),
1539 offset: Arc::new(AtomicU64::new(0)),
1540 fd_flags: Fdflagsext::empty(),
1541 },
1542 open_flags: 0,
1543 inode: self.root_inode.clone(),
1544 is_stdio: false,
1545 })
1546 } else {
1547 ret
1548 }
1549 }
1550
1551 pub fn get_fd_inode(&self, fd: WasiFd) -> Result<InodeGuard, Errno> {
1552 if fd == VIRTUAL_ROOT_FD {
1554 return Ok(self.root_inode.clone());
1555 }
1556 self.fd_map
1557 .read()
1558 .unwrap()
1559 .get(fd)
1560 .ok_or(Errno::Badf)
1561 .map(|a| a.inode.clone())
1562 }
1563
1564 pub fn filestat_fd(&self, fd: WasiFd) -> Result<Filestat, Errno> {
1565 let inode = self.get_fd_inode(fd)?;
1566 let guard = inode.stat.read().unwrap();
1567 Ok(*guard.deref())
1568 }
1569
1570 pub fn fdstat(&self, fd: WasiFd) -> Result<Fdstat, Errno> {
1571 match fd {
1572 __WASI_STDIN_FILENO => {
1573 return Ok(Fdstat {
1574 fs_filetype: Filetype::CharacterDevice,
1575 fs_flags: Fdflags::empty(),
1576 fs_rights_base: STDIN_DEFAULT_RIGHTS,
1577 fs_rights_inheriting: Rights::empty(),
1578 });
1579 }
1580 __WASI_STDOUT_FILENO => {
1581 return Ok(Fdstat {
1582 fs_filetype: Filetype::CharacterDevice,
1583 fs_flags: Fdflags::APPEND,
1584 fs_rights_base: STDOUT_DEFAULT_RIGHTS,
1585 fs_rights_inheriting: Rights::empty(),
1586 });
1587 }
1588 __WASI_STDERR_FILENO => {
1589 return Ok(Fdstat {
1590 fs_filetype: Filetype::CharacterDevice,
1591 fs_flags: Fdflags::APPEND,
1592 fs_rights_base: STDERR_DEFAULT_RIGHTS,
1593 fs_rights_inheriting: Rights::empty(),
1594 });
1595 }
1596 VIRTUAL_ROOT_FD => {
1597 return Ok(Fdstat {
1598 fs_filetype: Filetype::Directory,
1599 fs_flags: Fdflags::empty(),
1600 fs_rights_base: ALL_RIGHTS,
1602 fs_rights_inheriting: ALL_RIGHTS,
1603 });
1604 }
1605 _ => (),
1606 }
1607 let fd = self.get_fd(fd)?;
1608
1609 let guard = fd.inode.read();
1610 let deref = guard.deref();
1611 Ok(Fdstat {
1612 fs_filetype: match deref {
1613 Kind::File { .. } => Filetype::RegularFile,
1614 Kind::Dir { .. } => Filetype::Directory,
1615 Kind::Symlink { .. } => Filetype::SymbolicLink,
1616 Kind::Socket { socket } => match &socket.inner.protected.read().unwrap().kind {
1617 InodeSocketKind::TcpStream { .. } => Filetype::SocketStream,
1618 InodeSocketKind::Raw { .. } => Filetype::SocketRaw,
1619 InodeSocketKind::PreSocket { props, .. } => match props.ty {
1620 Socktype::Stream => Filetype::SocketStream,
1621 Socktype::Dgram => Filetype::SocketDgram,
1622 Socktype::Raw => Filetype::SocketRaw,
1623 Socktype::Seqpacket => Filetype::SocketSeqpacket,
1624 _ => Filetype::Unknown,
1625 },
1626 _ => Filetype::Unknown,
1627 },
1628 _ => Filetype::Unknown,
1629 },
1630 fs_flags: fd.inner.flags,
1631 fs_rights_base: fd.inner.rights,
1632 fs_rights_inheriting: fd.inner.rights_inheriting, })
1634 }
1635
1636 pub fn prestat_fd(&self, fd: WasiFd) -> Result<Prestat, Errno> {
1637 let inode = self.get_fd_inode(fd)?;
1638 if inode.is_preopened {
1641 Ok(self.prestat_fd_inner(inode.deref()))
1642 } else {
1643 Err(Errno::Badf)
1644 }
1645 }
1646
1647 pub(crate) fn prestat_fd_inner(&self, inode_val: &InodeVal) -> Prestat {
1648 Prestat {
1649 pr_type: Preopentype::Dir,
1650 u: PrestatEnum::Dir {
1651 pr_name_len: inode_val.name.read().unwrap().len() as u32,
1653 }
1654 .untagged(),
1655 }
1656 }
1657
1658 #[allow(clippy::await_holding_lock)]
1659 pub async fn flush(&self, fd: WasiFd) -> Result<(), Errno> {
1660 match fd {
1661 __WASI_STDIN_FILENO => (),
1662 __WASI_STDOUT_FILENO => {
1663 let mut file =
1664 WasiInodes::stdout_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1665 file.flush().await.map_err(map_io_err)?
1666 }
1667 __WASI_STDERR_FILENO => {
1668 let mut file =
1669 WasiInodes::stderr_mut(&self.fd_map).map_err(fs_error_into_wasi_err)?;
1670 file.flush().await.map_err(map_io_err)?
1671 }
1672 _ => {
1673 let fd = self.get_fd(fd)?;
1674 if !fd.inner.rights.contains(Rights::FD_DATASYNC) {
1675 return Err(Errno::Access);
1676 }
1677
1678 let file = {
1679 let guard = fd.inode.read();
1680 match guard.deref() {
1681 Kind::File {
1682 handle: Some(file), ..
1683 } => file.clone(),
1684 Kind::Dir { .. } => return Err(Errno::Isdir),
1686 Kind::Buffer { .. } => return Ok(()),
1687 _ => return Err(Errno::Io),
1688 }
1689 };
1690 drop(fd);
1691
1692 struct FlushPoller {
1693 file: Arc<RwLock<Box<dyn VirtualFile + Send + Sync>>>,
1694 }
1695 impl Future for FlushPoller {
1696 type Output = Result<(), Errno>;
1697 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1698 let mut file = self.file.write().unwrap();
1699 Pin::new(file.as_mut())
1700 .poll_flush(cx)
1701 .map_err(|_| Errno::Io)
1702 }
1703 }
1704 FlushPoller { file }.await?;
1705 }
1706 }
1707 Ok(())
1708 }
1709
1710 pub(crate) fn create_inode(
1712 &self,
1713 inodes: &WasiInodes,
1714 kind: Kind,
1715 is_preopened: bool,
1716 name: String,
1717 ) -> Result<InodeGuard, Errno> {
1718 let stat = self.get_stat_for_kind(&kind)?;
1719 Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name.into(), stat))
1720 }
1721
1722 pub(crate) fn create_inode_with_default_stat(
1724 &self,
1725 inodes: &WasiInodes,
1726 kind: Kind,
1727 is_preopened: bool,
1728 name: Cow<'static, str>,
1729 ) -> InodeGuard {
1730 let stat = Filestat::default();
1731 self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)
1732 }
1733
1734 pub(crate) fn create_inode_with_stat(
1736 &self,
1737 inodes: &WasiInodes,
1738 kind: Kind,
1739 is_preopened: bool,
1740 name: Cow<'static, str>,
1741 mut stat: Filestat,
1742 ) -> InodeGuard {
1743 match &kind {
1744 Kind::File {
1745 handle: Some(handle),
1746 ..
1747 } => {
1748 let guard = handle.read().unwrap();
1749 stat.st_size = guard.size();
1750 }
1751 Kind::Buffer { buffer } => {
1752 stat.st_size = buffer.len() as u64;
1753 }
1754 _ => {}
1755 }
1756
1757 let inode_key: Cow<'_, str> = match &kind {
1758 Kind::File { path, .. } | Kind::Dir { path, .. } => {
1759 let path_str = path.to_string_lossy();
1760 if path_str.is_empty() {
1761 Cow::Borrowed(name.as_ref())
1762 } else {
1763 path_str
1764 }
1765 }
1766 Kind::Symlink {
1767 base_po_dir,
1768 path_to_symlink,
1769 ..
1770 } => {
1771 let path_str = path_to_symlink.to_string_lossy();
1772 if path_str.is_empty() {
1773 Cow::Owned(format!("{base_po_dir}:{}", name.as_ref()))
1774 } else {
1775 Cow::Owned(format!("{base_po_dir}:{path_str}"))
1776 }
1777 }
1778 _ => Cow::Borrowed(name.as_ref()),
1779 };
1780
1781 let st_ino = Inode::from_path(&inode_key);
1782 stat.st_ino = st_ino.as_u64();
1783
1784 inodes.add_inode_val(InodeVal {
1785 stat: RwLock::new(stat),
1786 is_preopened,
1787 name: RwLock::new(name),
1788 kind: RwLock::new(kind),
1789 })
1790 }
1791
1792 pub fn create_fd(
1793 &self,
1794 rights: Rights,
1795 rights_inheriting: Rights,
1796 fs_flags: Fdflags,
1797 fd_flags: Fdflagsext,
1798 open_flags: u16,
1799 inode: InodeGuard,
1800 ) -> Result<WasiFd, Errno> {
1801 self.create_fd_ext(
1802 rights,
1803 rights_inheriting,
1804 fs_flags,
1805 fd_flags,
1806 open_flags,
1807 inode,
1808 None,
1809 false,
1810 )
1811 }
1812
1813 #[allow(clippy::too_many_arguments)]
1814 pub fn with_fd(
1815 &self,
1816 rights: Rights,
1817 rights_inheriting: Rights,
1818 fs_flags: Fdflags,
1819 fd_flags: Fdflagsext,
1820 open_flags: u16,
1821 inode: InodeGuard,
1822 idx: WasiFd,
1823 ) -> Result<(), Errno> {
1824 self.create_fd_ext(
1825 rights,
1826 rights_inheriting,
1827 fs_flags,
1828 fd_flags,
1829 open_flags,
1830 inode,
1831 Some(idx),
1832 true,
1833 )?;
1834 Ok(())
1835 }
1836
1837 #[allow(clippy::too_many_arguments)]
1838 pub fn create_fd_ext(
1839 &self,
1840 rights: Rights,
1841 rights_inheriting: Rights,
1842 fs_flags: Fdflags,
1843 fd_flags: Fdflagsext,
1844 open_flags: u16,
1845 inode: InodeGuard,
1846 idx: Option<WasiFd>,
1847 exclusive: bool,
1848 ) -> Result<WasiFd, Errno> {
1849 let is_stdio = matches!(
1850 idx,
1851 Some(__WASI_STDIN_FILENO) | Some(__WASI_STDOUT_FILENO) | Some(__WASI_STDERR_FILENO)
1852 );
1853 let fd = Fd {
1854 inner: FdInner {
1855 rights,
1856 rights_inheriting,
1857 flags: fs_flags,
1858 offset: Arc::new(AtomicU64::new(0)),
1859 fd_flags,
1860 },
1861 open_flags,
1862 inode,
1863 is_stdio,
1864 };
1865
1866 let mut guard = self.fd_map.write().unwrap();
1867
1868 match idx {
1869 Some(idx) => {
1870 if idx > MAX_FD {
1871 return Err(Errno::Badf);
1872 }
1873 if guard.insert(exclusive, idx, fd) {
1874 Ok(idx)
1875 } else {
1876 Err(Errno::Exist)
1877 }
1878 }
1879 None => Ok(guard.insert_first_free(fd)),
1880 }
1881 }
1882
1883 pub fn clone_fd(&self, fd: WasiFd) -> Result<WasiFd, Errno> {
1884 self.clone_fd_ext(fd, 0, None)
1885 }
1886
1887 pub fn clone_fd_ext(
1888 &self,
1889 fd: WasiFd,
1890 min_result_fd: WasiFd,
1891 cloexec: Option<bool>,
1892 ) -> Result<WasiFd, Errno> {
1893 let fd = self.get_fd(fd)?;
1894 if min_result_fd > MAX_FD {
1895 return Err(Errno::Inval);
1896 }
1897 Ok(self.fd_map.write().unwrap().insert_first_free_after(
1898 Fd {
1899 inner: FdInner {
1900 rights: fd.inner.rights,
1901 rights_inheriting: fd.inner.rights_inheriting,
1902 flags: fd.inner.flags,
1903 offset: fd.inner.offset.clone(),
1904 fd_flags: match cloexec {
1905 None => fd.inner.fd_flags,
1906 Some(cloexec) => {
1907 let mut f = fd.inner.fd_flags;
1908 f.set(Fdflagsext::CLOEXEC, cloexec);
1909 f
1910 }
1911 },
1912 },
1913 open_flags: fd.open_flags,
1914 inode: fd.inode,
1915 is_stdio: fd.is_stdio,
1916 },
1917 min_result_fd,
1918 ))
1919 }
1920
1921 pub unsafe fn remove_inode(&self, inodes: &WasiInodes, ino: Inode) -> Option<Arc<InodeVal>> {
1930 let mut guard = inodes.protected.write().unwrap();
1931 guard.lookup.remove(&ino).and_then(|a| Weak::upgrade(&a))
1932 }
1933
1934 pub(crate) fn create_stdout(&self, inodes: &WasiInodes) {
1935 self.create_std_dev_inner(
1936 inodes,
1937 Box::<Stdout>::default(),
1938 "stdout",
1939 __WASI_STDOUT_FILENO,
1940 STDOUT_DEFAULT_RIGHTS,
1941 Fdflags::APPEND,
1942 FS_STDOUT_INO,
1943 );
1944 }
1945
1946 pub(crate) fn create_stdin(&self, inodes: &WasiInodes) {
1947 self.create_std_dev_inner(
1948 inodes,
1949 Box::<Stdin>::default(),
1950 "stdin",
1951 __WASI_STDIN_FILENO,
1952 STDIN_DEFAULT_RIGHTS,
1953 Fdflags::empty(),
1954 FS_STDIN_INO,
1955 );
1956 }
1957
1958 pub(crate) fn create_stderr(&self, inodes: &WasiInodes) {
1959 self.create_std_dev_inner(
1960 inodes,
1961 Box::<Stderr>::default(),
1962 "stderr",
1963 __WASI_STDERR_FILENO,
1964 STDERR_DEFAULT_RIGHTS,
1965 Fdflags::APPEND,
1966 FS_STDERR_INO,
1967 );
1968 }
1969
1970 pub(crate) fn create_rootfd(&self) -> Result<(), String> {
1971 let all_rights = ALL_RIGHTS;
1973 let root_rights = all_rights
1976 ;
1992 let fd = self
1993 .create_fd(
1994 root_rights,
1995 root_rights,
1996 Fdflags::empty(),
1997 Fdflagsext::empty(),
1998 Fd::READ,
1999 self.root_inode.clone(),
2000 )
2001 .map_err(|e| format!("Could not create root fd: {e}"))?;
2002 self.preopen_fds.write().unwrap().push(fd);
2003 Ok(())
2004 }
2005
2006 pub(crate) fn create_preopens(
2007 &self,
2008 inodes: &WasiInodes,
2009 ignore_duplicates: bool,
2010 ) -> Result<(), String> {
2011 for preopen_name in self.init_vfs_preopens.iter() {
2012 let kind = Kind::Dir {
2013 parent: self.root_inode.downgrade(),
2014 path: PathBuf::from(preopen_name),
2015 entries: Default::default(),
2016 };
2017 let rights = Rights::FD_ADVISE
2018 | Rights::FD_TELL
2019 | Rights::FD_SEEK
2020 | Rights::FD_READ
2021 | Rights::PATH_OPEN
2022 | Rights::FD_READDIR
2023 | Rights::PATH_READLINK
2024 | Rights::PATH_FILESTAT_GET
2025 | Rights::FD_FILESTAT_GET
2026 | Rights::PATH_LINK_SOURCE
2027 | Rights::PATH_RENAME_SOURCE
2028 | Rights::POLL_FD_READWRITE
2029 | Rights::SOCK_SHUTDOWN;
2030 let inode = self
2031 .create_inode(inodes, kind, true, preopen_name.clone())
2032 .map_err(|e| {
2033 format!(
2034 "Failed to create inode for preopened dir (name `{preopen_name}`): WASI error code: {e}",
2035 )
2036 })?;
2037 let fd_flags = Fd::READ;
2038 let fd = self
2039 .create_fd(
2040 rights,
2041 rights,
2042 Fdflags::empty(),
2043 Fdflagsext::empty(),
2044 fd_flags,
2045 inode.clone(),
2046 )
2047 .map_err(|e| format!("Could not open fd for file {preopen_name:?}: {e}"))?;
2048 {
2049 let mut guard = self.root_inode.write();
2050 if let Kind::Root { entries } = guard.deref_mut() {
2051 let existing_entry = entries.insert(preopen_name.clone(), inode);
2052 if existing_entry.is_some() && !ignore_duplicates {
2053 return Err(format!("Found duplicate entry for alias `{preopen_name}`"));
2054 }
2055 }
2056 }
2057 self.preopen_fds.write().unwrap().push(fd);
2058 }
2059
2060 for PreopenedDir {
2061 path,
2062 alias,
2063 read,
2064 write,
2065 create,
2066 } in self.init_preopens.iter()
2067 {
2068 debug!(
2069 "Attempting to preopen {} with alias {:?}",
2070 &path.to_string_lossy(),
2071 &alias
2072 );
2073 let cur_dir_metadata = self
2074 .root_fs
2075 .metadata(path)
2076 .map_err(|e| format!("Could not get metadata for file {path:?}: {e}"))?;
2077
2078 let kind = if cur_dir_metadata.is_dir() {
2079 Kind::Dir {
2080 parent: self.root_inode.downgrade(),
2081 path: path.clone(),
2082 entries: Default::default(),
2083 }
2084 } else {
2085 return Err(format!(
2086 "WASI only supports pre-opened directories right now; found \"{}\"",
2087 &path.to_string_lossy()
2088 ));
2089 };
2090
2091 let rights = {
2092 let mut rights = Rights::FD_ADVISE | Rights::FD_TELL | Rights::FD_SEEK;
2094 if *read {
2095 rights |= Rights::FD_READ
2096 | Rights::PATH_OPEN
2097 | Rights::FD_READDIR
2098 | Rights::PATH_READLINK
2099 | Rights::PATH_FILESTAT_GET
2100 | Rights::FD_FILESTAT_GET
2101 | Rights::PATH_LINK_SOURCE
2102 | Rights::PATH_RENAME_SOURCE
2103 | Rights::POLL_FD_READWRITE
2104 | Rights::SOCK_SHUTDOWN;
2105 }
2106 if *write {
2107 rights |= Rights::FD_DATASYNC
2108 | Rights::FD_FDSTAT_SET_FLAGS
2109 | Rights::FD_WRITE
2110 | Rights::FD_SYNC
2111 | Rights::FD_ALLOCATE
2112 | Rights::PATH_OPEN
2113 | Rights::PATH_RENAME_TARGET
2114 | Rights::PATH_FILESTAT_SET_SIZE
2115 | Rights::PATH_FILESTAT_SET_TIMES
2116 | Rights::FD_FILESTAT_SET_SIZE
2117 | Rights::FD_FILESTAT_SET_TIMES
2118 | Rights::PATH_REMOVE_DIRECTORY
2119 | Rights::PATH_UNLINK_FILE
2120 | Rights::POLL_FD_READWRITE
2121 | Rights::SOCK_SHUTDOWN;
2122 }
2123 if *create {
2124 rights |= Rights::PATH_CREATE_DIRECTORY
2125 | Rights::PATH_CREATE_FILE
2126 | Rights::PATH_LINK_TARGET
2127 | Rights::PATH_OPEN
2128 | Rights::PATH_RENAME_TARGET
2129 | Rights::PATH_SYMLINK;
2130 }
2131
2132 rights
2133 };
2134 let inode = if let Some(alias) = &alias {
2135 self.create_inode(inodes, kind, true, alias.clone())
2136 } else {
2137 self.create_inode(inodes, kind, true, path.to_string_lossy().into_owned())
2138 }
2139 .map_err(|e| {
2140 format!("Failed to create inode for preopened dir: WASI error code: {e}")
2141 })?;
2142 let fd_flags = {
2143 let mut fd_flags = 0;
2144 if *read {
2145 fd_flags |= Fd::READ;
2146 }
2147 if *write {
2148 fd_flags |= Fd::WRITE | Fd::APPEND | Fd::TRUNCATE;
2150 }
2151 if *create {
2152 fd_flags |= Fd::CREATE;
2153 }
2154 fd_flags
2155 };
2156 let fd = self
2157 .create_fd(
2158 rights,
2159 rights,
2160 Fdflags::empty(),
2161 Fdflagsext::empty(),
2162 fd_flags,
2163 inode.clone(),
2164 )
2165 .map_err(|e| format!("Could not open fd for file {path:?}: {e}"))?;
2166 {
2167 let mut guard = self.root_inode.write();
2168 if let Kind::Root { entries } = guard.deref_mut() {
2169 let key = if let Some(alias) = &alias {
2170 alias.clone()
2171 } else {
2172 path.to_string_lossy().into_owned()
2173 };
2174 let existing_entry = entries.insert(key.clone(), inode);
2175 if existing_entry.is_some() && !ignore_duplicates {
2176 return Err(format!("Found duplicate entry for alias `{key}`"));
2177 }
2178 }
2179 }
2180 self.preopen_fds.write().unwrap().push(fd);
2181 }
2182
2183 Ok(())
2184 }
2185
2186 #[allow(clippy::too_many_arguments)]
2187 pub(crate) fn create_std_dev_inner(
2188 &self,
2189 inodes: &WasiInodes,
2190 handle: Box<dyn VirtualFile + Send + Sync + 'static>,
2191 name: &'static str,
2192 raw_fd: WasiFd,
2193 rights: Rights,
2194 fd_flags: Fdflags,
2195 st_ino: Inode,
2196 ) {
2197 let inode = {
2198 let stat = Filestat {
2199 st_filetype: Filetype::CharacterDevice,
2200 st_ino: st_ino.as_u64(),
2201 ..Filestat::default()
2202 };
2203 let kind = Kind::File {
2204 fd: Some(raw_fd),
2205 handle: Some(Arc::new(RwLock::new(handle))),
2206 path: "".into(),
2207 };
2208 inodes.add_inode_val(InodeVal {
2209 stat: RwLock::new(stat),
2210 is_preopened: true,
2211 name: RwLock::new(name.to_string().into()),
2212 kind: RwLock::new(kind),
2213 })
2214 };
2215 self.fd_map.write().unwrap().insert(
2216 false,
2217 raw_fd,
2218 Fd {
2219 inner: FdInner {
2220 rights,
2221 rights_inheriting: Rights::empty(),
2222 flags: fd_flags,
2223 offset: Arc::new(AtomicU64::new(0)),
2224 fd_flags: Fdflagsext::empty(),
2225 },
2226 open_flags: 0,
2228 inode,
2229 is_stdio: true,
2230 },
2231 );
2232 }
2233
2234 pub fn get_stat_for_kind(&self, kind: &Kind) -> Result<Filestat, Errno> {
2235 let md = match kind {
2236 Kind::File { handle, path, .. } => match handle {
2237 Some(wf) => {
2238 let wf = wf.read().unwrap();
2239 return Ok(Filestat {
2240 st_filetype: Filetype::RegularFile,
2241 st_ino: Inode::from_path(path.to_string_lossy().as_ref()).as_u64(),
2242 st_size: wf.size(),
2243 st_atim: wf.last_accessed(),
2244 st_mtim: wf.last_modified(),
2245 st_ctim: wf.created_time(),
2246
2247 ..Filestat::default()
2248 });
2249 }
2250 None => self
2251 .root_fs
2252 .metadata(path)
2253 .map_err(fs_error_into_wasi_err)?,
2254 },
2255 Kind::Dir { path, .. } => self
2256 .root_fs
2257 .metadata(path)
2258 .map_err(fs_error_into_wasi_err)?,
2259 Kind::Symlink {
2260 base_po_dir,
2261 path_to_symlink,
2262 ..
2263 } => {
2264 let guard = self.fd_map.read().unwrap();
2265 let base_po_inode = &guard.get(*base_po_dir).unwrap().inode;
2266 let guard = base_po_inode.read();
2267 match guard.deref() {
2268 Kind::Root { .. } => self
2269 .root_fs
2270 .symlink_metadata(path_to_symlink)
2271 .map_err(fs_error_into_wasi_err)?,
2272 Kind::Dir { path, .. } => {
2273 let mut real_path = path.clone();
2274 real_path.push(path_to_symlink);
2281 self.root_fs
2282 .symlink_metadata(&real_path)
2283 .map_err(fs_error_into_wasi_err)?
2284 }
2285 _ => unreachable!(
2287 "Symlink pointing to something that's not a directory as its base preopened directory"
2288 ),
2289 }
2290 }
2291 _ => return Err(Errno::Io),
2292 };
2293 Ok(Filestat {
2294 st_filetype: virtual_file_type_to_wasi_file_type(md.file_type()),
2295 st_size: md.len(),
2296 st_atim: md.accessed(),
2297 st_mtim: md.modified(),
2298 st_ctim: md.created(),
2299 ..Filestat::default()
2300 })
2301 }
2302
2303 pub(crate) fn close_fd(&self, fd: WasiFd) -> Result<(), Errno> {
2305 let mut fd_map = self.fd_map.write().unwrap();
2306
2307 let pfd = fd_map.remove(fd).ok_or(Errno::Badf);
2308 match pfd {
2309 Ok(fd_ref) => {
2310 let inode = fd_ref.inode.ino().as_u64();
2311 let ref_cnt = fd_ref.inode.ref_cnt();
2312 if ref_cnt == 1 {
2313 trace!(%fd, %inode, %ref_cnt, "closing file descriptor");
2314 } else {
2315 trace!(%fd, %inode, %ref_cnt, "weakening file descriptor");
2316 }
2317 }
2318 Err(err) => {
2319 trace!(%fd, "closing file descriptor failed - {}", err);
2320 }
2321 }
2322 Ok(())
2323 }
2324}
2325
2326impl std::fmt::Debug for WasiFs {
2327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2328 if let Ok(guard) = self.current_dir.try_lock() {
2329 write!(f, "current_dir={} ", guard.as_str())?;
2330 } else {
2331 write!(f, "current_dir=(locked) ")?;
2332 }
2333 if let Ok(guard) = self.fd_map.read() {
2334 write!(
2335 f,
2336 "next_fd={} max_fd={:?} ",
2337 guard.next_free_fd(),
2338 guard.last_fd()
2339 )?;
2340 } else {
2341 write!(f, "next_fd=(locked) max_fd=(locked) ")?;
2342 }
2343 write!(f, "{:?}", self.root_fs)
2344 }
2345}
2346
2347pub fn default_fs_backing() -> Arc<dyn virtual_fs::FileSystem + Send + Sync> {
2349 cfg_if::cfg_if! {
2350 if #[cfg(feature = "host-fs")] {
2351 Arc::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
2352 } else if #[cfg(not(feature = "host-fs"))] {
2353 Arc::<virtual_fs::mem_fs::FileSystem>::default()
2354 } else {
2355 Arc::<FallbackFileSystem>::default()
2356 }
2357 }
2358}
2359
2360#[derive(Debug, Default)]
2361pub struct FallbackFileSystem;
2362
2363impl FallbackFileSystem {
2364 fn fail() -> ! {
2365 panic!(
2366 "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`"
2367 );
2368 }
2369}
2370
2371impl FileSystem for FallbackFileSystem {
2372 fn readlink(&self, _path: &Path) -> virtual_fs::Result<PathBuf> {
2373 Self::fail()
2374 }
2375 fn read_dir(&self, _path: &Path) -> Result<virtual_fs::ReadDir, FsError> {
2376 Self::fail();
2377 }
2378 fn create_dir(&self, _path: &Path) -> Result<(), FsError> {
2379 Self::fail();
2380 }
2381 fn remove_dir(&self, _path: &Path) -> Result<(), FsError> {
2382 Self::fail();
2383 }
2384 fn rename<'a>(&'a self, _from: &Path, _to: &Path) -> BoxFuture<'a, Result<(), FsError>> {
2385 Self::fail();
2386 }
2387 fn metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2388 Self::fail();
2389 }
2390 fn symlink_metadata(&self, _path: &Path) -> Result<virtual_fs::Metadata, FsError> {
2391 Self::fail();
2392 }
2393 fn remove_file(&self, _path: &Path) -> Result<(), FsError> {
2394 Self::fail();
2395 }
2396 fn new_open_options(&self) -> virtual_fs::OpenOptions<'_> {
2397 Self::fail();
2398 }
2399}
2400
2401pub fn virtual_file_type_to_wasi_file_type(file_type: virtual_fs::FileType) -> Filetype {
2402 if file_type.is_dir() {
2404 Filetype::Directory
2405 } else if file_type.is_file() {
2406 Filetype::RegularFile
2407 } else if file_type.is_symlink() {
2408 Filetype::SymbolicLink
2409 } else {
2410 Filetype::Unknown
2411 }
2412}
2413
2414pub fn fs_error_from_wasi_err(err: Errno) -> FsError {
2415 match err {
2416 Errno::Badf => FsError::InvalidFd,
2417 Errno::Exist => FsError::AlreadyExists,
2418 Errno::Io => FsError::IOError,
2419 Errno::Addrinuse => FsError::AddressInUse,
2420 Errno::Addrnotavail => FsError::AddressNotAvailable,
2421 Errno::Pipe => FsError::BrokenPipe,
2422 Errno::Connaborted => FsError::ConnectionAborted,
2423 Errno::Connrefused => FsError::ConnectionRefused,
2424 Errno::Connreset => FsError::ConnectionReset,
2425 Errno::Intr => FsError::Interrupted,
2426 Errno::Inval => FsError::InvalidInput,
2427 Errno::Notconn => FsError::NotConnected,
2428 Errno::Nodev => FsError::NoDevice,
2429 Errno::Noent => FsError::EntryNotFound,
2430 Errno::Perm => FsError::PermissionDenied,
2431 Errno::Timedout => FsError::TimedOut,
2432 Errno::Proto => FsError::UnexpectedEof,
2433 Errno::Again => FsError::WouldBlock,
2434 Errno::Nospc => FsError::WriteZero,
2435 Errno::Notempty => FsError::DirectoryNotEmpty,
2436 _ => FsError::UnknownError,
2437 }
2438}
2439
2440pub fn fs_error_into_wasi_err(fs_error: FsError) -> Errno {
2441 match fs_error {
2442 FsError::AlreadyExists => Errno::Exist,
2443 FsError::AddressInUse => Errno::Addrinuse,
2444 FsError::AddressNotAvailable => Errno::Addrnotavail,
2445 FsError::BaseNotDirectory => Errno::Notdir,
2446 FsError::BrokenPipe => Errno::Pipe,
2447 FsError::ConnectionAborted => Errno::Connaborted,
2448 FsError::ConnectionRefused => Errno::Connrefused,
2449 FsError::ConnectionReset => Errno::Connreset,
2450 FsError::Interrupted => Errno::Intr,
2451 FsError::InvalidData => Errno::Io,
2452 FsError::InvalidFd => Errno::Badf,
2453 FsError::InvalidInput => Errno::Inval,
2454 FsError::IOError => Errno::Io,
2455 FsError::NoDevice => Errno::Nodev,
2456 FsError::NotAFile => Errno::Inval,
2457 FsError::NotConnected => Errno::Notconn,
2458 FsError::EntryNotFound => Errno::Noent,
2459 FsError::PermissionDenied => Errno::Perm,
2460 FsError::TimedOut => Errno::Timedout,
2461 FsError::UnexpectedEof => Errno::Proto,
2462 FsError::WouldBlock => Errno::Again,
2463 FsError::WriteZero => Errno::Nospc,
2464 FsError::DirectoryNotEmpty => Errno::Notempty,
2465 FsError::StorageFull => Errno::Overflow,
2466 FsError::Lock | FsError::UnknownError => Errno::Io,
2467 FsError::Unsupported => Errno::Notsup,
2468 }
2469}
2470
2471#[cfg(test)]
2472mod tests {
2473 use super::*;
2474 use once_cell::sync::OnceCell;
2475 use tempfile::tempdir;
2476 use virtual_fs::{RootFileSystemBuilder, TmpFileSystem};
2477 use wasmer::Engine;
2478 use wasmer_config::package::PackageId;
2479
2480 use crate::WasiEnvBuilder;
2481 use crate::bin_factory::{BinaryPackage, BinaryPackageMount, BinaryPackageMounts};
2482
2483 #[tokio::test]
2484 async fn test_relative_path_to_absolute() {
2485 let inodes = WasiInodes::new();
2486 let fs_backing =
2487 WasiFsRoot::from_filesystem(Arc::new(RootFileSystemBuilder::default().build_tmp()));
2488 let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap();
2489
2490 assert_eq!(
2492 wasi_fs.relative_path_to_absolute("/foo/bar".to_string()),
2493 "/foo/bar"
2494 );
2495 assert_eq!(wasi_fs.relative_path_to_absolute("/".to_string()), "/");
2496
2497 assert_eq!(
2499 wasi_fs.relative_path_to_absolute("//foo//bar//".to_string()),
2500 "//foo//bar//"
2501 );
2502 assert_eq!(
2503 wasi_fs.relative_path_to_absolute("/a/b/./c".to_string()),
2504 "/a/b/./c"
2505 );
2506 assert_eq!(
2507 wasi_fs.relative_path_to_absolute("/a/b/../c".to_string()),
2508 "/a/b/../c"
2509 );
2510
2511 assert_eq!(
2513 wasi_fs.relative_path_to_absolute("foo/bar".to_string()),
2514 "/foo/bar"
2515 );
2516 assert_eq!(wasi_fs.relative_path_to_absolute("foo".to_string()), "/foo");
2517
2518 wasi_fs.set_current_dir("/home/user");
2520 assert_eq!(
2521 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2522 "/home/user/file.txt"
2523 );
2524 assert_eq!(
2525 wasi_fs.relative_path_to_absolute("dir/file.txt".to_string()),
2526 "/home/user/dir/file.txt"
2527 );
2528
2529 wasi_fs.set_current_dir("/a/b/c");
2531 assert_eq!(
2532 wasi_fs.relative_path_to_absolute("./file.txt".to_string()),
2533 "/a/b/c/./file.txt"
2534 );
2535 assert_eq!(
2536 wasi_fs.relative_path_to_absolute("../file.txt".to_string()),
2537 "/a/b/c/../file.txt"
2538 );
2539 assert_eq!(
2540 wasi_fs.relative_path_to_absolute("../../file.txt".to_string()),
2541 "/a/b/c/../../file.txt"
2542 );
2543
2544 assert_eq!(
2546 wasi_fs.relative_path_to_absolute(".".to_string()),
2547 "/a/b/c/."
2548 );
2549 assert_eq!(
2550 wasi_fs.relative_path_to_absolute("..".to_string()),
2551 "/a/b/c/.."
2552 );
2553 assert_eq!(wasi_fs.relative_path_to_absolute("".to_string()), "/a/b/c/");
2554
2555 wasi_fs.set_current_dir("/home/user/");
2557 assert_eq!(
2558 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2559 "/home/user/file.txt"
2560 );
2561
2562 wasi_fs.set_current_dir("/home/user");
2564 assert_eq!(
2565 wasi_fs.relative_path_to_absolute("file.txt".to_string()),
2566 "/home/user/file.txt"
2567 );
2568 }
2569
2570 #[cfg(feature = "host-fs")]
2571 #[tokio::test]
2572 async fn mapped_preopen_inode_paths_should_stay_in_guest_space() {
2573 let root_dir = tempdir().unwrap();
2574 let hamlet_dir = root_dir.path().join("hamlet");
2575 std::fs::create_dir_all(&hamlet_dir).unwrap();
2576
2577 let host_fs = virtual_fs::host_fs::FileSystem::new(
2578 tokio::runtime::Handle::current(),
2579 root_dir.path(),
2580 )
2581 .unwrap();
2582
2583 let init = WasiEnvBuilder::new("test_prog")
2584 .engine(Engine::default())
2585 .fs(Arc::new(host_fs) as Arc<dyn FileSystem + Send + Sync>)
2586 .map_dir("hamlet", "/hamlet")
2587 .unwrap()
2588 .build_init()
2589 .unwrap();
2590
2591 let preopen_inode = {
2592 let guard = init.state.fs.root_inode.read();
2593 let Kind::Root { entries } = guard.deref() else {
2594 panic!("expected root inode");
2595 };
2596 entries.get("hamlet").unwrap().clone()
2597 };
2598 let guard = preopen_inode.read();
2599
2600 let Kind::Dir { path, .. } = guard.deref() else {
2601 panic!("expected preopen inode to be a directory");
2602 };
2603
2604 assert_eq!(path, std::path::Path::new("/hamlet"));
2605 }
2606
2607 #[tokio::test]
2608 async fn dot_mapped_preopen_uses_guest_current_dir() {
2609 let init = WasiEnvBuilder::new("test_prog")
2610 .engine(Engine::default())
2611 .current_dir("/work")
2612 .map_dir(".", "/work")
2613 .unwrap()
2614 .build_init()
2615 .unwrap();
2616
2617 let preopen_inode = {
2618 let guard = init.state.fs.root_inode.read();
2619 let Kind::Root { entries } = guard.deref() else {
2620 panic!("expected root inode");
2621 };
2622 entries.get(".").unwrap().clone()
2623 };
2624 let guard = preopen_inode.read();
2625
2626 let Kind::Dir { path, .. } = guard.deref() else {
2627 panic!("expected preopen inode to be a directory");
2628 };
2629
2630 assert_eq!(path, std::path::Path::new("/work"));
2631 }
2632
2633 #[tokio::test]
2634 async fn writable_root_is_preserved_through_root_overlays() {
2635 let base_root = Arc::new(RootFileSystemBuilder::default().build_tmp());
2636 let root = WasiFsRoot::from_filesystem(base_root);
2637 assert!(root.writable_root().is_some());
2638
2639 let lower = Arc::new(TmpFileSystem::new()) as Arc<dyn FileSystem + Send + Sync>;
2640 root.stack_root_filesystem(lower).unwrap();
2641
2642 assert!(root.writable_root().is_some());
2643 }
2644
2645 #[tokio::test]
2646 async fn conditional_union_merges_root_and_non_root_package_mounts_once() {
2647 let inodes = WasiInodes::new();
2648 let fs_backing =
2649 WasiFsRoot::from_filesystem(Arc::new(RootFileSystemBuilder::default().build_tmp()));
2650 let wasi_fs = WasiFs::new_init(fs_backing, &inodes, FS_ROOT_INO).unwrap();
2651
2652 let root_layer = TmpFileSystem::new();
2653 root_layer
2654 .new_open_options()
2655 .create(true)
2656 .write(true)
2657 .open(Path::new("/root.txt"))
2658 .unwrap();
2659
2660 let public_mount = TmpFileSystem::new();
2661 public_mount
2662 .new_open_options()
2663 .create(true)
2664 .write(true)
2665 .open(Path::new("/index.html"))
2666 .unwrap();
2667
2668 let pkg = BinaryPackage {
2669 id: PackageId::new_named("ns/pkg", "0.1.0".parse().unwrap()),
2670 package_ids: vec![],
2671 when_cached: None,
2672 entrypoint_cmd: None,
2673 hash: OnceCell::new(),
2674 package_mounts: Some(Arc::new(BinaryPackageMounts {
2675 root_layer: Some(Arc::new(root_layer)),
2676 mounts: vec![BinaryPackageMount {
2677 guest_path: PathBuf::from("/public"),
2678 fs: Arc::new(public_mount),
2679 source_path: PathBuf::from("/"),
2680 }],
2681 })),
2682 commands: vec![],
2683 uses: vec![],
2684 file_system_memory_footprint: 0,
2685 additional_host_mapped_directories: vec![],
2686 };
2687
2688 wasi_fs.conditional_union(&pkg).await.unwrap();
2689 assert!(
2690 wasi_fs
2691 .root_fs
2692 .metadata(Path::new("/root.txt"))
2693 .unwrap()
2694 .is_file()
2695 );
2696 assert!(
2697 wasi_fs
2698 .root_fs
2699 .metadata(Path::new("/public/index.html"))
2700 .unwrap()
2701 .is_file()
2702 );
2703
2704 wasi_fs.conditional_union(&pkg).await.unwrap();
2705 assert!(
2706 wasi_fs
2707 .root_fs
2708 .metadata(Path::new("/root.txt"))
2709 .unwrap()
2710 .is_file()
2711 );
2712 assert!(
2713 wasi_fs
2714 .root_fs
2715 .metadata(Path::new("/public/index.html"))
2716 .unwrap()
2717 .is_file()
2718 );
2719 }
2720}