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