wasmer_wasix/fs/
mod.rs

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