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}