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