Skip to main content

wasmtime_wasi/
filesystem.rs

1use crate::clocks::Datetime;
2use crate::filesystem::primitives::{FollowSymlinks, Metadata, OpenOptions};
3use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking};
4use crate::{NamedId, WasiCtxNamedView};
5use std::collections::hash_map;
6use std::marker;
7use std::num::NonZeroUsize;
8use std::sync::Arc;
9use std::time::SystemTime;
10use tracing::debug;
11use wasmtime::component::{HasData, Resource, ResourceTable};
12use wasmtime::error::Context as _;
13
14#[cfg(unix)]
15pub(crate) mod unix;
16#[cfg(unix)]
17pub(crate) use unix as sys;
18#[cfg(windows)]
19pub(crate) mod windows;
20#[cfg(windows)]
21pub(crate) use windows as sys;
22
23pub(crate) mod primitives;
24
25/// A helper struct which implements [`HasData`] for the `wasi:filesystem` APIs.
26///
27/// This can be useful when directly calling `add_to_linker` functions directly,
28/// such as [`wasmtime_wasi::p2::bindings::filesystem::types::add_to_linker`] as
29/// the `D` type parameter. See [`HasData`] for more information about the type
30/// parameter's purpose.
31///
32/// When using this type you can skip the [`WasiFilesystemView`] trait, for
33/// example.
34///
35/// [`wasmtime_wasi::p2::bindings::filesystem::types::add_to_linker`]: crate::p2::bindings::filesystem::types::add_to_linker
36///
37/// # Examples
38///
39/// ```
40/// use wasmtime::component::{Linker, ResourceTable};
41/// use wasmtime::{Engine, Result};
42/// use wasmtime_wasi::filesystem::*;
43///
44/// struct MyStoreState {
45///     table: ResourceTable,
46///     filesystem: WasiFilesystemCtx,
47/// }
48///
49/// fn main() -> Result<()> {
50///     let engine = Engine::default();
51///     let mut linker = Linker::new(&engine);
52///
53///     wasmtime_wasi::p2::bindings::filesystem::types::add_to_linker::<MyStoreState, WasiFilesystem>(
54///         &mut linker,
55///         |state| WasiFilesystemCtxView {
56///             table: &mut state.table,
57///             ctx: &mut state.filesystem,
58///         },
59///     )?;
60///     Ok(())
61/// }
62/// ```
63pub struct WasiFilesystem;
64
65impl HasData for WasiFilesystem {
66    type Data<'a> = WasiFilesystemCtxView<'a>;
67}
68
69#[derive(Clone)]
70pub struct WasiFilesystemCtx {
71    pub(crate) allow_blocking_current_thread: bool,
72    pub(crate) preopens: Vec<(Dir, String)>,
73    pub(crate) max_io_size: NonZeroUsize,
74}
75
76impl Default for WasiFilesystemCtx {
77    fn default() -> Self {
78        Self {
79            allow_blocking_current_thread: false,
80            preopens: Vec::new(),
81            max_io_size: crate::DEFAULT_MAX_IO_SIZE,
82        }
83    }
84}
85
86pub struct WasiFilesystemCtxView<'a> {
87    pub ctx: &'a mut WasiFilesystemCtx,
88    pub table: &'a mut ResourceTable,
89}
90
91pub trait WasiFilesystemView: Send {
92    fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>;
93}
94
95/// Permission bits for operating on filesystem, specified per preopen,
96/// as enforced by wasmtime-wasi.
97///
98/// Filesystems can deny all mutation operations (read-only) or permit
99/// mutations (read-write).
100///
101/// Read-only permissions allow reading the contents
102/// of any file or directory reachable under the preopen, as well as reading
103/// any file metadata. Changing, appending, or truncating files is not
104/// permitted. Creating or deleting files, directories, symbolic links, and
105/// hard links are not permitted.
106///
107/// Read-write permissions include changing the contents of any reachable
108/// file, creating and deleting files, directories, symbolic links, and
109/// creating hard links, as well as mutating any file metadata.
110///
111/// These permissions are enforced by wasmtime-wasi. The host filesystem may
112/// enforce additional restrictions not covered by these.
113#[derive(Copy, Clone, Debug, PartialEq, Eq)]
114pub enum FsPerms {
115    // Only read operations are permitted - no mutation permitted
116    ReadOnly,
117    // All operations are permitted.
118    ReadWrite,
119}
120
121impl FsPerms {
122    /// Tests whether writes are not permitted, returning a boolean. Shorthand
123    /// for matches!(perms, FsPerms::ReadOnly), used frequently in
124    /// if-statements.
125    pub fn write_not_permitted(&self) -> bool {
126        matches!(self, Self::ReadOnly)
127    }
128}
129
130bitflags::bitflags! {
131    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
132    pub struct OpenMode: usize {
133        const READ = 0b1;
134        const WRITE = 0b10;
135    }
136}
137
138bitflags::bitflags! {
139    /// Flags determining the method of how paths are resolved.
140    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
141    pub(crate) struct PathFlags: usize {
142        /// This directory can be read, for example its entries can be iterated
143        /// over and files can be opened.
144        const SYMLINK_FOLLOW = 0b1;
145    }
146}
147
148bitflags::bitflags! {
149    /// Open flags used by `open-at`.
150    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
151    pub(crate) struct OpenFlags: usize {
152        /// Create file if it does not exist, similar to `O_CREAT` in POSIX.
153        const CREATE = 0b1;
154        /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX.
155        const DIRECTORY = 0b10;
156        /// Fail if file already exists, similar to `O_EXCL` in POSIX.
157        const EXCLUSIVE = 0b100;
158        /// Truncate file to size 0, similar to `O_TRUNC` in POSIX.
159        const TRUNCATE = 0b1000;
160    }
161}
162
163bitflags::bitflags! {
164    /// Descriptor flags.
165    ///
166    /// Note: This was called `fdflags` in earlier versions of WASI.
167    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
168    pub(crate) struct DescriptorFlags: usize {
169        /// Read mode: Data can be read.
170        const READ = 0b1;
171        /// Write mode: Data can be written to.
172        const WRITE = 0b10;
173        /// Request that writes be performed according to synchronized I/O file
174        /// integrity completion. The data stored in the file and the file's
175        /// metadata are synchronized. This is similar to `O_SYNC` in POSIX.
176        ///
177        /// The precise semantics of this operation have not yet been defined for
178        /// WASI. At this time, it should be interpreted as a request, and not a
179        /// requirement.
180        const FILE_INTEGRITY_SYNC = 0b100;
181        /// Request that writes be performed according to synchronized I/O data
182        /// integrity completion. Only the data stored in the file is
183        /// synchronized. This is similar to `O_DSYNC` in POSIX.
184        ///
185        /// The precise semantics of this operation have not yet been defined for
186        /// WASI. At this time, it should be interpreted as a request, and not a
187        /// requirement.
188        const DATA_INTEGRITY_SYNC = 0b1000;
189        /// Requests that reads be performed at the same level of integrity
190        /// requested for writes. This is similar to `O_RSYNC` in POSIX.
191        ///
192        /// The precise semantics of this operation have not yet been defined for
193        /// WASI. At this time, it should be interpreted as a request, and not a
194        /// requirement.
195        const REQUESTED_WRITE_SYNC = 0b10000;
196        /// Mutating directories mode: Directory contents may be mutated.
197        ///
198        /// When this flag is unset on a descriptor, operations using the
199        /// descriptor which would create, rename, delete, modify the data or
200        /// metadata of filesystem objects, or obtain another handle which
201        /// would permit any of those, shall fail with `error-code::read-only` if
202        /// they would otherwise succeed.
203        ///
204        /// This may only be set on directories.
205        const MUTATE_DIRECTORY = 0b100000;
206    }
207}
208
209/// Error codes returned by functions, similar to `errno` in POSIX.
210/// Not all of these error codes are returned by the functions provided by this
211/// API; some are used in higher-level library layers, and others are provided
212/// merely for alignment with POSIX.
213#[cfg_attr(
214    windows,
215    expect(dead_code, reason = "on Windows, some of these are not used")
216)]
217pub(crate) enum ErrorCode {
218    /// Permission denied, similar to `EACCES` in POSIX.
219    Access,
220    /// Connection already in progress, similar to `EALREADY` in POSIX.
221    Already,
222    /// Bad descriptor, similar to `EBADF` in POSIX.
223    BadDescriptor,
224    /// Device or resource busy, similar to `EBUSY` in POSIX.
225    Busy,
226    /// File exists, similar to `EEXIST` in POSIX.
227    Exist,
228    /// File too large, similar to `EFBIG` in POSIX.
229    FileTooLarge,
230    /// Illegal byte sequence, similar to `EILSEQ` in POSIX.
231    IllegalByteSequence,
232    /// Operation in progress, similar to `EINPROGRESS` in POSIX.
233    InProgress,
234    /// Interrupted function, similar to `EINTR` in POSIX.
235    Interrupted,
236    /// Invalid argument, similar to `EINVAL` in POSIX.
237    Invalid,
238    /// I/O error, similar to `EIO` in POSIX.
239    Io,
240    /// Is a directory, similar to `EISDIR` in POSIX.
241    IsDirectory,
242    /// Too many levels of symbolic links, similar to `ELOOP` in POSIX.
243    Loop,
244    /// Too many links, similar to `EMLINK` in POSIX.
245    TooManyLinks,
246    /// Filename too long, similar to `ENAMETOOLONG` in POSIX.
247    NameTooLong,
248    /// No such file or directory, similar to `ENOENT` in POSIX.
249    NoEntry,
250    /// Not enough space, similar to `ENOMEM` in POSIX.
251    InsufficientMemory,
252    /// No space left on device, similar to `ENOSPC` in POSIX.
253    InsufficientSpace,
254    /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX.
255    NotDirectory,
256    /// Directory not empty, similar to `ENOTEMPTY` in POSIX.
257    NotEmpty,
258    /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX.
259    Unsupported,
260    /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX.
261    Overflow,
262    /// Operation not permitted, similar to `EPERM` in POSIX.
263    NotPermitted,
264    /// Broken pipe, similar to `EPIPE` in POSIX.
265    Pipe,
266    /// Invalid seek, similar to `ESPIPE` in POSIX.
267    InvalidSeek,
268}
269
270/// The type of a filesystem object referenced by a descriptor.
271///
272/// Note: This was called `filetype` in earlier versions of WASI.
273pub(crate) enum DescriptorType {
274    /// The type of the descriptor or file is unknown or is different from
275    /// any of the other types specified.
276    Unknown,
277    /// The descriptor refers to a block device inode.
278    #[cfg_attr(
279        windows,
280        expect(dead_code, reason = "windows has no notion of block devices")
281    )]
282    BlockDevice,
283    /// The descriptor refers to a character device inode.
284    CharacterDevice,
285    /// The descriptor refers to a directory inode.
286    Directory,
287    /// The file refers to a symbolic link inode.
288    SymbolicLink,
289    /// The descriptor refers to a regular file inode.
290    RegularFile,
291}
292
293impl From<crate::filesystem::primitives::FileType> for DescriptorType {
294    fn from(ft: crate::filesystem::primitives::FileType) -> Self {
295        if ft.is_dir() {
296            DescriptorType::Directory
297        } else if ft.is_symlink() {
298            DescriptorType::SymbolicLink
299        } else if ft.is_file() {
300            DescriptorType::RegularFile
301        } else {
302            sys::descriptor_type(ft)
303        }
304    }
305}
306
307/// File attributes.
308///
309/// Note: This was called `filestat` in earlier versions of WASI.
310pub(crate) struct DescriptorStat {
311    /// File type.
312    pub type_: DescriptorType,
313    /// Number of hard links to the file.
314    pub link_count: u64,
315    /// For regular files, the file size in bytes. For symbolic links, the
316    /// length in bytes of the pathname contained in the symbolic link.
317    pub size: u64,
318    /// Last data access timestamp.
319    ///
320    /// If the `option` is none, the platform doesn't maintain an access
321    /// timestamp for this file.
322    pub data_access_timestamp: Option<Datetime>,
323    /// Last data modification timestamp.
324    ///
325    /// If the `option` is none, the platform doesn't maintain a
326    /// modification timestamp for this file.
327    pub data_modification_timestamp: Option<Datetime>,
328    /// Last file status-change timestamp.
329    ///
330    /// If the `option` is none, the platform doesn't maintain a
331    /// status-change timestamp for this file.
332    pub status_change_timestamp: Option<Datetime>,
333}
334
335impl DescriptorStat {
336    /// Creates a `DescriptorStat` from a `Metadata` plus the hard link
337    /// count.
338    fn new(meta: &Metadata, link_count: u64) -> Self {
339        Self {
340            type_: meta.file_type().into(),
341            link_count,
342            size: meta.len(),
343            data_access_timestamp: meta
344                .accessed()
345                .ok()
346                .and_then(|t| Datetime::try_from(t).ok()),
347            data_modification_timestamp: meta
348                .modified()
349                .ok()
350                .and_then(|t| Datetime::try_from(t).ok()),
351            status_change_timestamp: meta.created().ok().and_then(|t| Datetime::try_from(t).ok()),
352        }
353    }
354}
355
356/// A 128-bit hash value, split into parts because wasm doesn't have a
357/// 128-bit integer type.
358pub(crate) struct MetadataHashValue {
359    /// 64 bits of a 128-bit hash value.
360    pub lower: u64,
361    /// Another 64 bits of a 128-bit hash value.
362    pub upper: u64,
363}
364
365impl MetadataHashValue {
366    /// Creates a hash value from a file's unique identity, e.g. a
367    /// device/inode number pair.
368    fn new(identity: impl std::hash::Hash) -> Self {
369        // Without incurring any deps, std provides us with a 64 bit hash
370        // function:
371        use std::hash::Hasher as _;
372        // Note that this means that the metadata hash (which becomes a preview1 ino) may
373        // change when a different rustc release is used to build this host implementation:
374        let mut hasher = hash_map::DefaultHasher::new();
375        identity.hash(&mut hasher);
376        let lower = hasher.finish();
377        // MetadataHashValue has a pair of 64-bit members for representing a
378        // single 128-bit number. However, we only have 64 bits of entropy. To
379        // synthesize the upper 64 bits, lets xor the lower half with an arbitrary
380        // constant, in this case the 64 bit integer corresponding to the IEEE
381        // double representation of (a number as close as possible to) pi.
382        // This seems better than just repeating the same bits in the upper and
383        // lower parts outright, which could make folks wonder if the struct was
384        // mangled in the ABI, or worse yet, lead to consumers of this interface
385        // expecting them to be equal.
386        let upper = lower ^ 4614256656552045848u64;
387        Self { lower, upper }
388    }
389}
390
391#[derive(Copy, Clone, Debug)]
392pub(crate) enum Advice {
393    Normal,
394    Sequential,
395    Random,
396    WillNeed,
397    DontNeed,
398    NoReuse,
399}
400
401#[cfg(unix)]
402fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
403    use rustix::io::Errno as RustixErrno;
404    if err.is_none() {
405        return None;
406    }
407    Some(match RustixErrno::from_raw_os_error(err.unwrap()) {
408        RustixErrno::PIPE => ErrorCode::Pipe,
409        RustixErrno::PERM => ErrorCode::NotPermitted,
410        RustixErrno::NOENT => ErrorCode::NoEntry,
411        RustixErrno::NOMEM => ErrorCode::InsufficientMemory,
412        RustixErrno::IO => ErrorCode::Io,
413        RustixErrno::BADF => ErrorCode::BadDescriptor,
414        RustixErrno::BUSY => ErrorCode::Busy,
415        RustixErrno::ACCESS => ErrorCode::Access,
416        RustixErrno::NOTDIR => ErrorCode::NotDirectory,
417        RustixErrno::ISDIR => ErrorCode::IsDirectory,
418        RustixErrno::INVAL => ErrorCode::Invalid,
419        RustixErrno::EXIST => ErrorCode::Exist,
420        RustixErrno::FBIG => ErrorCode::FileTooLarge,
421        RustixErrno::NOSPC => ErrorCode::InsufficientSpace,
422        RustixErrno::SPIPE => ErrorCode::InvalidSeek,
423        RustixErrno::MLINK => ErrorCode::TooManyLinks,
424        RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,
425        RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,
426        RustixErrno::LOOP => ErrorCode::Loop,
427        RustixErrno::OVERFLOW => ErrorCode::Overflow,
428        RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,
429        RustixErrno::NOTSUP => ErrorCode::Unsupported,
430        RustixErrno::ALREADY => ErrorCode::Already,
431        RustixErrno::INPROGRESS => ErrorCode::InProgress,
432        RustixErrno::INTR => ErrorCode::Interrupted,
433
434        // On some platforms, these have the same value as other errno values.
435        #[allow(unreachable_patterns, reason = "see comment")]
436        RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,
437
438        _ => return None,
439    })
440}
441
442#[cfg(windows)]
443fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
444    use windows_sys::Win32::Foundation;
445    Some(match raw_os_error.map(|code| code as u32) {
446        Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,
447        Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,
448        Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,
449        Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,
450        Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,
451        Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,
452        Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,
453        Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,
454        Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,
455        Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,
456        Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,
457        Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,
458        Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,
459        Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,
460        Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,
461        Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,
462        Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,
463        Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,
464        Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,
465        Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,
466        Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,
467        Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,
468        _ => return None,
469    })
470}
471
472impl<'a> From<&'a std::io::Error> for ErrorCode {
473    fn from(err: &'a std::io::Error) -> ErrorCode {
474        match from_raw_os_error(err.raw_os_error()) {
475            Some(errno) => errno,
476            None => {
477                debug!("unknown raw os error: {err}");
478                match err.kind() {
479                    std::io::ErrorKind::NotFound => ErrorCode::NoEntry,
480                    std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,
481                    std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,
482                    std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,
483                    _ => ErrorCode::Io,
484                }
485            }
486        }
487    }
488}
489
490impl From<std::io::Error> for ErrorCode {
491    fn from(err: std::io::Error) -> ErrorCode {
492        ErrorCode::from(&err)
493    }
494}
495
496#[derive(Clone)]
497pub enum Descriptor {
498    File(File),
499    Dir(Dir),
500}
501
502impl Descriptor {
503    pub(crate) fn file(&self) -> Result<&File, ErrorCode> {
504        match self {
505            Descriptor::File(f) => Ok(f),
506            // File-only ops such as advise stay bad-descriptor on a dir
507            // (wasi-testsuite filesystem-advise). read-via-stream maps Dir
508            // to is-directory on its own.
509            Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor),
510        }
511    }
512
513    pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> {
514        match self {
515            Descriptor::Dir(d) => Ok(d),
516            Descriptor::File(_) => Err(ErrorCode::NotDirectory),
517        }
518    }
519
520    pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> {
521        match self {
522            Self::File(f) => {
523                match f.run_blocking(|f| f.sync_data()).await {
524                    Ok(()) => Ok(()),
525                    // On windows, `sync_data` uses `FileFlushBuffers` which fails with
526                    // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore
527                    // this error, for POSIX compatibility.
528                    #[cfg(windows)]
529                    Err(err)
530                        if err.raw_os_error()
531                            == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
532                    {
533                        Ok(())
534                    }
535                    Err(err) => Err(err.into()),
536                }
537            }
538            Self::Dir(d) => {
539                d.run_blocking(|d| {
540                    let d = crate::filesystem::primitives::open(
541                        d,
542                        std::path::Component::CurDir.as_ref(),
543                        OpenOptions::new().read(true),
544                    )?;
545                    d.sync_data()?;
546                    Ok(())
547                })
548                .await
549            }
550        }
551    }
552
553    pub(crate) async fn get_flags(&self) -> Result<DescriptorFlags, ErrorCode> {
554        match self {
555            Self::File(f) => {
556                let mut flags = f.run_blocking(|f| sys::get_flags(f)).await?;
557                if f.open_mode.contains(OpenMode::READ) {
558                    flags |= DescriptorFlags::READ;
559                }
560                if f.open_mode.contains(OpenMode::WRITE) {
561                    flags |= DescriptorFlags::WRITE;
562                }
563                Ok(flags)
564            }
565            Self::Dir(d) => {
566                let mut flags = d.run_blocking(|d| sys::get_flags(d)).await?;
567                if d.open_mode.contains(OpenMode::READ) {
568                    flags |= DescriptorFlags::READ;
569                }
570                if d.open_mode.contains(OpenMode::WRITE) {
571                    flags |= DescriptorFlags::MUTATE_DIRECTORY;
572                }
573                Ok(flags)
574            }
575        }
576    }
577
578    pub(crate) async fn get_type(&self) -> Result<DescriptorType, ErrorCode> {
579        match self {
580            Self::File(f) => {
581                let meta = f.run_blocking(|f| Metadata::from_file(f)).await?;
582                Ok(meta.file_type().into())
583            }
584            Self::Dir(_) => Ok(DescriptorType::Directory),
585        }
586    }
587
588    pub(crate) async fn set_times(
589        &self,
590        atim: Option<SystemTime>,
591        mtim: Option<SystemTime>,
592    ) -> Result<(), ErrorCode> {
593        match self {
594            Self::File(f) => {
595                if f.perms.write_not_permitted() {
596                    return Err(ErrorCode::NotPermitted);
597                }
598                f.run_blocking(move |f| {
599                    crate::filesystem::primitives::set_times_on_fd(f, atim, mtim)
600                })
601                .await?;
602                Ok(())
603            }
604            Self::Dir(d) => {
605                if d.perms.write_not_permitted() {
606                    return Err(ErrorCode::NotPermitted);
607                }
608                d.run_blocking(move |d| {
609                    crate::filesystem::primitives::set_times_on_fd(d, atim, mtim)
610                })
611                .await?;
612                Ok(())
613            }
614        }
615    }
616
617    pub(crate) async fn sync(&self) -> Result<(), ErrorCode> {
618        match self {
619            Self::File(f) => {
620                match f.run_blocking(|f| f.sync_all()).await {
621                    Ok(()) => Ok(()),
622                    // On windows, `sync_data` uses `FileFlushBuffers` which fails with
623                    // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore
624                    // this error, for POSIX compatibility.
625                    #[cfg(windows)]
626                    Err(err)
627                        if err.raw_os_error()
628                            == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) =>
629                    {
630                        Ok(())
631                    }
632                    Err(err) => Err(err.into()),
633                }
634            }
635            Self::Dir(d) => {
636                d.run_blocking(|d| {
637                    let d = crate::filesystem::primitives::open(
638                        d,
639                        std::path::Component::CurDir.as_ref(),
640                        OpenOptions::new().read(true),
641                    )?;
642                    d.sync_all()?;
643                    Ok(())
644                })
645                .await
646            }
647        }
648    }
649
650    pub(crate) async fn stat(&self) -> Result<DescriptorStat, ErrorCode> {
651        match self {
652            Self::File(f) => Ok(f.run_blocking(|f| sys::stat(f)).await?),
653            Self::Dir(d) => Ok(d.run_blocking(|f| sys::stat(f)).await?),
654        }
655    }
656
657    pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result<bool> {
658        // No permissions check on metadata: if opened, allowed to stat it
659        let other = match other {
660            Self::File(f) => Arc::clone(&f.file),
661            Self::Dir(d) => Arc::clone(&d.dir),
662        };
663        Ok(match self {
664            Self::File(f) => {
665                f.run_blocking(move |f| sys::is_same_file(f, &other))
666                    .await?
667            }
668            Self::Dir(d) => {
669                d.run_blocking(move |d| sys::is_same_file(d, &other))
670                    .await?
671            }
672        })
673    }
674
675    pub(crate) async fn metadata_hash(&self) -> Result<MetadataHashValue, ErrorCode> {
676        match self {
677            Self::File(f) => Ok(f.run_blocking(|f| sys::metadata_hash(f)).await?),
678            Self::Dir(d) => Ok(d.run_blocking(|d| sys::metadata_hash(d)).await?),
679        }
680    }
681}
682
683#[derive(Clone)]
684pub struct File {
685    /// The operating system File this struct is mediating access to.
686    ///
687    /// Wrapped in an Arc because the same underlying file is used for
688    /// implementing the stream types. A copy is also needed for
689    /// `spawn_blocking`.
690    pub file: Arc<std::fs::File>,
691    /// Permissions to enforce on access to the file. These permissions are
692    /// specified to the parent preopen by a user of the
693    /// `crate::WasiCtxBuilder`, and are enforced prior to any enforced by the
694    /// underlying operating system.
695    pub perms: FsPerms,
696    /// The mode the file was opened under: bits for reading, and writing.
697    /// Required to correctly report the DescriptorFlags, because
698    /// cap-primitives doesn't presently provide a cross-platform equivalent
699    /// of reading the oflags back out using fcntl.
700    pub open_mode: OpenMode,
701
702    allow_blocking_current_thread: bool,
703}
704
705impl File {
706    pub fn new(
707        file: std::fs::File,
708        perms: FsPerms,
709        open_mode: OpenMode,
710        allow_blocking_current_thread: bool,
711    ) -> Self {
712        Self {
713            file: Arc::new(file),
714            perms,
715            open_mode,
716            allow_blocking_current_thread,
717        }
718    }
719
720    /// Execute the blocking `body` function.
721    ///
722    /// Depending on how the WasiCtx was configured, the body may either be:
723    /// - Executed directly on the current thread. In this case the `async`
724    ///   signature of this method is effectively a lie and the returned
725    ///   Future will always be immediately Ready. Or:
726    /// - Spawned on a background thread using [`tokio::task::spawn_blocking`]
727    ///   and immediately awaited.
728    ///
729    /// Intentionally blocking the executor thread might seem unorthodox, but is
730    /// not actually a problem for specific workloads. See:
731    /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`]
732    /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973)
733    /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190)
734    pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
735    where
736        F: FnOnce(&std::fs::File) -> R + Send + 'static,
737        R: Send + 'static,
738    {
739        match self.as_blocking_file() {
740            Some(file) => body(file),
741            None => self.spawn_blocking(body).await,
742        }
743    }
744
745    pub(crate) fn spawn_blocking<F, R>(&self, body: F) -> AbortOnDropJoinHandle<R>
746    where
747        F: FnOnce(&std::fs::File) -> R + Send + 'static,
748        R: Send + 'static,
749    {
750        let f = self.file.clone();
751        spawn_blocking(move || body(&f))
752    }
753
754    /// Returns `Some` when the current thread is allowed to block in filesystem
755    /// operations, and otherwise returns `None` to indicate that
756    /// `spawn_blocking` must be used.
757    pub(crate) fn as_blocking_file(&self) -> Option<&std::fs::File> {
758        if self.allow_blocking_current_thread {
759            Some(&self.file)
760        } else {
761            None
762        }
763    }
764
765    /// Returns reference to the underlying [`std::fs::File`]
766    #[cfg(feature = "p3")]
767    pub(crate) fn as_file(&self) -> &Arc<std::fs::File> {
768        &self.file
769    }
770
771    pub(crate) async fn advise(
772        &self,
773        offset: u64,
774        len: u64,
775        advice: Advice,
776    ) -> Result<(), ErrorCode> {
777        self.run_blocking(move |f| sys::advise(f, offset, len, advice))
778            .await?;
779        Ok(())
780    }
781
782    pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> {
783        if self.perms.write_not_permitted() {
784            return Err(ErrorCode::NotPermitted);
785        }
786        self.run_blocking(move |f| f.set_len(size)).await?;
787        Ok(())
788    }
789}
790
791#[derive(Clone)]
792pub struct Dir {
793    /// The operating system file descriptor this struct is mediating access
794    /// to.
795    ///
796    /// This is a handle to a directory, and all paths accessed through this
797    /// struct are sandboxed to be within this directory via `cap-primitives`.
798    ///
799    /// Wrapped in an Arc because a copy is needed for `run_blocking`.
800    pub dir: Arc<std::fs::File>,
801    /// Permissions to enforce on access to the filesystem under this
802    /// directory are specified by a user of the `crate::WasiCtxBuilder`, and
803    /// are enforced prior to any enforced by the underlying operating system.
804    ///
805    /// These permissions are also enforced on any directories opened under
806    /// this directory.
807    pub perms: FsPerms,
808    /// The mode the directory was opened under: bits for reading, and writing.
809    /// Required to correctly report the DescriptorFlags, because
810    /// cap-primitives doesn't presently provide a cross-platform equivalent
811    /// of reading the oflags back out using fcntl.
812    pub open_mode: OpenMode,
813
814    pub(crate) allow_blocking_current_thread: bool,
815}
816
817impl Dir {
818    pub fn new(
819        dir: std::fs::File,
820        perms: FsPerms,
821        open_mode: OpenMode,
822        allow_blocking_current_thread: bool,
823    ) -> Self {
824        Dir {
825            dir: Arc::new(dir),
826            perms,
827            open_mode,
828            allow_blocking_current_thread,
829        }
830    }
831
832    /// Execute the blocking `body` function.
833    ///
834    /// Depending on how the WasiCtx was configured, the body may either be:
835    /// - Executed directly on the current thread. In this case the `async`
836    ///   signature of this method is effectively a lie and the returned
837    ///   Future will always be immediately Ready. Or:
838    /// - Spawned on a background thread using [`tokio::task::spawn_blocking`]
839    ///   and immediately awaited.
840    ///
841    /// Intentionally blocking the executor thread might seem unorthodox, but is
842    /// not actually a problem for specific workloads. See:
843    /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`]
844    /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973)
845    /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190)
846    pub(crate) async fn run_blocking<F, R>(&self, body: F) -> R
847    where
848        F: FnOnce(&std::fs::File) -> R + Send + 'static,
849        R: Send + 'static,
850    {
851        if self.allow_blocking_current_thread {
852            body(&self.dir)
853        } else {
854            let d = self.dir.clone();
855            spawn_blocking(move || body(&d)).await
856        }
857    }
858
859    /// Returns reference to the underlying directory handle.
860    #[cfg(feature = "p3")]
861    pub(crate) fn as_dir(&self) -> &Arc<std::fs::File> {
862        &self.dir
863    }
864
865    pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> {
866        if self.perms.write_not_permitted() {
867            return Err(ErrorCode::NotPermitted);
868        }
869        self.run_blocking(move |d| crate::filesystem::primitives::create_dir(d, path.as_ref()))
870            .await?;
871        Ok(())
872    }
873
874    pub(crate) async fn stat_at(
875        &self,
876        path_flags: PathFlags,
877        path: String,
878    ) -> Result<DescriptorStat, ErrorCode> {
879        let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
880            FollowSymlinks::Yes
881        } else {
882            FollowSymlinks::No
883        };
884        let ret = self
885            .run_blocking(move |d| sys::stat_at(d, path.as_ref(), follow))
886            .await?;
887        Ok(ret)
888    }
889
890    pub(crate) async fn set_times_at(
891        &self,
892        path_flags: PathFlags,
893        path: String,
894        atim: Option<SystemTime>,
895        mtim: Option<SystemTime>,
896    ) -> Result<(), ErrorCode> {
897        if self.perms.write_not_permitted() {
898            return Err(ErrorCode::NotPermitted);
899        }
900        if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
901            self.run_blocking(move |d| {
902                crate::filesystem::primitives::set_times(d, path.as_ref(), atim, mtim)
903            })
904            .await?;
905        } else {
906            self.run_blocking(move |d| {
907                crate::filesystem::primitives::set_times_nofollow(d, path.as_ref(), atim, mtim)
908            })
909            .await?;
910        }
911        Ok(())
912    }
913
914    pub(crate) async fn link_at(
915        &self,
916        old_path_flags: PathFlags,
917        old_path: String,
918        new_dir: &Self,
919        new_path: String,
920    ) -> Result<(), ErrorCode> {
921        if self.perms.write_not_permitted() {
922            return Err(ErrorCode::NotPermitted);
923        }
924        if new_dir.perms.write_not_permitted() {
925            return Err(ErrorCode::NotPermitted);
926        }
927        if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
928            return Err(ErrorCode::Invalid);
929        }
930        if self.perms != new_dir.perms {
931            return Err(ErrorCode::NotPermitted);
932        }
933        let new_dir_handle = Arc::clone(&new_dir.dir);
934        self.run_blocking(move |d| {
935            crate::filesystem::primitives::hard_link(
936                d,
937                old_path.as_ref(),
938                &new_dir_handle,
939                new_path.as_ref(),
940            )
941        })
942        .await?;
943        Ok(())
944    }
945
946    pub(crate) async fn open_at(
947        &self,
948        path_flags: PathFlags,
949        path: String,
950        oflags: OpenFlags,
951        flags: DescriptorFlags,
952        allow_blocking_current_thread: bool,
953    ) -> Result<Descriptor, ErrorCode> {
954        // Track whether we are creating file, for permission check:
955        let mut create = false;
956        // Track open mode, for permission check and recording in created descriptor:
957        let mut open_mode = OpenMode::empty();
958        // Construct the OpenOptions to give the OS:
959        let mut opts = OpenOptions::new();
960        sys::maybe_dir(&mut opts);
961
962        if oflags.contains(OpenFlags::CREATE) {
963            if oflags.contains(OpenFlags::EXCLUSIVE) {
964                opts.create_new(true);
965            } else {
966                opts.create(true);
967            }
968            create = true;
969            opts.write(true);
970            open_mode |= OpenMode::WRITE;
971        }
972
973        if oflags.contains(OpenFlags::TRUNCATE) {
974            opts.truncate(true).write(true);
975            open_mode |= OpenMode::WRITE;
976        }
977        if flags.contains(DescriptorFlags::READ) {
978            opts.read(true);
979            open_mode |= OpenMode::READ;
980        }
981        if flags.contains(DescriptorFlags::WRITE) {
982            opts.write(true);
983            open_mode |= OpenMode::WRITE;
984        } else {
985            // If not opened write, open read. This way the OS lets us open
986            // the file, but we can use perms to reject use of the file later.
987            opts.read(true);
988            open_mode |= OpenMode::READ;
989        }
990
991        if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
992            opts.follow(FollowSymlinks::Yes);
993        } else {
994            opts.follow(FollowSymlinks::No);
995        }
996
997        // These flags are not yet supported in cap-primitives:
998        if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC)
999            || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC)
1000            || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC)
1001        {
1002            return Err(ErrorCode::Unsupported);
1003        }
1004
1005        if oflags.contains(OpenFlags::DIRECTORY) {
1006            if oflags.contains(OpenFlags::CREATE)
1007                || oflags.contains(OpenFlags::EXCLUSIVE)
1008                || oflags.contains(OpenFlags::TRUNCATE)
1009            {
1010                return Err(ErrorCode::Invalid);
1011            }
1012        }
1013
1014        // Now enforce this WasiCtx's permissions before letting the OS have
1015        // its shot:
1016        if self.perms.write_not_permitted() {
1017            if create || open_mode.contains(OpenMode::WRITE) {
1018                return Err(ErrorCode::NotPermitted);
1019            }
1020        }
1021
1022        // Represents each possible outcome from the spawn_blocking operation.
1023        // This makes sure we don't have to give spawn_blocking any way to
1024        // manipulate the table.
1025        enum OpenResult {
1026            Dir(std::fs::File),
1027            File(std::fs::File),
1028            NotDir,
1029        }
1030
1031        let opened = self
1032            .run_blocking::<_, std::io::Result<OpenResult>>(move |d| {
1033                let opened = crate::filesystem::primitives::open(d, path.as_ref(), &opts)?;
1034                if Metadata::from_file(&opened)?.is_dir() {
1035                    Ok(OpenResult::Dir(opened))
1036                } else if oflags.contains(OpenFlags::DIRECTORY) {
1037                    Ok(OpenResult::NotDir)
1038                } else {
1039                    Ok(OpenResult::File(opened))
1040                }
1041            })
1042            .await?;
1043
1044        match opened {
1045            // Paper over a divergence between Windows and POSIX, where
1046            // POSIX returns EISDIR if you open a directory with the
1047            // WRITE flag: https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html#:~:text=EISDIR
1048            #[cfg(windows)]
1049            OpenResult::Dir(_) if flags.contains(DescriptorFlags::WRITE) => {
1050                Err(ErrorCode::IsDirectory)
1051            }
1052
1053            OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new(
1054                dir,
1055                self.perms,
1056                open_mode,
1057                allow_blocking_current_thread,
1058            ))),
1059
1060            OpenResult::File(file) => Ok(Descriptor::File(File::new(
1061                file,
1062                self.perms,
1063                open_mode,
1064                allow_blocking_current_thread,
1065            ))),
1066
1067            OpenResult::NotDir => Err(ErrorCode::NotDirectory),
1068        }
1069    }
1070
1071    pub(crate) async fn readlink_at(&self, path: String) -> Result<String, ErrorCode> {
1072        let link = self
1073            .run_blocking(move |d| crate::filesystem::primitives::read_link(d, path.as_ref()))
1074            .await?;
1075        link.into_os_string()
1076            .into_string()
1077            .or(Err(ErrorCode::IllegalByteSequence))
1078    }
1079
1080    pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> {
1081        if self.perms.write_not_permitted() {
1082            return Err(ErrorCode::NotPermitted);
1083        }
1084        self.run_blocking(move |d| crate::filesystem::primitives::remove_dir(d, path.as_ref()))
1085            .await?;
1086        Ok(())
1087    }
1088
1089    pub(crate) async fn rename_at(
1090        &self,
1091        old_path: String,
1092        new_dir: &Self,
1093        new_path: String,
1094    ) -> Result<(), ErrorCode> {
1095        if self.perms.write_not_permitted() {
1096            return Err(ErrorCode::NotPermitted);
1097        }
1098        if new_dir.perms.write_not_permitted() {
1099            return Err(ErrorCode::NotPermitted);
1100        }
1101        if self.perms != new_dir.perms {
1102            return Err(ErrorCode::NotPermitted);
1103        }
1104        let new_dir_handle = Arc::clone(&new_dir.dir);
1105        self.run_blocking(move |d| {
1106            crate::filesystem::primitives::rename(
1107                d,
1108                old_path.as_ref(),
1109                &new_dir_handle,
1110                new_path.as_ref(),
1111            )
1112        })
1113        .await?;
1114        Ok(())
1115    }
1116
1117    pub(crate) async fn symlink_at(
1118        &self,
1119        src_path: String,
1120        dest_path: String,
1121    ) -> Result<(), ErrorCode> {
1122        if self.perms.write_not_permitted() {
1123            return Err(ErrorCode::NotPermitted);
1124        }
1125        self.run_blocking(move |d| sys::symlink(src_path.as_ref(), d, dest_path.as_ref()))
1126            .await?;
1127        Ok(())
1128    }
1129
1130    pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> {
1131        if self.perms.write_not_permitted() {
1132            return Err(ErrorCode::NotPermitted);
1133        }
1134        self.run_blocking(move |d| sys::remove_file_or_symlink(d, path.as_ref()))
1135            .await?;
1136        Ok(())
1137    }
1138
1139    pub(crate) async fn metadata_hash_at(
1140        &self,
1141        path_flags: PathFlags,
1142        path: String,
1143    ) -> Result<MetadataHashValue, ErrorCode> {
1144        // No permissions check on metadata: if dir opened, allowed to stat it
1145        let follow = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) {
1146            FollowSymlinks::Yes
1147        } else {
1148            FollowSymlinks::No
1149        };
1150        let hash = self
1151            .run_blocking(move |d| sys::metadata_hash_at(d, path.as_ref(), follow))
1152            .await?;
1153        Ok(hash)
1154    }
1155}
1156
1157impl WasiFilesystemCtxView<'_> {
1158    pub(crate) fn get_directories(
1159        &mut self,
1160    ) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {
1161        let preopens = self.ctx.preopens.clone();
1162        let mut results = Vec::with_capacity(preopens.len());
1163        for (dir, name) in preopens {
1164            let fd = self
1165                .table
1166                .push(Descriptor::Dir(dir))
1167                .with_context(|| format!("failed to push preopen {name}"))?;
1168            results.push((fd, name));
1169        }
1170        Ok(results)
1171    }
1172}
1173
1174/// A helper struct which implements [`HasData`] for the `wasi:filesystem` APIs
1175/// when used in combination with named imports.
1176///
1177/// This structure is similar in purpose to [`WasiFilesystem`] and is used
1178/// when using the [`named_imports`] module for `wasi:filesystem`. This structure
1179/// serves as the `D` type parameter for `add_to_linker` functions.
1180///
1181/// [`named_imports`]: crate::p3::bindings::named_imports::wasi::filesystem
1182///
1183/// # Meaning of the `T` parameter
1184///
1185/// Here the `T` must be something that implements [`WasiFilesystemNamedView`]. The
1186/// corresponding `Data` for this type is [`WasiCtxNamedView`] which internally
1187/// will contain `&mut T`.
1188///
1189/// Effectively you're going to implement [`WasiFilesystemNamedView`] for something in
1190/// your embedding, and that's the `T` you'll fill in here.
1191///
1192/// # Examples
1193///
1194/// ```
1195/// use wasmtime::component::{Linker, Component, ResourceTable};
1196/// use wasmtime::{Engine, Result};
1197/// use wasmtime_wasi::{NamedId, WasiCtxNamedView};
1198/// use wasmtime_wasi::filesystem::*;
1199/// use wasmtime_wasi::p2::bindings::named_imports;
1200/// use std::collections::HashMap;
1201///
1202/// struct MyStoreState {
1203///     table: ResourceTable,
1204///     states: HashMap<NamedId, WasiFilesystemCtx>,
1205/// }
1206///
1207/// fn main() -> Result<()> {
1208///     let engine = Engine::default();
1209///     let mut linker = Linker::new(&engine);
1210///     let component = Component::new(&engine, "(component)")?;
1211///     let mut name_map = HashMap::new();
1212///
1213///     named_imports::wasi::filesystem::preopens::add_to_linker::<MyStoreState, WasiFilesystemNamed<MyStoreState>>(
1214///         &mut linker,
1215///         &component,
1216///         |name| {
1217///             let len = name_map.len();
1218///             Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
1219///         },
1220///         |state| WasiCtxNamedView(state),
1221///     )?;
1222///     Ok(())
1223/// }
1224///
1225/// impl WasiFilesystemNamedView for MyStoreState {
1226///     fn filesystem(&mut self, id: NamedId) -> WasiFilesystemCtxView<'_> {
1227///         let ctx = self.states.get_mut(&id).expect("state for id");
1228///         WasiFilesystemCtxView {
1229///             table: &mut self.table,
1230///             ctx,
1231///         }
1232///     }
1233/// }
1234/// ```
1235pub struct WasiFilesystemNamed<T>(marker::PhantomData<fn() -> T>);
1236
1237impl<T> HasData for WasiFilesystemNamed<T>
1238where
1239    T: WasiFilesystemNamedView,
1240{
1241    type Data<'a> = WasiCtxNamedView<'a, T>;
1242}
1243
1244/// A trait used to look up a specific `wasi:filesystem` context for a named
1245/// import.
1246///
1247/// This trait is used in conjunction with the [`named_imports`] bindings
1248/// generated for all WASI interfaces. The purpose of this trait is for
1249/// embedders to define how a [`NamedId`] maps to a particular `wasi:filesystem`
1250/// context, here returned as [`WasiFilesystemCtxView`]. Embedders are responsible
1251/// for assigning meaning to [`NamedId`] values themselves. These IDs are
1252/// assigned when [`add_named_to_linker`] is called, for example, as the
1253/// `lookup` argument to that function.
1254///
1255/// When using [`add_named_to_linker`] it's sufficient to implement this trait
1256/// for the `T` in `Store<T>`. You can also instead implement the
1257/// [`WasiNamedView`] trait for `T` which implies an implementation of this
1258/// trait.
1259///
1260/// When using `add_to_linker` in the generated `bindings::named_imports`
1261/// module then values implementing this live within the `T` of `Store<T>`, and
1262/// be temporarily referenced in [`WasiCtxNamedView`] where internally that'll
1263/// hold `WasiCtxNamedView(&mut your_type)`.
1264///
1265/// [`named_imports`]: crate::p3::bindings::named_imports
1266/// [`add_named_to_linker`]: crate::p3::filesystem::add_named_to_linker
1267/// [`WasiNamedView`]: crate::WasiNamedView
1268///
1269/// # Examples
1270///
1271/// ```
1272/// use wasmtime::component::{Linker, Component, ResourceTable};
1273/// use wasmtime::{Engine, Result};
1274/// use wasmtime_wasi::{NamedId, WasiCtxNamedView};
1275/// use wasmtime_wasi::filesystem::*;
1276/// use std::collections::HashMap;
1277///
1278/// struct MyStoreState {
1279///     table: ResourceTable,
1280///     states: HashMap<NamedId, WasiFilesystemCtx>,
1281/// }
1282///
1283/// fn main() -> Result<()> {
1284///     let engine = Engine::default();
1285///     let mut linker = Linker::new(&engine);
1286///     let component = Component::new(&engine, "(component)")?;
1287///     let mut name_map = HashMap::new();
1288///
1289///     wasmtime_wasi::p3::filesystem::add_named_to_linker::<MyStoreState>(
1290///         &mut linker,
1291///         &component,
1292///         |_, name| {
1293///             let len = name_map.len();
1294///             Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
1295///         },
1296///     )?;
1297///     Ok(())
1298/// }
1299///
1300/// impl WasiFilesystemNamedView for MyStoreState {
1301///     fn filesystem(&mut self, id: NamedId) -> WasiFilesystemCtxView<'_> {
1302///         let ctx = self.states.get_mut(&id).expect("state for id");
1303///         WasiFilesystemCtxView {
1304///             table: &mut self.table,
1305///             ctx,
1306///         }
1307///     }
1308/// }
1309/// ```
1310pub trait WasiFilesystemNamedView: Send + 'static {
1311    /// Looks up the [`WasiFilesystemCtxView`] for the given [`NamedId`].
1312    ///
1313    /// This method will resolve the `id` specified to a specific filesystem
1314    /// context that is available to be used. Note that this method is
1315    /// specifically infallible meaning that a filesystem context must be returned
1316    /// and this cannot generate a trap or panic or similar.
1317    ///
1318    /// Embedders are responsible for allocating [`NamedId`] and assigning
1319    /// meaning to ids. When a `Linker` is populated embedders will have the
1320    /// ability to generate a `NamedId` for all imports found, and then that
1321    /// embedder-allocated id is then passed back here when the corresponding
1322    /// imported function is invoked.
1323    ///
1324    /// It's strongly recommended, though not always required, that the
1325    /// [`ResourceTable`] in the returned [`WasiFilesystemCtxView`] is the same
1326    /// table that the default context, [`WasiFilesystemView::filesystem`],
1327    /// uses. Resources created through a named import can be used through
1328    /// interfaces bound to the default context (and vice versa), and with
1329    /// separate tables those lookups fail or find another context's resources.
1330    fn filesystem(&mut self, id: NamedId) -> WasiFilesystemCtxView<'_>;
1331}