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