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