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