Skip to main content

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