wasmer_wasix/fs/
mod.rs

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