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