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