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