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