wasmtime_wasi/
p1.rs

1//! Bindings for WASIp1 aka Preview 1 aka `wasi_snapshot_preview1`.
2//!
3//! This module contains runtime support for configuring and executing
4//! WASIp1-using core WebAssembly modules. Support for WASIp1 is built on top of
5//! support for WASIp2 available at [the crate root](crate), but that's just an
6//! internal implementation detail.
7//!
8//! Unlike the crate root, support for WASIp1 centers around two APIs:
9//!
10//! * [`WasiP1Ctx`]
11//! * [`add_to_linker_sync`] (or [`add_to_linker_async`])
12//!
13//! First a [`WasiCtxBuilder`] will be used and finalized with the [`build_p1`]
14//! method to create a [`WasiCtx`]. Next a [`wasmtime::Linker`] is configured
15//! with WASI imports by using the `add_to_linker_*` desired (sync or async
16//! depending on [`Config::async_support`]).
17//!
18//! Note that WASIp1 is not as extensible or configurable as WASIp2 so the
19//! support in this module is enough to run wasm modules but any customization
20//! beyond that [`WasiCtxBuilder`] already supports is not possible yet.
21//!
22//! [`WasiCtxBuilder`]: crate::p2::WasiCtxBuilder
23//! [`build_p1`]: crate::p2::WasiCtxBuilder::build_p1
24//! [`Config::async_support`]: wasmtime::Config::async_support
25//!
26//! # Components vs Modules
27//!
28//! Note that WASIp1 does not work for components at this time, only core wasm
29//! modules. That means this module is only for users of [`wasmtime::Module`]
30//! and [`wasmtime::Linker`], for example. If you're using
31//! [`wasmtime::component::Component`] or [`wasmtime::component::Linker`] you'll
32//! want the WASIp2 [support this crate has](crate) instead.
33//!
34//! # Examples
35//!
36//! ```no_run
37//! use wasmtime::{Result, Engine, Linker, Module, Store};
38//! use wasmtime_wasi::p1::{self, WasiP1Ctx};
39//! use wasmtime_wasi::WasiCtxBuilder;
40//!
41//! // An example of executing a WASIp1 "command"
42//! fn main() -> Result<()> {
43//!     let args = std::env::args().skip(1).collect::<Vec<_>>();
44//!     let engine = Engine::default();
45//!     let module = Module::from_file(&engine, &args[0])?;
46//!
47//!     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
48//!     p1::add_to_linker_async(&mut linker, |t| t)?;
49//!     let pre = linker.instantiate_pre(&module)?;
50//!
51//!     let wasi_ctx = WasiCtxBuilder::new()
52//!         .inherit_stdio()
53//!         .inherit_env()
54//!         .args(&args)
55//!         .build_p1();
56//!
57//!     let mut store = Store::new(&engine, wasi_ctx);
58//!     let instance = pre.instantiate(&mut store)?;
59//!     let func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
60//!     func.call(&mut store, ())?;
61//!
62//!     Ok(())
63//! }
64//! ```
65
66use crate::cli::WasiCliView as _;
67use crate::clocks::WasiClocksView as _;
68use crate::filesystem::WasiFilesystemView as _;
69use crate::p2::bindings::{
70    cli::{
71        stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output,
72        terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _,
73    },
74    clocks::{monotonic_clock, wall_clock},
75    filesystem::types as filesystem,
76};
77use crate::p2::{FsError, IsATTY};
78use crate::{ResourceTable, WasiCtx, WasiCtxView, WasiView};
79use anyhow::{Context, bail};
80use std::collections::{BTreeMap, BTreeSet, HashSet, btree_map};
81use std::mem::{self, size_of, size_of_val};
82use std::slice;
83use std::sync::Arc;
84use std::sync::atomic::{AtomicU64, Ordering};
85use system_interface::fs::FileIoExt;
86use wasmtime::component::Resource;
87use wasmtime_wasi_io::{
88    bindings::wasi::io::streams,
89    streams::{StreamError, StreamResult},
90};
91use wiggle::tracing::instrument;
92use wiggle::{GuestError, GuestMemory, GuestPtr, GuestType};
93
94// Bring all WASI traits in scope that this implementation builds on.
95use crate::p2::bindings::cli::environment::Host as _;
96use crate::p2::bindings::filesystem::types::HostDescriptor as _;
97use crate::p2::bindings::random::random::Host as _;
98use wasmtime_wasi_io::bindings::wasi::io::poll::Host as _;
99
100/// Structure containing state for WASIp1.
101///
102/// This structure is created through [`WasiCtxBuilder::build_p1`] and is
103/// configured through the various methods of [`WasiCtxBuilder`]. This structure
104/// itself implements generated traits for WASIp1 as well as [`WasiView`] to
105/// have access to WASIp2.
106///
107/// Instances of [`WasiP1Ctx`] are typically stored within the `T` of
108/// [`Store<T>`](wasmtime::Store).
109///
110/// [`WasiCtxBuilder::build_p1`]: crate::p2::WasiCtxBuilder::build_p1
111/// [`WasiCtxBuilder`]: crate::p2::WasiCtxBuilder
112///
113/// # Examples
114///
115/// ```no_run
116/// use wasmtime::{Result, Linker};
117/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
118/// use wasmtime_wasi::WasiCtxBuilder;
119///
120/// struct MyState {
121///     // ... custom state as necessary ...
122///
123///     wasi: WasiP1Ctx,
124/// }
125///
126/// impl MyState {
127///     fn new() -> MyState {
128///         MyState {
129///             // .. initialize custom state if needed ..
130///
131///             wasi: WasiCtxBuilder::new()
132///                 .arg("./foo.wasm")
133///                 // .. more customization if necessary ..
134///                 .build_p1(),
135///         }
136///     }
137/// }
138///
139/// fn add_to_linker(linker: &mut Linker<MyState>) -> Result<()> {
140///     p1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?;
141///     Ok(())
142/// }
143/// ```
144pub struct WasiP1Ctx {
145    table: ResourceTable,
146    wasi: WasiCtx,
147    adapter: WasiP1Adapter,
148}
149
150impl WasiP1Ctx {
151    pub(crate) fn new(wasi: WasiCtx) -> Self {
152        Self {
153            table: ResourceTable::new(),
154            wasi,
155            adapter: WasiP1Adapter::new(),
156        }
157    }
158}
159
160impl WasiView for WasiP1Ctx {
161    fn ctx(&mut self) -> WasiCtxView<'_> {
162        WasiCtxView {
163            ctx: &mut self.wasi,
164            table: &mut self.table,
165        }
166    }
167}
168
169#[derive(Debug)]
170struct File {
171    /// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`].
172    fd: Resource<filesystem::Descriptor>,
173
174    /// The current-position pointer.
175    position: Arc<AtomicU64>,
176
177    /// In append mode, all writes append to the file.
178    append: bool,
179
180    /// When blocking, read and write calls dispatch to blocking_read and
181    /// blocking_check_write on the underlying streams. When false, read and write
182    /// dispatch to stream's plain read and check_write.
183    blocking_mode: BlockingMode,
184}
185
186/// NB: p1 files always use blocking writes regardless of what
187/// they're configured to use since OSes don't have nonblocking
188/// reads/writes anyway. This behavior originated in the first
189/// implementation of WASIp1 where flags were propagated to the
190/// OS and the OS ignored the nonblocking flag for files
191/// generally.
192#[derive(Clone, Copy, Debug)]
193enum BlockingMode {
194    Blocking,
195    NonBlocking,
196}
197impl BlockingMode {
198    fn from_fdflags(flags: &types::Fdflags) -> Self {
199        if flags.contains(types::Fdflags::NONBLOCK) {
200            BlockingMode::NonBlocking
201        } else {
202            BlockingMode::Blocking
203        }
204    }
205    async fn read(
206        &self,
207        host: &mut impl streams::HostInputStream,
208        input_stream: Resource<streams::InputStream>,
209        max_size: usize,
210    ) -> Result<Vec<u8>, types::Error> {
211        let max_size = max_size.try_into().unwrap_or(u64::MAX);
212        match streams::HostInputStream::blocking_read(host, input_stream, max_size).await {
213            Ok(r) if r.is_empty() => Err(types::Errno::Intr.into()),
214            Ok(r) => Ok(r),
215            Err(StreamError::Closed) => Ok(Vec::new()),
216            Err(e) => Err(e.into()),
217        }
218    }
219    async fn write(
220        &self,
221        memory: &mut GuestMemory<'_>,
222        host: &mut impl streams::HostOutputStream,
223        output_stream: Resource<streams::OutputStream>,
224        bytes: GuestPtr<[u8]>,
225    ) -> StreamResult<usize> {
226        use streams::HostOutputStream as Streams;
227
228        let bytes = memory
229            .as_cow(bytes)
230            .map_err(|e| StreamError::Trap(e.into()))?;
231        let mut bytes = &bytes[..];
232
233        let total = bytes.len();
234        while !bytes.is_empty() {
235            // NOTE: blocking_write_and_flush takes at most one 4k buffer.
236            let len = bytes.len().min(4096);
237            let (chunk, rest) = bytes.split_at(len);
238            bytes = rest;
239
240            Streams::blocking_write_and_flush(host, output_stream.borrowed(), Vec::from(chunk))
241                .await?
242        }
243
244        Ok(total)
245    }
246}
247
248#[derive(Debug)]
249enum Descriptor {
250    Stdin {
251        stream: Resource<streams::InputStream>,
252        isatty: IsATTY,
253    },
254    Stdout {
255        stream: Resource<streams::OutputStream>,
256        isatty: IsATTY,
257    },
258    Stderr {
259        stream: Resource<streams::OutputStream>,
260        isatty: IsATTY,
261    },
262    /// A fd of type [`crate::filesystem::Descriptor::Dir`]
263    Directory {
264        fd: Resource<filesystem::Descriptor>,
265        /// The path this directory was preopened as.
266        /// `None` means this directory was opened using `open-at`.
267        preopen_path: Option<String>,
268    },
269    /// A fd of type [`crate::filesystem::Descriptor::File`]
270    File(File),
271}
272
273#[derive(Debug, Default)]
274struct WasiP1Adapter {
275    descriptors: Option<Descriptors>,
276}
277
278#[derive(Debug, Default)]
279struct Descriptors {
280    used: BTreeMap<u32, Descriptor>,
281    free: BTreeSet<u32>,
282}
283
284impl Descriptors {
285    /// Initializes [Self] using `preopens`
286    fn new(host: &mut WasiP1Ctx) -> Result<Self, types::Error> {
287        let mut descriptors = Self::default();
288        descriptors.push(Descriptor::Stdin {
289            stream: host
290                .cli()
291                .get_stdin()
292                .context("failed to call `get-stdin`")
293                .map_err(types::Error::trap)?,
294            isatty: if let Some(term_in) = host
295                .cli()
296                .get_terminal_stdin()
297                .context("failed to call `get-terminal-stdin`")
298                .map_err(types::Error::trap)?
299            {
300                terminal_input::HostTerminalInput::drop(&mut host.cli(), term_in)
301                    .context("failed to call `drop-terminal-input`")
302                    .map_err(types::Error::trap)?;
303                IsATTY::Yes
304            } else {
305                IsATTY::No
306            },
307        })?;
308        descriptors.push(Descriptor::Stdout {
309            stream: host
310                .cli()
311                .get_stdout()
312                .context("failed to call `get-stdout`")
313                .map_err(types::Error::trap)?,
314            isatty: if let Some(term_out) = host
315                .cli()
316                .get_terminal_stdout()
317                .context("failed to call `get-terminal-stdout`")
318                .map_err(types::Error::trap)?
319            {
320                terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out)
321                    .context("failed to call `drop-terminal-output`")
322                    .map_err(types::Error::trap)?;
323                IsATTY::Yes
324            } else {
325                IsATTY::No
326            },
327        })?;
328        descriptors.push(Descriptor::Stderr {
329            stream: host
330                .cli()
331                .get_stderr()
332                .context("failed to call `get-stderr`")
333                .map_err(types::Error::trap)?,
334            isatty: if let Some(term_out) = host
335                .cli()
336                .get_terminal_stderr()
337                .context("failed to call `get-terminal-stderr`")
338                .map_err(types::Error::trap)?
339            {
340                terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out)
341                    .context("failed to call `drop-terminal-output`")
342                    .map_err(types::Error::trap)?;
343                IsATTY::Yes
344            } else {
345                IsATTY::No
346            },
347        })?;
348
349        for dir in host
350            .filesystem()
351            .get_directories()
352            .context("failed to call `get-directories`")
353            .map_err(types::Error::trap)?
354        {
355            descriptors.push(Descriptor::Directory {
356                fd: dir.0,
357                preopen_path: Some(dir.1),
358            })?;
359        }
360        Ok(descriptors)
361    }
362
363    /// Returns next descriptor number, which was never assigned
364    fn unused(&self) -> Result<u32> {
365        match self.used.last_key_value() {
366            Some((fd, _)) => {
367                if let Some(fd) = fd.checked_add(1) {
368                    return Ok(fd);
369                }
370                if self.used.len() == u32::MAX as usize {
371                    return Err(types::Errno::Loop.into());
372                }
373                // TODO: Optimize
374                Ok((0..u32::MAX)
375                    .rev()
376                    .find(|fd| !self.used.contains_key(fd))
377                    .expect("failed to find an unused file descriptor"))
378            }
379            None => Ok(0),
380        }
381    }
382
383    /// Pushes the [Descriptor] returning corresponding number.
384    /// This operation will try to reuse numbers previously removed via [`Self::remove`]
385    /// and rely on [`Self::unused`] if no free numbers are recorded
386    fn push(&mut self, desc: Descriptor) -> Result<u32> {
387        let fd = if let Some(fd) = self.free.pop_last() {
388            fd
389        } else {
390            self.unused()?
391        };
392        assert!(self.used.insert(fd, desc).is_none());
393        Ok(fd)
394    }
395}
396
397impl WasiP1Adapter {
398    fn new() -> Self {
399        Self::default()
400    }
401}
402
403/// A mutably-borrowed `WasiP1Ctx`, which provides access to the stored
404/// state. It can be thought of as an in-flight [`WasiP1Adapter`] transaction, all
405/// changes will be recorded in the underlying [`WasiP1Adapter`] returned by
406/// [`WasiPreview1View::adapter_mut`] on [`Drop`] of this struct.
407// NOTE: This exists for the most part just due to the fact that `bindgen` generates methods with
408// `&mut self` receivers and so this struct lets us extend the lifetime of the `&mut self` borrow
409// of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`]
410// without having to rely on something like `Arc<Mutex<Descriptors>>`, while also being able to
411// call methods like [`Descriptor::is_file`] and hiding complexity from p1 method implementations.
412struct Transaction<'a> {
413    view: &'a mut WasiP1Ctx,
414    descriptors: Descriptors,
415}
416
417impl Drop for Transaction<'_> {
418    /// Record changes in the [`WasiP1Adapter`] .
419    fn drop(&mut self) {
420        let descriptors = mem::take(&mut self.descriptors);
421        self.view.adapter.descriptors = Some(descriptors);
422    }
423}
424
425impl Transaction<'_> {
426    /// Borrows [`Descriptor`] corresponding to `fd`.
427    ///
428    /// # Errors
429    ///
430    /// Returns [`types::Errno::Badf`] if no [`Descriptor`] is found
431    fn get_descriptor(&self, fd: types::Fd) -> Result<&Descriptor> {
432        let fd = fd.into();
433        let desc = self.descriptors.used.get(&fd).ok_or(types::Errno::Badf)?;
434        Ok(desc)
435    }
436
437    /// Borrows [`File`] corresponding to `fd`
438    /// if it describes a [`Descriptor::File`]
439    fn get_file(&self, fd: types::Fd) -> Result<&File> {
440        let fd = fd.into();
441        match self.descriptors.used.get(&fd) {
442            Some(Descriptor::File(file)) => Ok(file),
443            _ => Err(types::Errno::Badf.into()),
444        }
445    }
446
447    /// Mutably borrows [`File`] corresponding to `fd`
448    /// if it describes a [`Descriptor::File`]
449    fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> {
450        let fd = fd.into();
451        match self.descriptors.used.get_mut(&fd) {
452            Some(Descriptor::File(file)) => Ok(file),
453            _ => Err(types::Errno::Badf.into()),
454        }
455    }
456
457    /// Borrows [`File`] corresponding to `fd`
458    /// if it describes a [`Descriptor::File`]
459    ///
460    /// # Errors
461    ///
462    /// Returns [`types::Errno::Spipe`] if the descriptor corresponds to stdio
463    fn get_seekable(&self, fd: types::Fd) -> Result<&File> {
464        let fd = fd.into();
465        match self.descriptors.used.get(&fd) {
466            Some(Descriptor::File(file)) => Ok(file),
467            Some(
468                Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. },
469            ) => {
470                // NOTE: legacy implementation returns SPIPE here
471                Err(types::Errno::Spipe.into())
472            }
473            _ => Err(types::Errno::Badf.into()),
474        }
475    }
476
477    /// Returns [`filesystem::Descriptor`] corresponding to `fd`
478    fn get_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
479        match self.get_descriptor(fd)? {
480            Descriptor::File(File { fd, .. }) => Ok(fd.borrowed()),
481            Descriptor::Directory { fd, .. } => Ok(fd.borrowed()),
482            Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => {
483                Err(types::Errno::Badf.into())
484            }
485        }
486    }
487
488    /// Returns [`filesystem::Descriptor`] corresponding to `fd`
489    /// if it describes a [`Descriptor::File`]
490    fn get_file_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
491        self.get_file(fd).map(|File { fd, .. }| fd.borrowed())
492    }
493
494    /// Returns [`filesystem::Descriptor`] corresponding to `fd`
495    /// if it describes a [`Descriptor::Directory`]
496    fn get_dir_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
497        let fd = fd.into();
498        match self.descriptors.used.get(&fd) {
499            Some(Descriptor::Directory { fd, .. }) => Ok(fd.borrowed()),
500            _ => Err(types::Errno::Badf.into()),
501        }
502    }
503}
504
505impl WasiP1Ctx {
506    /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
507    /// and returns [`Transaction`] on success
508    fn transact(&mut self) -> Result<Transaction<'_>, types::Error> {
509        let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() {
510            descriptors
511        } else {
512            Descriptors::new(self)?
513        };
514        Ok(Transaction {
515            view: self,
516            descriptors,
517        })
518    }
519
520    /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
521    /// and returns [`filesystem::Descriptor`] corresponding to `fd`
522    fn get_fd(&mut self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>, types::Error> {
523        let st = self.transact()?;
524        let fd = st.get_fd(fd)?;
525        Ok(fd)
526    }
527
528    /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
529    /// and returns [`filesystem::Descriptor`] corresponding to `fd`
530    /// if it describes a [`Descriptor::File`] of [`crate::p2::filesystem::File`] type
531    fn get_file_fd(
532        &mut self,
533        fd: types::Fd,
534    ) -> Result<Resource<filesystem::Descriptor>, types::Error> {
535        let st = self.transact()?;
536        let fd = st.get_file_fd(fd)?;
537        Ok(fd)
538    }
539
540    /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
541    /// and returns [`filesystem::Descriptor`] corresponding to `fd`
542    /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`]
543    /// of [`crate::p2::filesystem::Dir`] type
544    fn get_dir_fd(
545        &mut self,
546        fd: types::Fd,
547    ) -> Result<Resource<filesystem::Descriptor>, types::Error> {
548        let st = self.transact()?;
549        let fd = st.get_dir_fd(fd)?;
550        Ok(fd)
551    }
552
553    /// Shared implementation of `fd_write` and `fd_pwrite`.
554    async fn fd_write_impl(
555        &mut self,
556        memory: &mut GuestMemory<'_>,
557        fd: types::Fd,
558        ciovs: types::CiovecArray,
559        write: FdWrite,
560    ) -> Result<types::Size, types::Error> {
561        let t = self.transact()?;
562        let desc = t.get_descriptor(fd)?;
563        match desc {
564            Descriptor::File(File {
565                fd,
566                append,
567                position,
568                // NB: files always use blocking writes regardless of what
569                // they're configured to use since OSes don't have nonblocking
570                // reads/writes anyway. This behavior originated in the first
571                // implementation of WASIp1 where flags were propagated to the
572                // OS and the OS ignored the nonblocking flag for files
573                // generally.
574                blocking_mode: _,
575            }) => {
576                let fd = fd.borrowed();
577                let position = position.clone();
578                let pos = position.load(Ordering::Relaxed);
579                let append = *append;
580                drop(t);
581                let f = self.table.get(&fd)?.file()?;
582                let buf = first_non_empty_ciovec(memory, ciovs)?;
583
584                let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) {
585                    // Note that this is implementing Linux semantics of
586                    // `pwrite` where the offset is ignored if the file was
587                    // opened in append mode.
588                    (true, _) => f.append(&buf),
589                    (false, FdWrite::At(pos)) => f.write_at(&buf, pos),
590                    (false, FdWrite::AtCur) => f.write_at(&buf, pos),
591                };
592
593                let nwritten = match f.as_blocking_file() {
594                    // If we can block then skip the copy out of wasm memory and
595                    // write directly to `f`.
596                    Some(f) => do_write(f, &memory.as_cow(buf)?),
597                    // ... otherwise copy out of wasm memory and use
598                    // `spawn_blocking` to do this write in a thread that can
599                    // block.
600                    None => {
601                        let buf = memory.to_vec(buf)?;
602                        f.run_blocking(move |f| do_write(f, &buf)).await
603                    }
604                };
605
606                let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
607
608                // If this was a write at the current position then update the
609                // current position with the result, otherwise the current
610                // position is left unmodified.
611                if let FdWrite::AtCur = write {
612                    if append {
613                        let len = self.filesystem().stat(fd).await?;
614                        position.store(len.size, Ordering::Relaxed);
615                    } else {
616                        let pos = pos
617                            .checked_add(nwritten as u64)
618                            .ok_or(types::Errno::Overflow)?;
619                        position.store(pos, Ordering::Relaxed);
620                    }
621                }
622                Ok(nwritten.try_into()?)
623            }
624            Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
625                match write {
626                    // Reject calls to `fd_pwrite` on stdio descriptors...
627                    FdWrite::At(_) => return Err(types::Errno::Spipe.into()),
628                    // ... but allow calls to `fd_write`
629                    FdWrite::AtCur => {}
630                }
631                let stream = stream.borrowed();
632                drop(t);
633                let buf = first_non_empty_ciovec(memory, ciovs)?;
634                let n = BlockingMode::Blocking
635                    .write(memory, &mut self.table, stream, buf)
636                    .await?
637                    .try_into()?;
638                Ok(n)
639            }
640            _ => Err(types::Errno::Badf.into()),
641        }
642    }
643}
644
645#[derive(Copy, Clone)]
646enum FdWrite {
647    At(u64),
648    AtCur,
649}
650
651/// Adds asynchronous versions of all WASIp1 functions to the
652/// [`wasmtime::Linker`] provided.
653///
654/// This method will add WASIp1 functions to `linker`. Access to [`WasiP1Ctx`]
655/// is provided with `f` by projecting from the store-local state of `T` to
656/// [`WasiP1Ctx`]. The closure `f` is invoked every time a WASIp1 function is
657/// called to get access to [`WasiP1Ctx`] from `T`. The returned [`WasiP1Ctx`] is
658/// used to implement I/O and controls what each function will return.
659///
660/// It's recommended that [`WasiP1Ctx`] is stored as a field in `T` or that `T =
661/// WasiP1Ctx` itself. The closure `f` should be a small projection (e.g. `&mut
662/// arg.field`) or something otherwise "small" as it will be executed every time
663/// a WASI call is made.
664///
665/// Note that this function is intended for use with
666/// [`Config::async_support(true)`]. If you're looking for a synchronous version
667/// see [`add_to_linker_sync`].
668///
669/// [`Config::async_support(true)`]: wasmtime::Config::async_support
670///
671/// # Examples
672///
673/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
674///
675/// ```no_run
676/// use wasmtime::{Result, Linker, Engine, Config};
677/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
678///
679/// fn main() -> Result<()> {
680///     let mut config = Config::new();
681///     config.async_support(true);
682///     let engine = Engine::new(&config)?;
683///
684///     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
685///     p1::add_to_linker_async(&mut linker, |cx| cx)?;
686///
687///     // ... continue to add more to `linker` as necessary and use it ...
688///
689///     Ok(())
690/// }
691/// ```
692///
693/// If the `T` in `Linker<T>` is custom state:
694///
695/// ```no_run
696/// use wasmtime::{Result, Linker, Engine, Config};
697/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
698///
699/// struct MyState {
700///     // .. other custom state here ..
701///
702///     wasi: WasiP1Ctx,
703/// }
704///
705/// fn main() -> Result<()> {
706///     let mut config = Config::new();
707///     config.async_support(true);
708///     let engine = Engine::new(&config)?;
709///
710///     let mut linker: Linker<MyState> = Linker::new(&engine);
711///     p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
712///
713///     // ... continue to add more to `linker` as necessary and use it ...
714///
715///     Ok(())
716/// }
717/// ```
718pub fn add_to_linker_async<T: Send + 'static>(
719    linker: &mut wasmtime::Linker<T>,
720    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
721) -> anyhow::Result<()> {
722    crate::p1::wasi_snapshot_preview1::add_to_linker(linker, f)
723}
724
725/// Adds synchronous versions of all WASIp1 functions to the
726/// [`wasmtime::Linker`] provided.
727///
728/// This method will add WASIp1 functions to `linker`. Access to [`WasiP1Ctx`]
729/// is provided with `f` by projecting from the store-local state of `T` to
730/// [`WasiP1Ctx`]. The closure `f` is invoked every time a WASIp1 function is
731/// called to get access to [`WasiP1Ctx`] from `T`. The returned [`WasiP1Ctx`] is
732/// used to implement I/O and controls what each function will return.
733///
734/// It's recommended that [`WasiP1Ctx`] is stored as a field in `T` or that `T =
735/// WasiP1Ctx` itself. The closure `f` should be a small projection (e.g. `&mut
736/// arg.field`) or something otherwise "small" as it will be executed every time
737/// a WASI call is made.
738///
739/// Note that this function is intended for use with
740/// [`Config::async_support(false)`]. If you're looking for a synchronous version
741/// see [`add_to_linker_async`].
742///
743/// [`Config::async_support(false)`]: wasmtime::Config::async_support
744///
745/// # Examples
746///
747/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
748///
749/// ```no_run
750/// use wasmtime::{Result, Linker, Engine, Config};
751/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
752///
753/// fn main() -> Result<()> {
754///     let mut config = Config::new();
755///     config.async_support(true);
756///     let engine = Engine::new(&config)?;
757///
758///     let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
759///     p1::add_to_linker_async(&mut linker, |cx| cx)?;
760///
761///     // ... continue to add more to `linker` as necessary and use it ...
762///
763///     Ok(())
764/// }
765/// ```
766///
767/// If the `T` in `Linker<T>` is custom state:
768///
769/// ```no_run
770/// use wasmtime::{Result, Linker, Engine, Config};
771/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
772///
773/// struct MyState {
774///     // .. other custom state here ..
775///
776///     wasi: WasiP1Ctx,
777/// }
778///
779/// fn main() -> Result<()> {
780///     let mut config = Config::new();
781///     config.async_support(true);
782///     let engine = Engine::new(&config)?;
783///
784///     let mut linker: Linker<MyState> = Linker::new(&engine);
785///     p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
786///
787///     // ... continue to add more to `linker` as necessary and use it ...
788///
789///     Ok(())
790/// }
791/// ```
792pub fn add_to_linker_sync<T: Send + 'static>(
793    linker: &mut wasmtime::Linker<T>,
794    f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
795) -> anyhow::Result<()> {
796    sync::add_wasi_snapshot_preview1_to_linker(linker, f)
797}
798
799// Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait,
800// and the module types.
801// None of the generated modules, traits, or types should be used externally
802// to this module.
803wiggle::from_witx!({
804    witx: ["witx/p1/wasi_snapshot_preview1.witx"],
805    async: {
806        wasi_snapshot_preview1::{
807            fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
808            fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
809            fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
810            path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
811            path_rename, path_symlink, path_unlink_file
812        }
813    },
814    errors: { errno => trappable Error },
815});
816
817pub(crate) mod sync {
818    use anyhow::Result;
819    use std::future::Future;
820
821    wiggle::wasmtime_integration!({
822        witx: ["witx/p1/wasi_snapshot_preview1.witx"],
823        target: super,
824        block_on[in_tokio]: {
825            wasi_snapshot_preview1::{
826                fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
827                fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
828                fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
829                path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
830                path_rename, path_symlink, path_unlink_file
831            }
832        },
833        errors: { errno => trappable Error },
834    });
835
836    // Small wrapper around `in_tokio` to add a `Result` layer which is always
837    // `Ok`
838    fn in_tokio<F: Future>(future: F) -> Result<F::Output> {
839        Ok(crate::runtime::in_tokio(future))
840    }
841}
842
843impl wiggle::GuestErrorType for types::Errno {
844    fn success() -> Self {
845        Self::Success
846    }
847}
848
849impl From<StreamError> for types::Error {
850    fn from(err: StreamError) -> Self {
851        match err {
852            StreamError::Closed => types::Errno::Io.into(),
853            StreamError::LastOperationFailed(e) => match e.downcast::<std::io::Error>() {
854                Ok(err) => filesystem::ErrorCode::from(err).into(),
855                Err(e) => {
856                    tracing::debug!("dropping error {e:?}");
857                    types::Errno::Io.into()
858                }
859            },
860            StreamError::Trap(e) => types::Error::trap(e),
861        }
862    }
863}
864
865impl From<FsError> for types::Error {
866    fn from(err: FsError) -> Self {
867        match err.downcast() {
868            Ok(code) => code.into(),
869            Err(e) => types::Error::trap(e),
870        }
871    }
872}
873
874fn systimespec(set: bool, ts: types::Timestamp, now: bool) -> Result<filesystem::NewTimestamp> {
875    if set && now {
876        Err(types::Errno::Inval.into())
877    } else if set {
878        Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime {
879            seconds: ts / 1_000_000_000,
880            nanoseconds: (ts % 1_000_000_000) as _,
881        }))
882    } else if now {
883        Ok(filesystem::NewTimestamp::Now)
884    } else {
885        Ok(filesystem::NewTimestamp::NoChange)
886    }
887}
888
889impl TryFrom<wall_clock::Datetime> for types::Timestamp {
890    type Error = types::Errno;
891
892    fn try_from(
893        wall_clock::Datetime {
894            seconds,
895            nanoseconds,
896        }: wall_clock::Datetime,
897    ) -> Result<Self, Self::Error> {
898        types::Timestamp::from(seconds)
899            .checked_mul(1_000_000_000)
900            .and_then(|ns| ns.checked_add(nanoseconds.into()))
901            .ok_or(types::Errno::Overflow)
902    }
903}
904
905impl From<types::Lookupflags> for filesystem::PathFlags {
906    fn from(flags: types::Lookupflags) -> Self {
907        if flags.contains(types::Lookupflags::SYMLINK_FOLLOW) {
908            filesystem::PathFlags::SYMLINK_FOLLOW
909        } else {
910            filesystem::PathFlags::empty()
911        }
912    }
913}
914
915impl From<types::Oflags> for filesystem::OpenFlags {
916    fn from(flags: types::Oflags) -> Self {
917        let mut out = filesystem::OpenFlags::empty();
918        if flags.contains(types::Oflags::CREAT) {
919            out |= filesystem::OpenFlags::CREATE;
920        }
921        if flags.contains(types::Oflags::DIRECTORY) {
922            out |= filesystem::OpenFlags::DIRECTORY;
923        }
924        if flags.contains(types::Oflags::EXCL) {
925            out |= filesystem::OpenFlags::EXCLUSIVE;
926        }
927        if flags.contains(types::Oflags::TRUNC) {
928            out |= filesystem::OpenFlags::TRUNCATE;
929        }
930        out
931    }
932}
933
934impl From<types::Advice> for filesystem::Advice {
935    fn from(advice: types::Advice) -> Self {
936        match advice {
937            types::Advice::Normal => filesystem::Advice::Normal,
938            types::Advice::Sequential => filesystem::Advice::Sequential,
939            types::Advice::Random => filesystem::Advice::Random,
940            types::Advice::Willneed => filesystem::Advice::WillNeed,
941            types::Advice::Dontneed => filesystem::Advice::DontNeed,
942            types::Advice::Noreuse => filesystem::Advice::NoReuse,
943        }
944    }
945}
946
947impl TryFrom<filesystem::DescriptorType> for types::Filetype {
948    type Error = anyhow::Error;
949
950    fn try_from(ty: filesystem::DescriptorType) -> Result<Self, Self::Error> {
951        match ty {
952            filesystem::DescriptorType::RegularFile => Ok(types::Filetype::RegularFile),
953            filesystem::DescriptorType::Directory => Ok(types::Filetype::Directory),
954            filesystem::DescriptorType::BlockDevice => Ok(types::Filetype::BlockDevice),
955            filesystem::DescriptorType::CharacterDevice => Ok(types::Filetype::CharacterDevice),
956            // p1 never had a FIFO code.
957            filesystem::DescriptorType::Fifo => Ok(types::Filetype::Unknown),
958            // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and
959            // FILETYPE_SOCKET_DGRAM.
960            filesystem::DescriptorType::Socket => {
961                bail!("sockets are not currently supported")
962            }
963            filesystem::DescriptorType::SymbolicLink => Ok(types::Filetype::SymbolicLink),
964            filesystem::DescriptorType::Unknown => Ok(types::Filetype::Unknown),
965        }
966    }
967}
968
969impl From<IsATTY> for types::Filetype {
970    fn from(isatty: IsATTY) -> Self {
971        match isatty {
972            IsATTY::Yes => types::Filetype::CharacterDevice,
973            IsATTY::No => types::Filetype::Unknown,
974        }
975    }
976}
977
978impl From<crate::filesystem::ErrorCode> for types::Errno {
979    fn from(code: crate::filesystem::ErrorCode) -> Self {
980        match code {
981            crate::filesystem::ErrorCode::Access => types::Errno::Acces,
982            crate::filesystem::ErrorCode::Already => types::Errno::Already,
983            crate::filesystem::ErrorCode::BadDescriptor => types::Errno::Badf,
984            crate::filesystem::ErrorCode::Busy => types::Errno::Busy,
985            crate::filesystem::ErrorCode::Exist => types::Errno::Exist,
986            crate::filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig,
987            crate::filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq,
988            crate::filesystem::ErrorCode::InProgress => types::Errno::Inprogress,
989            crate::filesystem::ErrorCode::Interrupted => types::Errno::Intr,
990            crate::filesystem::ErrorCode::Invalid => types::Errno::Inval,
991            crate::filesystem::ErrorCode::Io => types::Errno::Io,
992            crate::filesystem::ErrorCode::IsDirectory => types::Errno::Isdir,
993            crate::filesystem::ErrorCode::Loop => types::Errno::Loop,
994            crate::filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink,
995            crate::filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong,
996            crate::filesystem::ErrorCode::NoEntry => types::Errno::Noent,
997            crate::filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem,
998            crate::filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc,
999            crate::filesystem::ErrorCode::Unsupported => types::Errno::Notsup,
1000            crate::filesystem::ErrorCode::NotDirectory => types::Errno::Notdir,
1001            crate::filesystem::ErrorCode::NotEmpty => types::Errno::Notempty,
1002            crate::filesystem::ErrorCode::Overflow => types::Errno::Overflow,
1003            crate::filesystem::ErrorCode::NotPermitted => types::Errno::Perm,
1004            crate::filesystem::ErrorCode::Pipe => types::Errno::Pipe,
1005            crate::filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe,
1006        }
1007    }
1008}
1009
1010impl From<filesystem::ErrorCode> for types::Errno {
1011    fn from(code: filesystem::ErrorCode) -> Self {
1012        match code {
1013            filesystem::ErrorCode::Access => types::Errno::Acces,
1014            filesystem::ErrorCode::WouldBlock => types::Errno::Again,
1015            filesystem::ErrorCode::Already => types::Errno::Already,
1016            filesystem::ErrorCode::BadDescriptor => types::Errno::Badf,
1017            filesystem::ErrorCode::Busy => types::Errno::Busy,
1018            filesystem::ErrorCode::Deadlock => types::Errno::Deadlk,
1019            filesystem::ErrorCode::Quota => types::Errno::Dquot,
1020            filesystem::ErrorCode::Exist => types::Errno::Exist,
1021            filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig,
1022            filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq,
1023            filesystem::ErrorCode::InProgress => types::Errno::Inprogress,
1024            filesystem::ErrorCode::Interrupted => types::Errno::Intr,
1025            filesystem::ErrorCode::Invalid => types::Errno::Inval,
1026            filesystem::ErrorCode::Io => types::Errno::Io,
1027            filesystem::ErrorCode::IsDirectory => types::Errno::Isdir,
1028            filesystem::ErrorCode::Loop => types::Errno::Loop,
1029            filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink,
1030            filesystem::ErrorCode::MessageSize => types::Errno::Msgsize,
1031            filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong,
1032            filesystem::ErrorCode::NoDevice => types::Errno::Nodev,
1033            filesystem::ErrorCode::NoEntry => types::Errno::Noent,
1034            filesystem::ErrorCode::NoLock => types::Errno::Nolck,
1035            filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem,
1036            filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc,
1037            filesystem::ErrorCode::Unsupported => types::Errno::Notsup,
1038            filesystem::ErrorCode::NotDirectory => types::Errno::Notdir,
1039            filesystem::ErrorCode::NotEmpty => types::Errno::Notempty,
1040            filesystem::ErrorCode::NotRecoverable => types::Errno::Notrecoverable,
1041            filesystem::ErrorCode::NoTty => types::Errno::Notty,
1042            filesystem::ErrorCode::NoSuchDevice => types::Errno::Nxio,
1043            filesystem::ErrorCode::Overflow => types::Errno::Overflow,
1044            filesystem::ErrorCode::NotPermitted => types::Errno::Perm,
1045            filesystem::ErrorCode::Pipe => types::Errno::Pipe,
1046            filesystem::ErrorCode::ReadOnly => types::Errno::Rofs,
1047            filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe,
1048            filesystem::ErrorCode::TextFileBusy => types::Errno::Txtbsy,
1049            filesystem::ErrorCode::CrossDevice => types::Errno::Xdev,
1050        }
1051    }
1052}
1053
1054impl From<std::num::TryFromIntError> for types::Error {
1055    fn from(_: std::num::TryFromIntError) -> Self {
1056        types::Errno::Overflow.into()
1057    }
1058}
1059
1060impl From<GuestError> for types::Error {
1061    fn from(err: GuestError) -> Self {
1062        use wiggle::GuestError::*;
1063        match err {
1064            InvalidFlagValue { .. } => types::Errno::Inval.into(),
1065            InvalidEnumValue { .. } => types::Errno::Inval.into(),
1066            // As per
1067            // https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers
1068            //
1069            // > If a misaligned pointer is passed to a function, the function
1070            // > shall trap.
1071            // >
1072            // > If an out-of-bounds pointer is passed to a function and the
1073            // > function needs to dereference it, the function shall trap.
1074            //
1075            // so this turns OOB and misalignment errors into traps.
1076            PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => {
1077                types::Error::trap(err.into())
1078            }
1079            InvalidUtf8 { .. } => types::Errno::Ilseq.into(),
1080            TryFromIntError { .. } => types::Errno::Overflow.into(),
1081            SliceLengthsDiffer { .. } => types::Errno::Fault.into(),
1082            InFunc { err, .. } => types::Error::from(*err),
1083        }
1084    }
1085}
1086
1087impl From<filesystem::ErrorCode> for types::Error {
1088    fn from(code: filesystem::ErrorCode) -> Self {
1089        types::Errno::from(code).into()
1090    }
1091}
1092
1093impl From<crate::filesystem::ErrorCode> for types::Error {
1094    fn from(code: crate::filesystem::ErrorCode) -> Self {
1095        types::Errno::from(code).into()
1096    }
1097}
1098
1099impl From<wasmtime::component::ResourceTableError> for types::Error {
1100    fn from(err: wasmtime::component::ResourceTableError) -> Self {
1101        types::Error::trap(err.into())
1102    }
1103}
1104
1105type Result<T, E = types::Error> = std::result::Result<T, E>;
1106
1107fn write_bytes(
1108    memory: &mut GuestMemory<'_>,
1109    ptr: GuestPtr<u8>,
1110    buf: &[u8],
1111) -> Result<GuestPtr<u8>, types::Error> {
1112    // NOTE: legacy implementation always returns Inval errno
1113
1114    let len = u32::try_from(buf.len())?;
1115
1116    memory.copy_from_slice(buf, ptr.as_array(len))?;
1117    let next = ptr.add(len)?;
1118    Ok(next)
1119}
1120
1121fn write_byte(memory: &mut GuestMemory<'_>, ptr: GuestPtr<u8>, byte: u8) -> Result<GuestPtr<u8>> {
1122    memory.write(ptr, byte)?;
1123    let next = ptr.add(1)?;
1124    Ok(next)
1125}
1126
1127fn read_string<'a>(memory: &'a GuestMemory<'_>, ptr: GuestPtr<str>) -> Result<String> {
1128    Ok(memory.as_cow_str(ptr)?.into_owned())
1129}
1130
1131// Returns the first non-empty buffer in `ciovs` or a single empty buffer if
1132// they're all empty.
1133fn first_non_empty_ciovec(
1134    memory: &GuestMemory<'_>,
1135    ciovs: types::CiovecArray,
1136) -> Result<GuestPtr<[u8]>> {
1137    for iov in ciovs.iter() {
1138        let iov = memory.read(iov?)?;
1139        if iov.buf_len == 0 {
1140            continue;
1141        }
1142        return Ok(iov.buf.as_array(iov.buf_len));
1143    }
1144    Ok(GuestPtr::new((0, 0)))
1145}
1146
1147// Returns the first non-empty buffer in `iovs` or a single empty buffer if
1148// they're all empty.
1149fn first_non_empty_iovec(
1150    memory: &GuestMemory<'_>,
1151    iovs: types::IovecArray,
1152) -> Result<GuestPtr<[u8]>> {
1153    for iov in iovs.iter() {
1154        let iov = memory.read(iov?)?;
1155        if iov.buf_len == 0 {
1156            continue;
1157        }
1158        return Ok(iov.buf.as_array(iov.buf_len));
1159    }
1160    Ok(GuestPtr::new((0, 0)))
1161}
1162
1163// Implement the WasiSnapshotPreview1 trait using only the traits that are
1164// required for T, i.e., in terms of the preview 2 wit interface, and state
1165// stored in the WasiP1Adapter struct.
1166impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
1167    #[instrument(skip(self, memory))]
1168    fn args_get(
1169        &mut self,
1170        memory: &mut GuestMemory<'_>,
1171        argv: GuestPtr<GuestPtr<u8>>,
1172        argv_buf: GuestPtr<u8>,
1173    ) -> Result<(), types::Error> {
1174        self.cli()
1175            .get_arguments()
1176            .context("failed to call `get-arguments`")
1177            .map_err(types::Error::trap)?
1178            .into_iter()
1179            .try_fold((argv, argv_buf), |(argv, argv_buf), arg| -> Result<_> {
1180                memory.write(argv, argv_buf)?;
1181                let argv = argv.add(1)?;
1182
1183                let argv_buf = write_bytes(memory, argv_buf, arg.as_bytes())?;
1184                let argv_buf = write_byte(memory, argv_buf, 0)?;
1185
1186                Ok((argv, argv_buf))
1187            })?;
1188        Ok(())
1189    }
1190
1191    #[instrument(skip(self, _memory))]
1192    fn args_sizes_get(
1193        &mut self,
1194        _memory: &mut GuestMemory<'_>,
1195    ) -> Result<(types::Size, types::Size), types::Error> {
1196        let args = self
1197            .cli()
1198            .get_arguments()
1199            .context("failed to call `get-arguments`")
1200            .map_err(types::Error::trap)?;
1201        let num = args.len().try_into().map_err(|_| types::Errno::Overflow)?;
1202        let len = args
1203            .iter()
1204            .map(|buf| buf.len() + 1) // Each argument is expected to be `\0` terminated.
1205            .sum::<usize>()
1206            .try_into()
1207            .map_err(|_| types::Errno::Overflow)?;
1208        Ok((num, len))
1209    }
1210
1211    #[instrument(skip(self, memory))]
1212    fn environ_get(
1213        &mut self,
1214        memory: &mut GuestMemory<'_>,
1215        environ: GuestPtr<GuestPtr<u8>>,
1216        environ_buf: GuestPtr<u8>,
1217    ) -> Result<(), types::Error> {
1218        self.cli()
1219            .get_environment()
1220            .context("failed to call `get-environment`")
1221            .map_err(types::Error::trap)?
1222            .into_iter()
1223            .try_fold(
1224                (environ, environ_buf),
1225                |(environ, environ_buf), (k, v)| -> Result<_, types::Error> {
1226                    memory.write(environ, environ_buf)?;
1227                    let environ = environ.add(1)?;
1228
1229                    let environ_buf = write_bytes(memory, environ_buf, k.as_bytes())?;
1230                    let environ_buf = write_byte(memory, environ_buf, b'=')?;
1231                    let environ_buf = write_bytes(memory, environ_buf, v.as_bytes())?;
1232                    let environ_buf = write_byte(memory, environ_buf, 0)?;
1233
1234                    Ok((environ, environ_buf))
1235                },
1236            )?;
1237        Ok(())
1238    }
1239
1240    #[instrument(skip(self, _memory))]
1241    fn environ_sizes_get(
1242        &mut self,
1243        _memory: &mut GuestMemory<'_>,
1244    ) -> Result<(types::Size, types::Size), types::Error> {
1245        let environ = self
1246            .cli()
1247            .get_environment()
1248            .context("failed to call `get-environment`")
1249            .map_err(types::Error::trap)?;
1250        let num = environ.len().try_into()?;
1251        let len = environ
1252            .iter()
1253            .map(|(k, v)| k.len() + 1 + v.len() + 1) // Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
1254            .sum::<usize>()
1255            .try_into()?;
1256        Ok((num, len))
1257    }
1258
1259    #[instrument(skip(self, _memory))]
1260    fn clock_res_get(
1261        &mut self,
1262        _memory: &mut GuestMemory<'_>,
1263        id: types::Clockid,
1264    ) -> Result<types::Timestamp, types::Error> {
1265        let res = match id {
1266            types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.clocks())
1267                .context("failed to call `wall_clock::resolution`")
1268                .map_err(types::Error::trap)?
1269                .try_into()?,
1270            types::Clockid::Monotonic => monotonic_clock::Host::resolution(&mut self.clocks())
1271                .context("failed to call `monotonic_clock::resolution`")
1272                .map_err(types::Error::trap)?,
1273            types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
1274                return Err(types::Errno::Badf.into());
1275            }
1276        };
1277        Ok(res)
1278    }
1279
1280    #[instrument(skip(self, _memory))]
1281    fn clock_time_get(
1282        &mut self,
1283        _memory: &mut GuestMemory<'_>,
1284        id: types::Clockid,
1285        _precision: types::Timestamp,
1286    ) -> Result<types::Timestamp, types::Error> {
1287        let now = match id {
1288            types::Clockid::Realtime => wall_clock::Host::now(&mut self.clocks())
1289                .context("failed to call `wall_clock::now`")
1290                .map_err(types::Error::trap)?
1291                .try_into()?,
1292            types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.clocks())
1293                .context("failed to call `monotonic_clock::now`")
1294                .map_err(types::Error::trap)?,
1295            types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
1296                return Err(types::Errno::Badf.into());
1297            }
1298        };
1299        Ok(now)
1300    }
1301
1302    #[instrument(skip(self, _memory))]
1303    async fn fd_advise(
1304        &mut self,
1305        _memory: &mut GuestMemory<'_>,
1306        fd: types::Fd,
1307        offset: types::Filesize,
1308        len: types::Filesize,
1309        advice: types::Advice,
1310    ) -> Result<(), types::Error> {
1311        let fd = self.get_file_fd(fd)?;
1312        self.filesystem()
1313            .advise(fd, offset, len, advice.into())
1314            .await?;
1315        Ok(())
1316    }
1317
1318    /// Force the allocation of space in a file.
1319    /// NOTE: This is similar to `posix_fallocate` in POSIX.
1320    #[instrument(skip(self, _memory))]
1321    fn fd_allocate(
1322        &mut self,
1323        _memory: &mut GuestMemory<'_>,
1324        fd: types::Fd,
1325        _offset: types::Filesize,
1326        _len: types::Filesize,
1327    ) -> Result<(), types::Error> {
1328        self.get_file_fd(fd)?;
1329        Err(types::Errno::Notsup.into())
1330    }
1331
1332    /// Close a file descriptor.
1333    /// NOTE: This is similar to `close` in POSIX.
1334    #[instrument(skip(self, _memory))]
1335    async fn fd_close(
1336        &mut self,
1337        _memory: &mut GuestMemory<'_>,
1338        fd: types::Fd,
1339    ) -> Result<(), types::Error> {
1340        let desc = {
1341            let fd = fd.into();
1342            let mut st = self.transact()?;
1343            let desc = st.descriptors.used.remove(&fd).ok_or(types::Errno::Badf)?;
1344            st.descriptors.free.insert(fd);
1345            desc
1346        };
1347        match desc {
1348            Descriptor::Stdin { stream, .. } => {
1349                streams::HostInputStream::drop(&mut self.table, stream)
1350                    .await
1351                    .context("failed to call `drop` on `input-stream`")
1352            }
1353            Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
1354                streams::HostOutputStream::drop(&mut self.table, stream)
1355                    .await
1356                    .context("failed to call `drop` on `output-stream`")
1357            }
1358            Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => {
1359                filesystem::HostDescriptor::drop(&mut self.filesystem(), fd)
1360                    .context("failed to call `drop`")
1361            }
1362        }
1363        .map_err(types::Error::trap)
1364    }
1365
1366    /// Synchronize the data of a file to disk.
1367    /// NOTE: This is similar to `fdatasync` in POSIX.
1368    #[instrument(skip(self, _memory))]
1369    async fn fd_datasync(
1370        &mut self,
1371        _memory: &mut GuestMemory<'_>,
1372        fd: types::Fd,
1373    ) -> Result<(), types::Error> {
1374        let fd = self.get_file_fd(fd)?;
1375        self.filesystem().sync_data(fd).await?;
1376        Ok(())
1377    }
1378
1379    /// Get the attributes of a file descriptor.
1380    /// NOTE: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
1381    #[instrument(skip(self, _memory))]
1382    async fn fd_fdstat_get(
1383        &mut self,
1384        _memory: &mut GuestMemory<'_>,
1385        fd: types::Fd,
1386    ) -> Result<types::Fdstat, types::Error> {
1387        let (fd, blocking, append) = match self.transact()?.get_descriptor(fd)? {
1388            Descriptor::Stdin { isatty, .. } => {
1389                let fs_rights_base = types::Rights::FD_READ;
1390                return Ok(types::Fdstat {
1391                    fs_filetype: (*isatty).into(),
1392                    fs_flags: types::Fdflags::empty(),
1393                    fs_rights_base,
1394                    fs_rights_inheriting: fs_rights_base,
1395                });
1396            }
1397            Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => {
1398                let fs_rights_base = types::Rights::FD_WRITE;
1399                return Ok(types::Fdstat {
1400                    fs_filetype: (*isatty).into(),
1401                    fs_flags: types::Fdflags::empty(),
1402                    fs_rights_base,
1403                    fs_rights_inheriting: fs_rights_base,
1404                });
1405            }
1406            Descriptor::Directory {
1407                preopen_path: Some(_),
1408                ..
1409            } => {
1410                // Hard-coded set or rights expected by many userlands:
1411                let fs_rights_base = types::Rights::PATH_CREATE_DIRECTORY
1412                    | types::Rights::PATH_CREATE_FILE
1413                    | types::Rights::PATH_LINK_SOURCE
1414                    | types::Rights::PATH_LINK_TARGET
1415                    | types::Rights::PATH_OPEN
1416                    | types::Rights::FD_READDIR
1417                    | types::Rights::PATH_READLINK
1418                    | types::Rights::PATH_RENAME_SOURCE
1419                    | types::Rights::PATH_RENAME_TARGET
1420                    | types::Rights::PATH_SYMLINK
1421                    | types::Rights::PATH_REMOVE_DIRECTORY
1422                    | types::Rights::PATH_UNLINK_FILE
1423                    | types::Rights::PATH_FILESTAT_GET
1424                    | types::Rights::PATH_FILESTAT_SET_TIMES
1425                    | types::Rights::FD_FILESTAT_GET
1426                    | types::Rights::FD_FILESTAT_SET_TIMES;
1427
1428                let fs_rights_inheriting = fs_rights_base
1429                    | types::Rights::FD_DATASYNC
1430                    | types::Rights::FD_READ
1431                    | types::Rights::FD_SEEK
1432                    | types::Rights::FD_FDSTAT_SET_FLAGS
1433                    | types::Rights::FD_SYNC
1434                    | types::Rights::FD_TELL
1435                    | types::Rights::FD_WRITE
1436                    | types::Rights::FD_ADVISE
1437                    | types::Rights::FD_ALLOCATE
1438                    | types::Rights::FD_FILESTAT_GET
1439                    | types::Rights::FD_FILESTAT_SET_SIZE
1440                    | types::Rights::FD_FILESTAT_SET_TIMES
1441                    | types::Rights::POLL_FD_READWRITE;
1442
1443                return Ok(types::Fdstat {
1444                    fs_filetype: types::Filetype::Directory,
1445                    fs_flags: types::Fdflags::empty(),
1446                    fs_rights_base,
1447                    fs_rights_inheriting,
1448                });
1449            }
1450            Descriptor::Directory { fd, .. } => (fd.borrowed(), BlockingMode::Blocking, false),
1451            Descriptor::File(File {
1452                fd,
1453                blocking_mode,
1454                append,
1455                ..
1456            }) => (fd.borrowed(), *blocking_mode, *append),
1457        };
1458        let flags = self.filesystem().get_flags(fd.borrowed()).await?;
1459        let fs_filetype = self
1460            .filesystem()
1461            .get_type(fd.borrowed())
1462            .await?
1463            .try_into()
1464            .map_err(types::Error::trap)?;
1465        let mut fs_flags = types::Fdflags::empty();
1466        let mut fs_rights_base = types::Rights::all();
1467        if let types::Filetype::Directory = fs_filetype {
1468            fs_rights_base &= !types::Rights::FD_SEEK;
1469            fs_rights_base &= !types::Rights::FD_FILESTAT_SET_SIZE;
1470            fs_rights_base &= !types::Rights::PATH_FILESTAT_SET_SIZE;
1471        }
1472        if !flags.contains(filesystem::DescriptorFlags::READ) {
1473            fs_rights_base &= !types::Rights::FD_READ;
1474            fs_rights_base &= !types::Rights::FD_READDIR;
1475        }
1476        if !flags.contains(filesystem::DescriptorFlags::WRITE) {
1477            fs_rights_base &= !types::Rights::FD_WRITE;
1478        }
1479        if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
1480            fs_flags |= types::Fdflags::DSYNC;
1481        }
1482        if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
1483            fs_flags |= types::Fdflags::RSYNC;
1484        }
1485        if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
1486            fs_flags |= types::Fdflags::SYNC;
1487        }
1488        if append {
1489            fs_flags |= types::Fdflags::APPEND;
1490        }
1491        if matches!(blocking, BlockingMode::NonBlocking) {
1492            fs_flags |= types::Fdflags::NONBLOCK;
1493        }
1494        Ok(types::Fdstat {
1495            fs_filetype,
1496            fs_flags,
1497            fs_rights_base,
1498            fs_rights_inheriting: fs_rights_base,
1499        })
1500    }
1501
1502    /// Adjust the flags associated with a file descriptor.
1503    /// NOTE: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
1504    #[instrument(skip(self, _memory))]
1505    fn fd_fdstat_set_flags(
1506        &mut self,
1507        _memory: &mut GuestMemory<'_>,
1508        fd: types::Fd,
1509        flags: types::Fdflags,
1510    ) -> Result<(), types::Error> {
1511        let mut st = self.transact()?;
1512        let File {
1513            append,
1514            blocking_mode,
1515            ..
1516        } = st.get_file_mut(fd)?;
1517
1518        // Only support changing the NONBLOCK or APPEND flags.
1519        if flags.contains(types::Fdflags::DSYNC)
1520            || flags.contains(types::Fdflags::SYNC)
1521            || flags.contains(types::Fdflags::RSYNC)
1522        {
1523            return Err(types::Errno::Inval.into());
1524        }
1525        *append = flags.contains(types::Fdflags::APPEND);
1526        *blocking_mode = BlockingMode::from_fdflags(&flags);
1527        Ok(())
1528    }
1529
1530    /// Does not do anything if `fd` corresponds to a valid descriptor and returns `[types::Errno::Badf]` error otherwise.
1531    #[instrument(skip(self, _memory))]
1532    fn fd_fdstat_set_rights(
1533        &mut self,
1534        _memory: &mut GuestMemory<'_>,
1535        fd: types::Fd,
1536        _fs_rights_base: types::Rights,
1537        _fs_rights_inheriting: types::Rights,
1538    ) -> Result<(), types::Error> {
1539        self.get_fd(fd)?;
1540        Err(types::Errno::Notsup.into())
1541    }
1542
1543    /// Return the attributes of an open file.
1544    #[instrument(skip(self, _memory))]
1545    async fn fd_filestat_get(
1546        &mut self,
1547        _memory: &mut GuestMemory<'_>,
1548        fd: types::Fd,
1549    ) -> Result<types::Filestat, types::Error> {
1550        let t = self.transact()?;
1551        let desc = t.get_descriptor(fd)?;
1552        match desc {
1553            Descriptor::Stdin { isatty, .. }
1554            | Descriptor::Stdout { isatty, .. }
1555            | Descriptor::Stderr { isatty, .. } => Ok(types::Filestat {
1556                dev: 0,
1557                ino: 0,
1558                filetype: (*isatty).into(),
1559                nlink: 0,
1560                size: 0,
1561                atim: 0,
1562                mtim: 0,
1563                ctim: 0,
1564            }),
1565            Descriptor::Directory { fd, .. } | Descriptor::File(File { fd, .. }) => {
1566                let fd = fd.borrowed();
1567                drop(t);
1568                let filesystem::DescriptorStat {
1569                    type_,
1570                    link_count: nlink,
1571                    size,
1572                    data_access_timestamp,
1573                    data_modification_timestamp,
1574                    status_change_timestamp,
1575                } = self.filesystem().stat(fd.borrowed()).await?;
1576                let metadata_hash = self.filesystem().metadata_hash(fd).await?;
1577                let filetype = type_.try_into().map_err(types::Error::trap)?;
1578                let zero = wall_clock::Datetime {
1579                    seconds: 0,
1580                    nanoseconds: 0,
1581                };
1582                let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
1583                let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
1584                let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
1585                Ok(types::Filestat {
1586                    dev: 1,
1587                    ino: metadata_hash.lower,
1588                    filetype,
1589                    nlink,
1590                    size,
1591                    atim,
1592                    mtim,
1593                    ctim,
1594                })
1595            }
1596        }
1597    }
1598
1599    /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
1600    /// NOTE: This is similar to `ftruncate` in POSIX.
1601    #[instrument(skip(self, _memory))]
1602    async fn fd_filestat_set_size(
1603        &mut self,
1604        _memory: &mut GuestMemory<'_>,
1605        fd: types::Fd,
1606        size: types::Filesize,
1607    ) -> Result<(), types::Error> {
1608        let fd = self.get_file_fd(fd)?;
1609        self.filesystem().set_size(fd, size).await?;
1610        Ok(())
1611    }
1612
1613    /// Adjust the timestamps of an open file or directory.
1614    /// NOTE: This is similar to `futimens` in POSIX.
1615    #[instrument(skip(self, _memory))]
1616    async fn fd_filestat_set_times(
1617        &mut self,
1618        _memory: &mut GuestMemory<'_>,
1619        fd: types::Fd,
1620        atim: types::Timestamp,
1621        mtim: types::Timestamp,
1622        fst_flags: types::Fstflags,
1623    ) -> Result<(), types::Error> {
1624        let atim = systimespec(
1625            fst_flags.contains(types::Fstflags::ATIM),
1626            atim,
1627            fst_flags.contains(types::Fstflags::ATIM_NOW),
1628        )?;
1629        let mtim = systimespec(
1630            fst_flags.contains(types::Fstflags::MTIM),
1631            mtim,
1632            fst_flags.contains(types::Fstflags::MTIM_NOW),
1633        )?;
1634
1635        let fd = self.get_fd(fd)?;
1636        self.filesystem().set_times(fd, atim, mtim).await?;
1637        Ok(())
1638    }
1639
1640    /// Read from a file descriptor.
1641    /// NOTE: This is similar to `readv` in POSIX.
1642    #[instrument(skip(self, memory))]
1643    async fn fd_read(
1644        &mut self,
1645        memory: &mut GuestMemory<'_>,
1646        fd: types::Fd,
1647        iovs: types::IovecArray,
1648    ) -> Result<types::Size, types::Error> {
1649        let t = self.transact()?;
1650        let desc = t.get_descriptor(fd)?;
1651        match desc {
1652            Descriptor::File(File {
1653                fd,
1654                position,
1655                // NB: the nonblocking flag is intentionally ignored here and
1656                // blocking reads/writes are always performed.
1657                blocking_mode: _,
1658                ..
1659            }) => {
1660                let fd = fd.borrowed();
1661                let position = position.clone();
1662                drop(t);
1663                let pos = position.load(Ordering::Relaxed);
1664                let file = self.table.get(&fd)?.file()?;
1665                let iov = first_non_empty_iovec(memory, iovs)?;
1666                let bytes_read = match (file.as_blocking_file(), memory.as_slice_mut(iov)?) {
1667                    // Try to read directly into wasm memory where possible
1668                    // when the current thread can block and additionally wasm
1669                    // memory isn't shared.
1670                    (Some(file), Some(mut buf)) => file
1671                        .read_at(&mut buf, pos)
1672                        .map_err(|e| StreamError::LastOperationFailed(e.into()))?,
1673                    // ... otherwise fall back to performing the read on a
1674                    // blocking thread and which copies the data back into wasm
1675                    // memory.
1676                    (_, buf) => {
1677                        drop(buf);
1678                        let mut buf = vec![0; iov.len() as usize];
1679                        let buf = file
1680                            .run_blocking(move |file| -> Result<_, types::Error> {
1681                                let bytes_read = file
1682                                    .read_at(&mut buf, pos)
1683                                    .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
1684                                buf.truncate(bytes_read);
1685                                Ok(buf)
1686                            })
1687                            .await?;
1688                        let iov = iov.get_range(0..u32::try_from(buf.len())?).unwrap();
1689                        memory.copy_from_slice(&buf, iov)?;
1690                        buf.len()
1691                    }
1692                };
1693
1694                let pos = pos
1695                    .checked_add(bytes_read.try_into()?)
1696                    .ok_or(types::Errno::Overflow)?;
1697                position.store(pos, Ordering::Relaxed);
1698
1699                Ok(bytes_read.try_into()?)
1700            }
1701            Descriptor::Stdin { stream, .. } => {
1702                let stream = stream.borrowed();
1703                drop(t);
1704                let buf = first_non_empty_iovec(memory, iovs)?;
1705                let read = BlockingMode::Blocking
1706                    .read(&mut self.table, stream, buf.len().try_into()?)
1707                    .await?;
1708                if read.len() > buf.len().try_into()? {
1709                    return Err(types::Errno::Range.into());
1710                }
1711                let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
1712                memory.copy_from_slice(&read, buf)?;
1713                let n = read.len().try_into()?;
1714                Ok(n)
1715            }
1716            _ => return Err(types::Errno::Badf.into()),
1717        }
1718    }
1719
1720    /// Read from a file descriptor, without using and updating the file descriptor's offset.
1721    /// NOTE: This is similar to `preadv` in POSIX.
1722    #[instrument(skip(self, memory))]
1723    async fn fd_pread(
1724        &mut self,
1725        memory: &mut GuestMemory<'_>,
1726        fd: types::Fd,
1727        iovs: types::IovecArray,
1728        offset: types::Filesize,
1729    ) -> Result<types::Size, types::Error> {
1730        let t = self.transact()?;
1731        let desc = t.get_descriptor(fd)?;
1732        let (buf, read) = match desc {
1733            Descriptor::File(File {
1734                fd, blocking_mode, ..
1735            }) => {
1736                let fd = fd.borrowed();
1737                let blocking_mode = *blocking_mode;
1738                drop(t);
1739                let buf = first_non_empty_iovec(memory, iovs)?;
1740
1741                let stream = self.filesystem().read_via_stream(fd, offset)?;
1742                let read = blocking_mode
1743                    .read(&mut self.table, stream.borrowed(), buf.len().try_into()?)
1744                    .await;
1745                streams::HostInputStream::drop(&mut self.table, stream)
1746                    .await
1747                    .map_err(|e| types::Error::trap(e))?;
1748                (buf, read?)
1749            }
1750            Descriptor::Stdin { .. } => {
1751                // NOTE: legacy implementation returns SPIPE here
1752                return Err(types::Errno::Spipe.into());
1753            }
1754            _ => return Err(types::Errno::Badf.into()),
1755        };
1756        if read.len() > buf.len().try_into()? {
1757            return Err(types::Errno::Range.into());
1758        }
1759        let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
1760        memory.copy_from_slice(&read, buf)?;
1761        let n = read.len().try_into()?;
1762        Ok(n)
1763    }
1764
1765    /// Write to a file descriptor.
1766    /// NOTE: This is similar to `writev` in POSIX.
1767    #[instrument(skip(self, memory))]
1768    async fn fd_write(
1769        &mut self,
1770        memory: &mut GuestMemory<'_>,
1771        fd: types::Fd,
1772        ciovs: types::CiovecArray,
1773    ) -> Result<types::Size, types::Error> {
1774        self.fd_write_impl(memory, fd, ciovs, FdWrite::AtCur).await
1775    }
1776
1777    /// Write to a file descriptor, without using and updating the file descriptor's offset.
1778    /// NOTE: This is similar to `pwritev` in POSIX.
1779    #[instrument(skip(self, memory))]
1780    async fn fd_pwrite(
1781        &mut self,
1782        memory: &mut GuestMemory<'_>,
1783        fd: types::Fd,
1784        ciovs: types::CiovecArray,
1785        offset: types::Filesize,
1786    ) -> Result<types::Size, types::Error> {
1787        self.fd_write_impl(memory, fd, ciovs, FdWrite::At(offset))
1788            .await
1789    }
1790
1791    /// Return a description of the given preopened file descriptor.
1792    #[instrument(skip(self, _memory))]
1793    fn fd_prestat_get(
1794        &mut self,
1795        _memory: &mut GuestMemory<'_>,
1796        fd: types::Fd,
1797    ) -> Result<types::Prestat, types::Error> {
1798        if let Descriptor::Directory {
1799            preopen_path: Some(p),
1800            ..
1801        } = self.transact()?.get_descriptor(fd)?
1802        {
1803            let pr_name_len = p.len().try_into()?;
1804            return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len }));
1805        }
1806        Err(types::Errno::Badf.into()) // NOTE: legacy implementation returns BADF here
1807    }
1808
1809    /// Return a description of the given preopened file descriptor.
1810    #[instrument(skip(self, memory))]
1811    fn fd_prestat_dir_name(
1812        &mut self,
1813        memory: &mut GuestMemory<'_>,
1814        fd: types::Fd,
1815        path: GuestPtr<u8>,
1816        path_max_len: types::Size,
1817    ) -> Result<(), types::Error> {
1818        let path_max_len = path_max_len.try_into()?;
1819        if let Descriptor::Directory {
1820            preopen_path: Some(p),
1821            ..
1822        } = self.transact()?.get_descriptor(fd)?
1823        {
1824            if p.len() > path_max_len {
1825                return Err(types::Errno::Nametoolong.into());
1826            }
1827            write_bytes(memory, path, p.as_bytes())?;
1828            return Ok(());
1829        }
1830        Err(types::Errno::Notdir.into()) // NOTE: legacy implementation returns NOTDIR here
1831    }
1832
1833    /// Atomically replace a file descriptor by renumbering another file descriptor.
1834    #[instrument(skip(self, _memory))]
1835    fn fd_renumber(
1836        &mut self,
1837        _memory: &mut GuestMemory<'_>,
1838        from: types::Fd,
1839        to: types::Fd,
1840    ) -> Result<(), types::Error> {
1841        let mut st = self.transact()?;
1842        let from = from.into();
1843        let to = to.into();
1844        if !st.descriptors.used.contains_key(&to) {
1845            return Err(types::Errno::Badf.into());
1846        }
1847        let btree_map::Entry::Occupied(desc) = st.descriptors.used.entry(from) else {
1848            return Err(types::Errno::Badf.into());
1849        };
1850        if from != to {
1851            let desc = desc.remove();
1852            st.descriptors.free.insert(from);
1853            st.descriptors.free.remove(&to);
1854            st.descriptors.used.insert(to, desc);
1855        }
1856        Ok(())
1857    }
1858
1859    /// Move the offset of a file descriptor.
1860    /// NOTE: This is similar to `lseek` in POSIX.
1861    #[instrument(skip(self, _memory))]
1862    async fn fd_seek(
1863        &mut self,
1864        _memory: &mut GuestMemory<'_>,
1865        fd: types::Fd,
1866        offset: types::Filedelta,
1867        whence: types::Whence,
1868    ) -> Result<types::Filesize, types::Error> {
1869        let t = self.transact()?;
1870        let File { fd, position, .. } = t.get_seekable(fd)?;
1871        let fd = fd.borrowed();
1872        let position = position.clone();
1873        drop(t);
1874        let pos = match whence {
1875            types::Whence::Set if offset >= 0 => {
1876                offset.try_into().map_err(|_| types::Errno::Inval)?
1877            }
1878            types::Whence::Cur => position
1879                .load(Ordering::Relaxed)
1880                .checked_add_signed(offset)
1881                .ok_or(types::Errno::Inval)?,
1882            types::Whence::End => {
1883                let filesystem::DescriptorStat { size, .. } = self.filesystem().stat(fd).await?;
1884                size.checked_add_signed(offset).ok_or(types::Errno::Inval)?
1885            }
1886            _ => return Err(types::Errno::Inval.into()),
1887        };
1888        position.store(pos, Ordering::Relaxed);
1889        Ok(pos)
1890    }
1891
1892    /// Synchronize the data and metadata of a file to disk.
1893    /// NOTE: This is similar to `fsync` in POSIX.
1894    #[instrument(skip(self, _memory))]
1895    async fn fd_sync(
1896        &mut self,
1897        _memory: &mut GuestMemory<'_>,
1898        fd: types::Fd,
1899    ) -> Result<(), types::Error> {
1900        let fd = self.get_file_fd(fd)?;
1901        self.filesystem().sync(fd).await?;
1902        Ok(())
1903    }
1904
1905    /// Return the current offset of a file descriptor.
1906    /// NOTE: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
1907    #[instrument(skip(self, _memory))]
1908    fn fd_tell(
1909        &mut self,
1910        _memory: &mut GuestMemory<'_>,
1911        fd: types::Fd,
1912    ) -> Result<types::Filesize, types::Error> {
1913        let pos = self
1914            .transact()?
1915            .get_seekable(fd)
1916            .map(|File { position, .. }| position.load(Ordering::Relaxed))?;
1917        Ok(pos)
1918    }
1919
1920    #[instrument(skip(self, memory))]
1921    async fn fd_readdir(
1922        &mut self,
1923        memory: &mut GuestMemory<'_>,
1924        fd: types::Fd,
1925        buf: GuestPtr<u8>,
1926        buf_len: types::Size,
1927        cookie: types::Dircookie,
1928    ) -> Result<types::Size, types::Error> {
1929        let fd = self.get_dir_fd(fd)?;
1930        let stream = self.filesystem().read_directory(fd.borrowed()).await?;
1931        let dir_metadata_hash = self.filesystem().metadata_hash(fd.borrowed()).await?;
1932        let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?;
1933
1934        let head = [
1935            (
1936                types::Dirent {
1937                    d_next: 1u64.to_le(),
1938                    d_ino: dir_metadata_hash.lower.to_le(),
1939                    d_type: types::Filetype::Directory,
1940                    d_namlen: 1u32.to_le(),
1941                },
1942                ".".into(),
1943            ),
1944            (
1945                types::Dirent {
1946                    d_next: 2u64.to_le(),
1947                    d_ino: dir_metadata_hash.lower.to_le(), // NOTE: incorrect, but legacy implementation returns `fd` inode here
1948                    d_type: types::Filetype::Directory,
1949                    d_namlen: 2u32.to_le(),
1950                },
1951                "..".into(),
1952            ),
1953        ];
1954
1955        let mut dir = Vec::new();
1956        for (entry, d_next) in self
1957            .table
1958            // remove iterator from table and use it directly:
1959            .delete(stream)?
1960            .into_iter()
1961            .zip(3u64..)
1962        {
1963            let filesystem::DirectoryEntry { type_, name } = entry?;
1964            let metadata_hash = self
1965                .filesystem()
1966                .metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone())
1967                .await?;
1968            let d_type = type_.try_into().map_err(types::Error::trap)?;
1969            let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?;
1970            dir.push((
1971                types::Dirent {
1972                    d_next: d_next.to_le(),
1973                    d_ino: metadata_hash.lower.to_le(),
1974                    d_type, // endian-invariant
1975                    d_namlen: d_namlen.to_le(),
1976                },
1977                name,
1978            ))
1979        }
1980
1981        // assume that `types::Dirent` size always fits in `u32`
1982        const DIRENT_SIZE: u32 = size_of::<types::Dirent>() as _;
1983        assert_eq!(
1984            types::Dirent::guest_size(),
1985            DIRENT_SIZE,
1986            "Dirent guest repr and host repr should match"
1987        );
1988        let mut buf = buf;
1989        let mut cap = buf_len;
1990        for (ref entry, path) in head.into_iter().chain(dir.into_iter()).skip(cookie) {
1991            let mut path = path.into_bytes();
1992            assert_eq!(
1993                1,
1994                size_of_val(&entry.d_type),
1995                "Dirent member d_type should be endian-invariant"
1996            );
1997            let entry_len = cap.min(DIRENT_SIZE);
1998            let entry = entry as *const _ as _;
1999            let entry = unsafe { slice::from_raw_parts(entry, entry_len as _) };
2000            cap = cap.checked_sub(entry_len).unwrap();
2001            buf = write_bytes(memory, buf, entry)?;
2002            if cap == 0 {
2003                return Ok(buf_len);
2004            }
2005
2006            if let Ok(cap) = cap.try_into() {
2007                // `path` cannot be longer than `usize`, only truncate if `cap` fits in `usize`
2008                path.truncate(cap);
2009            }
2010            cap = cap.checked_sub(path.len() as _).unwrap();
2011            buf = write_bytes(memory, buf, &path)?;
2012            if cap == 0 {
2013                return Ok(buf_len);
2014            }
2015        }
2016        Ok(buf_len.checked_sub(cap).unwrap())
2017    }
2018
2019    #[instrument(skip(self, memory))]
2020    async fn path_create_directory(
2021        &mut self,
2022        memory: &mut GuestMemory<'_>,
2023        dirfd: types::Fd,
2024        path: GuestPtr<str>,
2025    ) -> Result<(), types::Error> {
2026        let dirfd = self.get_dir_fd(dirfd)?;
2027        let path = read_string(memory, path)?;
2028        self.filesystem()
2029            .create_directory_at(dirfd.borrowed(), path)
2030            .await?;
2031        Ok(())
2032    }
2033
2034    /// Return the attributes of a file or directory.
2035    /// NOTE: This is similar to `stat` in POSIX.
2036    #[instrument(skip(self, memory))]
2037    async fn path_filestat_get(
2038        &mut self,
2039        memory: &mut GuestMemory<'_>,
2040        dirfd: types::Fd,
2041        flags: types::Lookupflags,
2042        path: GuestPtr<str>,
2043    ) -> Result<types::Filestat, types::Error> {
2044        let dirfd = self.get_dir_fd(dirfd)?;
2045        let path = read_string(memory, path)?;
2046        let filesystem::DescriptorStat {
2047            type_,
2048            link_count: nlink,
2049            size,
2050            data_access_timestamp,
2051            data_modification_timestamp,
2052            status_change_timestamp,
2053        } = self
2054            .filesystem()
2055            .stat_at(dirfd.borrowed(), flags.into(), path.clone())
2056            .await?;
2057        let metadata_hash = self
2058            .filesystem()
2059            .metadata_hash_at(dirfd, flags.into(), path)
2060            .await?;
2061        let filetype = type_.try_into().map_err(types::Error::trap)?;
2062        let zero = wall_clock::Datetime {
2063            seconds: 0,
2064            nanoseconds: 0,
2065        };
2066        let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
2067        let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
2068        let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
2069        Ok(types::Filestat {
2070            dev: 1,
2071            ino: metadata_hash.lower,
2072            filetype,
2073            nlink,
2074            size,
2075            atim,
2076            mtim,
2077            ctim,
2078        })
2079    }
2080
2081    /// Adjust the timestamps of a file or directory.
2082    /// NOTE: This is similar to `utimensat` in POSIX.
2083    #[instrument(skip(self, memory))]
2084    async fn path_filestat_set_times(
2085        &mut self,
2086        memory: &mut GuestMemory<'_>,
2087        dirfd: types::Fd,
2088        flags: types::Lookupflags,
2089        path: GuestPtr<str>,
2090        atim: types::Timestamp,
2091        mtim: types::Timestamp,
2092        fst_flags: types::Fstflags,
2093    ) -> Result<(), types::Error> {
2094        let atim = systimespec(
2095            fst_flags.contains(types::Fstflags::ATIM),
2096            atim,
2097            fst_flags.contains(types::Fstflags::ATIM_NOW),
2098        )?;
2099        let mtim = systimespec(
2100            fst_flags.contains(types::Fstflags::MTIM),
2101            mtim,
2102            fst_flags.contains(types::Fstflags::MTIM_NOW),
2103        )?;
2104
2105        let dirfd = self.get_dir_fd(dirfd)?;
2106        let path = read_string(memory, path)?;
2107        self.filesystem()
2108            .set_times_at(dirfd, flags.into(), path, atim, mtim)
2109            .await?;
2110        Ok(())
2111    }
2112
2113    /// Create a hard link.
2114    /// NOTE: This is similar to `linkat` in POSIX.
2115    #[instrument(skip(self, memory))]
2116    async fn path_link(
2117        &mut self,
2118        memory: &mut GuestMemory<'_>,
2119        src_fd: types::Fd,
2120        src_flags: types::Lookupflags,
2121        src_path: GuestPtr<str>,
2122        target_fd: types::Fd,
2123        target_path: GuestPtr<str>,
2124    ) -> Result<(), types::Error> {
2125        let src_fd = self.get_dir_fd(src_fd)?;
2126        let target_fd = self.get_dir_fd(target_fd)?;
2127        let src_path = read_string(memory, src_path)?;
2128        let target_path = read_string(memory, target_path)?;
2129        self.filesystem()
2130            .link_at(src_fd, src_flags.into(), src_path, target_fd, target_path)
2131            .await?;
2132        Ok(())
2133    }
2134
2135    /// Open a file or directory.
2136    /// NOTE: This is similar to `openat` in POSIX.
2137    #[instrument(skip(self, memory))]
2138    async fn path_open(
2139        &mut self,
2140        memory: &mut GuestMemory<'_>,
2141        dirfd: types::Fd,
2142        dirflags: types::Lookupflags,
2143        path: GuestPtr<str>,
2144        oflags: types::Oflags,
2145        fs_rights_base: types::Rights,
2146        _fs_rights_inheriting: types::Rights,
2147        fdflags: types::Fdflags,
2148    ) -> Result<types::Fd, types::Error> {
2149        let path = read_string(memory, path)?;
2150
2151        let mut flags = filesystem::DescriptorFlags::empty();
2152        if fs_rights_base.contains(types::Rights::FD_READ) {
2153            flags |= filesystem::DescriptorFlags::READ;
2154        }
2155        if fs_rights_base.contains(types::Rights::FD_WRITE) {
2156            flags |= filesystem::DescriptorFlags::WRITE;
2157        }
2158        if fdflags.contains(types::Fdflags::SYNC) {
2159            flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;
2160        }
2161        if fdflags.contains(types::Fdflags::DSYNC) {
2162            flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;
2163        }
2164        if fdflags.contains(types::Fdflags::RSYNC) {
2165            flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;
2166        }
2167
2168        let t = self.transact()?;
2169        let dirfd = match t.get_descriptor(dirfd)? {
2170            Descriptor::Directory { fd, .. } => fd.borrowed(),
2171            Descriptor::File(_) => return Err(types::Errno::Notdir.into()),
2172            _ => return Err(types::Errno::Badf.into()),
2173        };
2174        drop(t);
2175        let fd = self
2176            .filesystem()
2177            .open_at(dirfd, dirflags.into(), path, oflags.into(), flags)
2178            .await?;
2179        let mut t = self.transact()?;
2180        let desc = match t.view.table.get(&fd)? {
2181            crate::filesystem::Descriptor::Dir(_) => Descriptor::Directory {
2182                fd,
2183                preopen_path: None,
2184            },
2185            crate::filesystem::Descriptor::File(_) => Descriptor::File(File {
2186                fd,
2187                position: Default::default(),
2188                append: fdflags.contains(types::Fdflags::APPEND),
2189                blocking_mode: BlockingMode::from_fdflags(&fdflags),
2190            }),
2191        };
2192        let fd = t.descriptors.push(desc)?;
2193        Ok(fd.into())
2194    }
2195
2196    /// Read the contents of a symbolic link.
2197    /// NOTE: This is similar to `readlinkat` in POSIX.
2198    #[instrument(skip(self, memory))]
2199    async fn path_readlink(
2200        &mut self,
2201        memory: &mut GuestMemory<'_>,
2202        dirfd: types::Fd,
2203        path: GuestPtr<str>,
2204        buf: GuestPtr<u8>,
2205        buf_len: types::Size,
2206    ) -> Result<types::Size, types::Error> {
2207        let dirfd = self.get_dir_fd(dirfd)?;
2208        let path = read_string(memory, path)?;
2209        let mut path = self
2210            .filesystem()
2211            .readlink_at(dirfd, path)
2212            .await?
2213            .into_bytes();
2214        if let Ok(buf_len) = buf_len.try_into() {
2215            // `path` cannot be longer than `usize`, only truncate if `buf_len` fits in `usize`
2216            path.truncate(buf_len);
2217        }
2218        let n = path.len().try_into().map_err(|_| types::Errno::Overflow)?;
2219        write_bytes(memory, buf, &path)?;
2220        Ok(n)
2221    }
2222
2223    #[instrument(skip(self, memory))]
2224    async fn path_remove_directory(
2225        &mut self,
2226        memory: &mut GuestMemory<'_>,
2227        dirfd: types::Fd,
2228        path: GuestPtr<str>,
2229    ) -> Result<(), types::Error> {
2230        let dirfd = self.get_dir_fd(dirfd)?;
2231        let path = read_string(memory, path)?;
2232        self.filesystem().remove_directory_at(dirfd, path).await?;
2233        Ok(())
2234    }
2235
2236    /// Rename a file or directory.
2237    /// NOTE: This is similar to `renameat` in POSIX.
2238    #[instrument(skip(self, memory))]
2239    async fn path_rename(
2240        &mut self,
2241        memory: &mut GuestMemory<'_>,
2242        src_fd: types::Fd,
2243        src_path: GuestPtr<str>,
2244        dest_fd: types::Fd,
2245        dest_path: GuestPtr<str>,
2246    ) -> Result<(), types::Error> {
2247        let src_fd = self.get_dir_fd(src_fd)?;
2248        let dest_fd = self.get_dir_fd(dest_fd)?;
2249        let src_path = read_string(memory, src_path)?;
2250        let dest_path = read_string(memory, dest_path)?;
2251        self.filesystem()
2252            .rename_at(src_fd, src_path, dest_fd, dest_path)
2253            .await?;
2254        Ok(())
2255    }
2256
2257    #[instrument(skip(self, memory))]
2258    async fn path_symlink(
2259        &mut self,
2260        memory: &mut GuestMemory<'_>,
2261        src_path: GuestPtr<str>,
2262        dirfd: types::Fd,
2263        dest_path: GuestPtr<str>,
2264    ) -> Result<(), types::Error> {
2265        let dirfd = self.get_dir_fd(dirfd)?;
2266        let src_path = read_string(memory, src_path)?;
2267        let dest_path = read_string(memory, dest_path)?;
2268        self.filesystem()
2269            .symlink_at(dirfd.borrowed(), src_path, dest_path)
2270            .await?;
2271        Ok(())
2272    }
2273
2274    #[instrument(skip(self, memory))]
2275    async fn path_unlink_file(
2276        &mut self,
2277        memory: &mut GuestMemory<'_>,
2278        dirfd: types::Fd,
2279        path: GuestPtr<str>,
2280    ) -> Result<(), types::Error> {
2281        let dirfd = self.get_dir_fd(dirfd)?;
2282        let path = memory.as_cow_str(path)?.into_owned();
2283        self.filesystem()
2284            .unlink_file_at(dirfd.borrowed(), path)
2285            .await?;
2286        Ok(())
2287    }
2288
2289    #[instrument(skip(self, memory))]
2290    async fn poll_oneoff(
2291        &mut self,
2292        memory: &mut GuestMemory<'_>,
2293        subs: GuestPtr<types::Subscription>,
2294        events: GuestPtr<types::Event>,
2295        nsubscriptions: types::Size,
2296    ) -> Result<types::Size, types::Error> {
2297        if nsubscriptions == 0 {
2298            // Indefinite sleeping is not supported in p1.
2299            return Err(types::Errno::Inval.into());
2300        }
2301
2302        // This is a special case where `poll_oneoff` is just sleeping
2303        // on a single relative timer event. This special case was added
2304        // after experimental observations showed that std::thread::sleep
2305        // results in more consistent sleep times. This design ensures that
2306        // wasmtime can handle real-time requirements more accurately.
2307        if nsubscriptions == 1 {
2308            let sub = memory.read(subs)?;
2309            if let types::SubscriptionU::Clock(clocksub) = sub.u {
2310                if !clocksub
2311                    .flags
2312                    .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
2313                    && self.wasi.filesystem.allow_blocking_current_thread
2314                {
2315                    std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout));
2316                    memory.write(
2317                        events,
2318                        types::Event {
2319                            userdata: sub.userdata,
2320                            error: types::Errno::Success,
2321                            type_: types::Eventtype::Clock,
2322                            fd_readwrite: types::EventFdReadwrite {
2323                                flags: types::Eventrwflags::empty(),
2324                                nbytes: 1,
2325                            },
2326                        },
2327                    )?;
2328                    return Ok(1);
2329                }
2330            }
2331        }
2332
2333        let subs = subs.as_array(nsubscriptions);
2334        let events = events.as_array(nsubscriptions);
2335
2336        let n = usize::try_from(nsubscriptions).unwrap_or(usize::MAX);
2337        let mut pollables = Vec::with_capacity(n);
2338        for sub in subs.iter() {
2339            let sub = memory.read(sub?)?;
2340            let p = match sub.u {
2341                types::SubscriptionU::Clock(types::SubscriptionClock {
2342                    id,
2343                    timeout,
2344                    flags,
2345                    ..
2346                }) => {
2347                    let absolute = flags.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
2348                    let (timeout, absolute) = match id {
2349                        types::Clockid::Monotonic => (timeout, absolute),
2350                        types::Clockid::Realtime if !absolute => (timeout, false),
2351                        types::Clockid::Realtime => {
2352                            let now = wall_clock::Host::now(&mut self.clocks())
2353                                .context("failed to call `wall_clock::now`")
2354                                .map_err(types::Error::trap)?;
2355
2356                            // Convert `timeout` to `Datetime` format.
2357                            let seconds = timeout / 1_000_000_000;
2358                            let nanoseconds = timeout % 1_000_000_000;
2359
2360                            let timeout = if now.seconds < seconds
2361                                || now.seconds == seconds
2362                                    && u64::from(now.nanoseconds) < nanoseconds
2363                            {
2364                                // `now` is less than `timeout`, which is expressible as u64,
2365                                // subtract the nanosecond counts directly
2366                                now.seconds * 1_000_000_000 + u64::from(now.nanoseconds) - timeout
2367                            } else {
2368                                0
2369                            };
2370                            (timeout, false)
2371                        }
2372                        _ => return Err(types::Errno::Inval.into()),
2373                    };
2374                    if absolute {
2375                        monotonic_clock::Host::subscribe_instant(&mut self.clocks(), timeout)
2376                            .context("failed to call `monotonic_clock::subscribe_instant`")
2377                            .map_err(types::Error::trap)?
2378                    } else {
2379                        monotonic_clock::Host::subscribe_duration(&mut self.clocks(), timeout)
2380                            .context("failed to call `monotonic_clock::subscribe_duration`")
2381                            .map_err(types::Error::trap)?
2382                    }
2383                }
2384                types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
2385                    file_descriptor,
2386                }) => {
2387                    let stream = {
2388                        let t = self.transact()?;
2389                        let desc = t.get_descriptor(file_descriptor)?;
2390                        match desc {
2391                            Descriptor::Stdin { stream, .. } => stream.borrowed(),
2392                            Descriptor::File(File { fd, position, .. }) => {
2393                                let pos = position.load(Ordering::Relaxed);
2394                                let fd = fd.borrowed();
2395                                drop(t);
2396                                self.filesystem().read_via_stream(fd, pos)?
2397                            }
2398                            // TODO: Support sockets
2399                            _ => return Err(types::Errno::Badf.into()),
2400                        }
2401                    };
2402                    streams::HostInputStream::subscribe(&mut self.table, stream)
2403                        .context("failed to call `subscribe` on `input-stream`")
2404                        .map_err(types::Error::trap)?
2405                }
2406                types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
2407                    file_descriptor,
2408                }) => {
2409                    let stream = {
2410                        let t = self.transact()?;
2411                        let desc = t.get_descriptor(file_descriptor)?;
2412                        match desc {
2413                            Descriptor::Stdout { stream, .. }
2414                            | Descriptor::Stderr { stream, .. } => stream.borrowed(),
2415                            Descriptor::File(File {
2416                                fd,
2417                                position,
2418                                append,
2419                                ..
2420                            }) => {
2421                                let fd = fd.borrowed();
2422                                let position = position.clone();
2423                                let append = *append;
2424                                drop(t);
2425                                if append {
2426                                    self.filesystem().append_via_stream(fd)?
2427                                } else {
2428                                    let pos = position.load(Ordering::Relaxed);
2429                                    self.filesystem().write_via_stream(fd, pos)?
2430                                }
2431                            }
2432                            // TODO: Support sockets
2433                            _ => return Err(types::Errno::Badf.into()),
2434                        }
2435                    };
2436                    streams::HostOutputStream::subscribe(&mut self.table, stream)
2437                        .context("failed to call `subscribe` on `output-stream`")
2438                        .map_err(types::Error::trap)?
2439                }
2440            };
2441            pollables.push(p);
2442        }
2443        let ready: HashSet<_> = self
2444            .table
2445            .poll(pollables)
2446            .await
2447            .context("failed to call `poll-oneoff`")
2448            .map_err(types::Error::trap)?
2449            .into_iter()
2450            .collect();
2451
2452        let mut count: types::Size = 0;
2453        for (sub, event) in (0..)
2454            .zip(subs.iter())
2455            .filter_map(|(idx, sub)| ready.contains(&idx).then_some(sub))
2456            .zip(events.iter())
2457        {
2458            let sub = memory.read(sub?)?;
2459            let event = event?;
2460            let e = match sub.u {
2461                types::SubscriptionU::Clock(..) => types::Event {
2462                    userdata: sub.userdata,
2463                    error: types::Errno::Success,
2464                    type_: types::Eventtype::Clock,
2465                    fd_readwrite: types::EventFdReadwrite {
2466                        flags: types::Eventrwflags::empty(),
2467                        nbytes: 0,
2468                    },
2469                },
2470                types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
2471                    file_descriptor,
2472                }) => {
2473                    let t = self.transact()?;
2474                    let desc = t.get_descriptor(file_descriptor)?;
2475                    match desc {
2476                        Descriptor::Stdin { .. } => types::Event {
2477                            userdata: sub.userdata,
2478                            error: types::Errno::Success,
2479                            type_: types::Eventtype::FdRead,
2480                            fd_readwrite: types::EventFdReadwrite {
2481                                flags: types::Eventrwflags::empty(),
2482                                nbytes: 1,
2483                            },
2484                        },
2485                        Descriptor::File(File { fd, position, .. }) => {
2486                            let fd = fd.borrowed();
2487                            let position = position.clone();
2488                            drop(t);
2489                            match self.filesystem().stat(fd).await? {
2490                                filesystem::DescriptorStat { size, .. } => {
2491                                    let pos = position.load(Ordering::Relaxed);
2492                                    let nbytes = size.saturating_sub(pos);
2493                                    types::Event {
2494                                        userdata: sub.userdata,
2495                                        error: types::Errno::Success,
2496                                        type_: types::Eventtype::FdRead,
2497                                        fd_readwrite: types::EventFdReadwrite {
2498                                            flags: if nbytes == 0 {
2499                                                types::Eventrwflags::FD_READWRITE_HANGUP
2500                                            } else {
2501                                                types::Eventrwflags::empty()
2502                                            },
2503                                            nbytes: 1,
2504                                        },
2505                                    }
2506                                }
2507                            }
2508                        }
2509                        // TODO: Support sockets
2510                        _ => return Err(types::Errno::Badf.into()),
2511                    }
2512                }
2513                types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
2514                    file_descriptor,
2515                }) => {
2516                    let t = self.transact()?;
2517                    let desc = t.get_descriptor(file_descriptor)?;
2518                    match desc {
2519                        Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => types::Event {
2520                            userdata: sub.userdata,
2521                            error: types::Errno::Success,
2522                            type_: types::Eventtype::FdWrite,
2523                            fd_readwrite: types::EventFdReadwrite {
2524                                flags: types::Eventrwflags::empty(),
2525                                nbytes: 1,
2526                            },
2527                        },
2528                        Descriptor::File(_) => types::Event {
2529                            userdata: sub.userdata,
2530                            error: types::Errno::Success,
2531                            type_: types::Eventtype::FdWrite,
2532                            fd_readwrite: types::EventFdReadwrite {
2533                                flags: types::Eventrwflags::empty(),
2534                                nbytes: 1,
2535                            },
2536                        },
2537                        // TODO: Support sockets
2538                        _ => return Err(types::Errno::Badf.into()),
2539                    }
2540                }
2541            };
2542            memory.write(event, e)?;
2543            count = count
2544                .checked_add(1)
2545                .ok_or_else(|| types::Error::from(types::Errno::Overflow))?
2546        }
2547        Ok(count)
2548    }
2549
2550    #[instrument(skip(self, _memory))]
2551    fn proc_exit(
2552        &mut self,
2553        _memory: &mut GuestMemory<'_>,
2554        status: types::Exitcode,
2555    ) -> anyhow::Error {
2556        // Check that the status is within WASI's range.
2557        if status >= 126 {
2558            return anyhow::Error::msg("exit with invalid exit status outside of [0..126)");
2559        }
2560        crate::I32Exit(status as i32).into()
2561    }
2562
2563    #[instrument(skip(self, _memory))]
2564    fn proc_raise(
2565        &mut self,
2566        _memory: &mut GuestMemory<'_>,
2567        _sig: types::Signal,
2568    ) -> Result<(), types::Error> {
2569        Err(types::Errno::Notsup.into())
2570    }
2571
2572    #[instrument(skip(self, _memory))]
2573    fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), types::Error> {
2574        // No such thing in preview 2. Intentionally left empty.
2575        Ok(())
2576    }
2577
2578    #[instrument(skip(self, memory))]
2579    fn random_get(
2580        &mut self,
2581        memory: &mut GuestMemory<'_>,
2582        buf: GuestPtr<u8>,
2583        buf_len: types::Size,
2584    ) -> Result<(), types::Error> {
2585        let rand = self
2586            .wasi
2587            .random
2588            .get_random_bytes(buf_len.into())
2589            .context("failed to call `get-random-bytes`")
2590            .map_err(types::Error::trap)?;
2591        write_bytes(memory, buf, &rand)?;
2592        Ok(())
2593    }
2594
2595    #[instrument(skip(self, _memory))]
2596    fn sock_accept(
2597        &mut self,
2598        _memory: &mut GuestMemory<'_>,
2599        fd: types::Fd,
2600        flags: types::Fdflags,
2601    ) -> Result<types::Fd, types::Error> {
2602        tracing::warn!("p1 sock_accept is not implemented");
2603        self.transact()?.get_descriptor(fd)?;
2604        Err(types::Errno::Notsock.into())
2605    }
2606
2607    #[instrument(skip(self, _memory))]
2608    fn sock_recv(
2609        &mut self,
2610        _memory: &mut GuestMemory<'_>,
2611        fd: types::Fd,
2612        ri_data: types::IovecArray,
2613        ri_flags: types::Riflags,
2614    ) -> Result<(types::Size, types::Roflags), types::Error> {
2615        tracing::warn!("p1 sock_recv is not implemented");
2616        self.transact()?.get_descriptor(fd)?;
2617        Err(types::Errno::Notsock.into())
2618    }
2619
2620    #[instrument(skip(self, _memory))]
2621    fn sock_send(
2622        &mut self,
2623        _memory: &mut GuestMemory<'_>,
2624        fd: types::Fd,
2625        si_data: types::CiovecArray,
2626        _si_flags: types::Siflags,
2627    ) -> Result<types::Size, types::Error> {
2628        tracing::warn!("p1 sock_send is not implemented");
2629        self.transact()?.get_descriptor(fd)?;
2630        Err(types::Errno::Notsock.into())
2631    }
2632
2633    #[instrument(skip(self, _memory))]
2634    fn sock_shutdown(
2635        &mut self,
2636        _memory: &mut GuestMemory<'_>,
2637        fd: types::Fd,
2638        how: types::Sdflags,
2639    ) -> Result<(), types::Error> {
2640        tracing::warn!("p1 sock_shutdown is not implemented");
2641        self.transact()?.get_descriptor(fd)?;
2642        Err(types::Errno::Notsock.into())
2643    }
2644}
2645
2646trait ResourceExt<T> {
2647    fn borrowed(&self) -> Resource<T>;
2648}
2649
2650impl<T: 'static> ResourceExt<T> for Resource<T> {
2651    fn borrowed(&self) -> Resource<T> {
2652        Resource::new_borrow(self.rep())
2653    }
2654}