Path: blob/main/crates/wasi-preview1-component-adapter/src/lib.rs
1692 views
// The proxy world has no filesystem which most of this file is concerned with,1// so disable many warnings to avoid having to contort code too much for the2// proxy world.3#![cfg_attr(4feature = "proxy",5expect(6unused_mut,7unused_variables,8dead_code,9unused_imports,10unreachable_code,11reason = "stripped down in proxy build",12)13)]1415use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock};16use crate::bindings::wasi::io::poll;17use crate::bindings::wasi::io::streams;18use crate::bindings::wasi::random::random;19use core::cell::OnceCell;20use core::cell::{Cell, RefCell, RefMut, UnsafeCell};21use core::cmp::min;22use core::ffi::c_void;23use core::hint::black_box;24use core::mem::{self, ManuallyDrop, MaybeUninit, align_of, forget, size_of};25use core::num::NonZeroUsize;26use core::ops::{Deref, DerefMut};27use core::ptr::{self, null_mut};28use core::slice;29use poll::Pollable;30use wasi::*;3132#[cfg(not(feature = "proxy"))]33use crate::bindings::wasi::filesystem::types as filesystem;3435#[cfg(any(36all(feature = "command", feature = "reactor"),37all(feature = "reactor", feature = "proxy"),38all(feature = "command", feature = "proxy"),39))]40compile_error!(41"only one of the `command`, `reactor` or `proxy` features may be selected at a time"42);4344#[macro_use]45mod macros;4647mod descriptors;48use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams};4950pub mod bindings {51#[cfg(feature = "command")]52wit_bindgen_rust_macro::generate!({53path: "../wasi/src/p2/wit",54world: "wasi:cli/command",55raw_strings,56runtime_path: "crate::bindings::wit_bindgen_rt_shim",57// Automatically generated bindings for these functions will allocate58// Vecs, which in turn pulls in the panic machinery from std, which59// creates vtables that end up in the wasm elem section, which we60// can't support in these special core-wasm adapters.61// Instead, we manually define the bindings for these functions in62// terms of raw pointers.63skip: [64"run",65"get-environment",66"poll",67"[method]outgoing-datagram-stream.send",68],69generate_all,70disable_custom_section_link_helpers: true,71});7273#[cfg(feature = "reactor")]74wit_bindgen_rust_macro::generate!({75path: "../wasi/src/p2/wit",76world: "wasi:cli/imports",77raw_strings,78runtime_path: "crate::bindings::wit_bindgen_rt_shim",79// Automatically generated bindings for these functions will allocate80// Vecs, which in turn pulls in the panic machinery from std, which81// creates vtables that end up in the wasm elem section, which we82// can't support in these special core-wasm adapters.83// Instead, we manually define the bindings for these functions in84// terms of raw pointers.85skip: [86"get-environment",87"poll",88"[method]outgoing-datagram-stream.send",89],90generate_all,91disable_custom_section_link_helpers: true,92});9394#[cfg(feature = "proxy")]95wit_bindgen_rust_macro::generate!({96path: "../wasi-http/wit",97inline: r#"98package wasmtime:adapter;99100world adapter {101import wasi:clocks/[email protected];102import wasi:clocks/[email protected];103import wasi:random/[email protected];104import wasi:cli/[email protected];105import wasi:cli/[email protected];106import wasi:cli/[email protected];107}108"#,109world: "wasmtime:adapter/adapter",110raw_strings,111runtime_path: "crate::bindings::wit_bindgen_rt_shim",112skip: ["poll", "[method]outgoing-datagram-stream.send"],113generate_all,114disable_custom_section_link_helpers: true,115});116117pub mod wit_bindgen_rt_shim {118pub use bitflags;119120pub fn maybe_link_cabi_realloc() {}121}122}123124#[unsafe(export_name = "wasi:cli/[email protected]#run")]125#[cfg(feature = "command")]126pub extern "C" fn run() -> u32 {127#[link(wasm_import_module = "__main_module__")]128unsafe extern "C" {129safe fn _start();130}131_start();1320133}134135#[cfg(feature = "proxy")]136macro_rules! cfg_filesystem_available {137($($t:tt)*) => {138wasi::ERRNO_NOTSUP139};140}141#[cfg(not(feature = "proxy"))]142macro_rules! cfg_filesystem_available {143($($t:tt)*) => ($($t)*);144}145146// The unwrap/expect methods in std pull panic when they fail, which pulls147// in unwinding machinery that we can't use in the adapter. Instead, use this148// extension trait to get postfixed upwrap on Option and Result.149trait TrappingUnwrap<T> {150fn trapping_unwrap(self) -> T;151}152153impl<T> TrappingUnwrap<T> for Option<T> {154fn trapping_unwrap(self) -> T {155match self {156Some(t) => t,157None => unreachable!(),158}159}160}161162impl<T, E> TrappingUnwrap<T> for Result<T, E> {163fn trapping_unwrap(self) -> T {164match self {165Ok(t) => t,166Err(_) => unreachable!(),167}168}169}170171/// Allocate a file descriptor which will generate an `ERRNO_BADF` if passed to172/// any WASI Preview 1 function implemented by this adapter.173///174/// This is intended for use by `wasi-libc` during its incremental transition175/// from WASI Preview 1 to Preview 2. It will use this function to reserve176/// descriptors for its own use, valid only for use with libc functions.177#[unsafe(no_mangle)]178pub unsafe extern "C" fn adapter_open_badfd(fd: &mut u32) -> Errno {179State::with(|state| {180*fd = state.descriptors_mut().open(Descriptor::Bad)?;181Ok(())182})183}184185/// Close a descriptor previously opened using `adapter_open_badfd`.186#[unsafe(no_mangle)]187pub unsafe extern "C" fn adapter_close_badfd(fd: u32) -> Errno {188State::with(|state| state.descriptors_mut().close(fd))189}190191#[unsafe(no_mangle)]192pub unsafe extern "C" fn reset_adapter_state() {193unsafe {194let state = get_state_ptr();195if !state.is_null() {196State::init(state)197}198}199}200201#[unsafe(no_mangle)]202pub unsafe extern "C" fn cabi_import_realloc(203old_ptr: *mut u8,204old_size: usize,205align: usize,206new_size: usize,207) -> *mut u8 {208let mut ptr = null_mut::<u8>();209State::with(|state| {210let mut alloc = state.import_alloc.replace(ImportAlloc::None);211ptr = unsafe { alloc.alloc(old_ptr, old_size, align, new_size) };212state.import_alloc.set(alloc);213Ok(())214});215ptr216}217218/// Different ways that calling imports can allocate memory.219///220/// This behavior is used to customize the behavior of `cabi_import_realloc`.221/// This is configured within `State` whenever an import is called that may222/// invoke `cabi_import_realloc`.223///224/// The general idea behind these various behaviors of import allocation is225/// that we're limited for space in the adapter here to 1 page of memory but226/// that may not fit the total sum of arguments, environment variables, and227/// preopens. WASIp1 APIs all provide user-provided buffers as well for these228/// allocations so we technically don't need to store them in the adapter229/// itself. Instead what this does is it tries to copy strings and such directly230/// into their destination pointers where possible.231///232/// The types requiring allocation in the WASIp2 APIs that the WASIp1 APIs call233/// are relatively simple. They all look like `list<T>` where `T` only has234/// indirections in the form of `String`. This means that we can apply a235/// "clever" hack where the alignment of an allocation is used to disambiguate236/// whether we're allocating a string or allocating the `list<T>` allocation.237/// This signal with alignment means that we can configure where everything238/// goes.239///240/// For example consider `args_sizes_get` and `args_get`. When `args_sizes_get`241/// is called the `list<T>` allocation happens first with alignment 4. This242/// must be valid for the rest of the strings since the canonical ABI will fill243/// it in, so it's allocated from `State::temporary_data`. Next all other244/// arguments will be `string` type with alignment 1. These are also allocated245/// within `State::temporary_data` but they're "allocated on top of one246/// another" meaning that internal allocator state isn't updated after a string247/// is allocated. While these strings are discarded their sizes are all summed248/// up and returned from `args_sizes_get`.249///250/// Later though when `args_get` is called it's a similar allocation strategy251/// except that strings are instead redirected to the allocation provided to252/// `args_get` itself. This enables strings to be directly allocated into their253/// destinations.254///255/// Overall this means that we're limiting the maximum number of arguments plus256/// the size of the largest string, but otherwise we're not limiting the total257/// size of all arguments (or env vars, preopens, etc).258enum ImportAlloc {259/// A single allocation from the provided `BumpAlloc` is supported. After260/// the single allocation is performed all future allocations will fail.261OneAlloc(BumpAlloc),262263/// An allocator intended for `list<T>` where `T` has string types but no264/// other indirections. String allocations are discarded but counted for265/// size.266///267/// This allocator will use `alloc` for all allocations. Any string-related268/// allocation, detected via an alignment of 1, is considered "temporary"269/// and doesn't affect the internal state of the allocator. The allocation270/// is assumed to not be used after the import call returns.271///272/// The total sum of all string sizes, however, is accumulated within273/// `strings_size`.274CountAndDiscardStrings {275strings_size: usize,276alloc: BumpAlloc,277},278279/// An allocator intended for `list<T>` where `T` has string types but no280/// other indirections. String allocations go into `strings` and the281/// `list<..>` allocation goes into `pointers`.282///283/// This allocator enables placing strings within a caller-supplied buffer284/// configured with `strings`. The `pointers` allocation is285/// `State::temporary_data`.286///287/// This will additionally over-allocate strings with one extra byte to be288/// nul-terminated or `=`-terminated in the case of env vars.289SeparateStringsAndPointers {290strings: BumpAlloc,291pointers: BumpAlloc,292},293294/// An allocator specifically for getting the nth string allocation used295/// for preopens.296///297/// This will allocate everything into `alloc`. All strings other than the298/// `nth` string, however, will be discarded (the allocator's state is reset299/// after the allocation). This means that the pointer returned for the300/// `nth` string will be retained in `alloc` while all others will be301/// discarded.302///303/// The `cur` count starts at 0 and counts up per-string.304GetPreopenPath {305cur: u32,306nth: u32,307alloc: BumpAlloc,308},309310/// No import allocator is configured and if an allocation happens then311/// this will abort.312None,313}314315impl ImportAlloc {316/// To be used by cabi_import_realloc only!317unsafe fn alloc(318&mut self,319old_ptr: *mut u8,320old_size: usize,321align: usize,322size: usize,323) -> *mut u8 {324// This is ... a hack. This is a hack in subtle ways that is quite325// brittle and may break over time. There's only one case for the326// `realloc`-like-behavior in the canonical ABI and that's when the host327// is transferring a string to the guest and the host has a different328// string encoding. For example JS uses utf-16 (ish) and Rust/WASIp1 use329// utf-8. That means that when this adapter is used with a JS host330// realloc behavior may be triggered in which case `old_ptr` may not be331// null.332//333// In the case that `old_ptr` may not be null we come to the first334// brittle assumption: it's assumed that this is shrinking memory. In335// the canonical ABI overlarge allocations are made originally and then336// shrunk afterwards once encoding is finished. This means that the337// first allocation is too big and the `realloc` call is shrinking338// memory. This assumption may be violated in the future if the339// canonical ABI is updated to handle growing strings in addition to340// shrinking strings. (e.g. starting with an assume-ascii path and then341// falling back to an ok-needs-more-space path for larger unicode code342// points).343//344// This comes to the second brittle assumption, nothing happens here345// when a shrink happens. This is brittle for each of the cases below,346// enumerated here:347//348// * For `OneAlloc` this isn't the end of the world. That's already349// asserting that only a single string is allocated. Returning the350// original pointer keeps the pointer the same and the host will keep351// track of the appropriate length. In this case the final length is352// read out of the return value of a function, meaning that everything353// actually works out here.354//355// * For `CountAndDiscardStrings` we're relying on the fact that356// this is only used for `environ_sizes_get` and `args_sizes_get`. In357// both situations we're actually going to return an "overlarge"358// return value for the size of arguments and return values. By359// assuming memory shrinks after the first allocation the return value360// of `environ_sizes_get` and `args_sizes_get` will be the overlong361// approximation for all strings. That means that the final exact size362// won't be what's returned. This ends up being ok because technically363// nothing about WASI says that those blocks have to be exact-sized.364// In our case we're (ab)using that to force the caller to make an365// overlarge return area which we'll allocate into. All-in-all we366// don't track the shrink request and ignore the size.367//368// * For `SeparateStringsAndPointers` it's similar to the previous case369// except the weird part is that the caller is providing the370// argument/env space buffer to write into. It's over-large because of371// the case of `CountAndDiscardStrings` above, but we'll exploit that372// here and end up having space between all the arguments. Technically373// WASI doesn't say all the strings have to be adjacent, so this374// should work out in practice.375//376// * Finally for `GetPreopenPath` this works out only insofar that the377// `State::temporary_alloc` space is used to store the path. The378// WASI-provided buffer is precisely sized, not overly large, meaning379// that we're forced to copy from `temporary_alloc` into the380// destination buffer for this WASI call.381//382// Basically it's a case-by-case basis here that enables ignoring383// shrinking return calls here. Not robust.384if !old_ptr.is_null() {385assert!(old_size > size);386assert_eq!(align, 1);387return old_ptr;388}389match self {390ImportAlloc::OneAlloc(alloc) => unsafe {391let ret = alloc.alloc(align, size);392*self = ImportAlloc::None;393ret394},395ImportAlloc::SeparateStringsAndPointers { strings, pointers } => unsafe {396if align == 1 {397strings.alloc(align, size + 1)398} else {399pointers.alloc(align, size)400}401},402ImportAlloc::CountAndDiscardStrings {403strings_size,404alloc,405} => unsafe {406if align == 1 {407*strings_size += size;408alloc.clone().alloc(align, size)409} else {410alloc.alloc(align, size)411}412},413ImportAlloc::GetPreopenPath { cur, nth, alloc } => unsafe {414if align == 1 {415let real_alloc = *nth == *cur;416*cur += 1;417if real_alloc {418alloc.alloc(align, size)419} else {420alloc.clone().alloc(align, size)421}422} else {423alloc.alloc(align, size)424}425},426ImportAlloc::None => {427unreachable!("no allocator configured")428}429}430}431}432433/// Helper type to manage allocations from a `base`/`len` combo.434///435/// This isn't really used much in an arena-style per se but it's used in436/// combination with the `ImportAlloc` flavors above.437#[derive(Clone)]438struct BumpAlloc {439base: *mut u8,440len: usize,441}442443impl BumpAlloc {444unsafe fn alloc(&mut self, align: usize, size: usize) -> *mut u8 {445unsafe {446self.align_to(align);447}448if size > self.len {449unreachable!("allocation size is too large")450}451self.len -= size;452let ret = self.base;453self.base = unsafe { ret.add(size) };454ret455}456457unsafe fn align_to(&mut self, align: usize) {458if !align.is_power_of_two() {459unreachable!("invalid alignment");460}461let align_offset = self.base.align_offset(align);462if align_offset > self.len {463unreachable!("failed to allocate")464}465self.len -= align_offset;466self.base = unsafe { self.base.add(align_offset) };467}468}469470#[cfg(not(feature = "proxy"))]471#[link(wasm_import_module = "wasi:cli/[email protected]")]472unsafe extern "C" {473#[link_name = "get-arguments"]474fn wasi_cli_get_arguments(rval: *mut WasmStrList);475#[link_name = "get-environment"]476fn wasi_cli_get_environment(rval: *mut StrTupleList);477}478479/// Read command-line argument data.480/// The size of the array should match that returned by `args_sizes_get`481#[unsafe(no_mangle)]482pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno {483State::with(|state| {484#[cfg(not(feature = "proxy"))]485{486let alloc = ImportAlloc::SeparateStringsAndPointers {487strings: BumpAlloc {488base: argv_buf,489len: usize::MAX,490},491pointers: unsafe { state.temporary_alloc() },492};493let (list, _) = state.with_import_alloc(alloc, || unsafe {494let mut list = WasmStrList {495base: std::ptr::null(),496len: 0,497};498wasi_cli_get_arguments(&mut list);499list500});501502// Fill in `argv` by walking over the returned `list` and then503// additionally apply the nul-termination for each argument itself504// here.505unsafe {506for i in 0..list.len {507let s = list.base.add(i).read();508*argv.add(i) = s.ptr.cast_mut();509*s.ptr.add(s.len).cast_mut() = 0;510}511}512}513Ok(())514})515}516517/// Return command-line argument data sizes.518#[unsafe(no_mangle)]519pub unsafe extern "C" fn args_sizes_get(argc: &mut Size, argv_buf_size: &mut Size) -> Errno {520State::with(|state| {521#[cfg(feature = "proxy")]522{523*argc = 0;524*argv_buf_size = 0;525}526#[cfg(not(feature = "proxy"))]527{528let alloc = ImportAlloc::CountAndDiscardStrings {529strings_size: 0,530alloc: unsafe { state.temporary_alloc() },531};532let (len, alloc) = state.with_import_alloc(alloc, || unsafe {533let mut list = WasmStrList {534base: std::ptr::null(),535len: 0,536};537wasi_cli_get_arguments(&mut list);538list.len539});540match alloc {541ImportAlloc::CountAndDiscardStrings {542strings_size,543alloc: _,544} => {545*argc = len;546// add in bytes needed for a 0-byte at the end of each547// argument.548*argv_buf_size = strings_size + len;549}550_ => unreachable!(),551}552}553Ok(())554})555}556557/// Read environment variable data.558/// The sizes of the buffers should match that returned by `environ_sizes_get`.559#[unsafe(no_mangle)]560pub unsafe extern "C" fn environ_get(environ: *mut *const u8, environ_buf: *mut u8) -> Errno {561State::with(|state| {562#[cfg(not(feature = "proxy"))]563{564let alloc = ImportAlloc::SeparateStringsAndPointers {565strings: BumpAlloc {566base: environ_buf,567len: usize::MAX,568},569pointers: unsafe { state.temporary_alloc() },570};571let (list, _) = state.with_import_alloc(alloc, || unsafe {572let mut list = StrTupleList {573base: std::ptr::null(),574len: 0,575};576wasi_cli_get_environment(&mut list);577list578});579580// Fill in `environ` by walking over the returned `list`. Strings581// are guaranteed to be allocated next to each other with one582// extra byte at the end, so also insert the `=` between keys and583// the `\0` at the end of the env var.584unsafe {585for i in 0..list.len {586let s = list.base.add(i).read();587*environ.add(i) = s.key.ptr;588*s.key.ptr.add(s.key.len).cast_mut() = b'=';589*s.value.ptr.add(s.value.len).cast_mut() = 0;590}591}592}593594Ok(())595})596}597598/// Return environment variable data sizes.599#[unsafe(no_mangle)]600pub unsafe extern "C" fn environ_sizes_get(601environc: &mut Size,602environ_buf_size: &mut Size,603) -> Errno {604if !matches!(605unsafe { get_allocation_state() },606AllocationState::StackAllocated | AllocationState::StateAllocated607) {608*environc = 0;609*environ_buf_size = 0;610return ERRNO_SUCCESS;611}612613State::with(|state| {614#[cfg(feature = "proxy")]615{616*environc = 0;617*environ_buf_size = 0;618}619#[cfg(not(feature = "proxy"))]620{621let alloc = ImportAlloc::CountAndDiscardStrings {622strings_size: 0,623alloc: unsafe { state.temporary_alloc() },624};625let (len, alloc) = state.with_import_alloc(alloc, || unsafe {626let mut list = StrTupleList {627base: std::ptr::null(),628len: 0,629};630wasi_cli_get_environment(&mut list);631list.len632});633match alloc {634ImportAlloc::CountAndDiscardStrings {635strings_size,636alloc: _,637} => {638*environc = len;639// Account for `=` between keys and a 0-byte at the end of640// each key.641*environ_buf_size = strings_size + 2 * len;642}643_ => unreachable!(),644}645}646647Ok(())648})649}650651/// Return the resolution of a clock.652/// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,653/// return `errno::inval`.654/// Note: This is similar to `clock_getres` in POSIX.655#[unsafe(no_mangle)]656pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno {657match id {658CLOCKID_MONOTONIC => {659*resolution = monotonic_clock::resolution();660ERRNO_SUCCESS661}662CLOCKID_REALTIME => {663let res = wall_clock::resolution();664*resolution = match Timestamp::from(res.seconds)665.checked_mul(1_000_000_000)666.and_then(|ns| ns.checked_add(res.nanoseconds.into()))667{668Some(ns) => ns,669None => return ERRNO_OVERFLOW,670};671ERRNO_SUCCESS672}673_ => ERRNO_BADF,674}675}676677/// Return the time value of a clock.678/// Note: This is similar to `clock_gettime` in POSIX.679#[unsafe(no_mangle)]680pub unsafe extern "C" fn clock_time_get(681id: Clockid,682_precision: Timestamp,683time: &mut Timestamp,684) -> Errno {685match id {686CLOCKID_MONOTONIC => {687*time = monotonic_clock::now();688ERRNO_SUCCESS689}690CLOCKID_REALTIME => {691let res = wall_clock::now();692*time = match Timestamp::from(res.seconds)693.checked_mul(1_000_000_000)694.and_then(|ns| ns.checked_add(res.nanoseconds.into()))695{696Some(ns) => ns,697None => return ERRNO_OVERFLOW,698};699ERRNO_SUCCESS700}701_ => ERRNO_BADF,702}703}704705/// Provide file advisory information on a file descriptor.706/// Note: This is similar to `posix_fadvise` in POSIX.707#[unsafe(no_mangle)]708pub unsafe extern "C" fn fd_advise(709fd: Fd,710offset: Filesize,711len: Filesize,712advice: Advice,713) -> Errno {714cfg_filesystem_available! {715let advice = match advice {716ADVICE_NORMAL => filesystem::Advice::Normal,717ADVICE_SEQUENTIAL => filesystem::Advice::Sequential,718ADVICE_RANDOM => filesystem::Advice::Random,719ADVICE_WILLNEED => filesystem::Advice::WillNeed,720ADVICE_DONTNEED => filesystem::Advice::DontNeed,721ADVICE_NOREUSE => filesystem::Advice::NoReuse,722_ => return ERRNO_INVAL,723};724State::with(|state| {725let ds = state.descriptors();726let file = ds.get_seekable_file(fd)?;727file.fd.advise(offset, len, advice)?;728Ok(())729})730}731}732733/// Force the allocation of space in a file.734/// Note: This is similar to `posix_fallocate` in POSIX.735#[unsafe(no_mangle)]736pub unsafe extern "C" fn fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize) -> Errno {737cfg_filesystem_available! {738State::with(|state| {739let ds = state.descriptors();740// For not-files, fail with BADF741ds.get_file(fd)?;742// For all files, fail with NOTSUP, because this call does not exist in preview 2.743Err(wasi::ERRNO_NOTSUP)744})745}746}747748/// Close a file descriptor.749/// Note: This is similar to `close` in POSIX.750#[unsafe(no_mangle)]751pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno {752State::with(|state| {753if let Descriptor::Bad = state.descriptors().get(fd)? {754return Err(wasi::ERRNO_BADF);755}756757// If there's a dirent cache entry for this file descriptor then drop758// it since the descriptor is being closed and future calls to759// `fd_readdir` should return an error.760#[cfg(not(feature = "proxy"))]761if fd == state.dirent_cache.for_fd.get() {762drop(state.dirent_cache.stream.replace(None));763}764765state.descriptors_mut().close(fd)?;766Ok(())767})768}769770/// Synchronize the data of a file to disk.771/// Note: This is similar to `fdatasync` in POSIX.772#[unsafe(no_mangle)]773pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno {774cfg_filesystem_available! {775State::with(|state| {776let ds = state.descriptors();777let file = ds.get_file(fd)?;778file.fd.sync_data()?;779Ok(())780})781}782}783784/// Get the attributes of a file descriptor.785/// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.786#[unsafe(no_mangle)]787pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {788cfg_filesystem_available! {789State::with(|state| {790let ds = state.descriptors();791match ds.get(fd)? {792Descriptor::Streams(Streams {793type_: StreamType::File(file),794..795}) => {796let flags = file.fd.get_flags()?;797let type_ = file.fd.get_type()?;798match type_ {799filesystem::DescriptorType::Directory => {800// Hard-coded set of rights expected by many userlands:801let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY802| wasi::RIGHTS_PATH_CREATE_FILE803| wasi::RIGHTS_PATH_LINK_SOURCE804| wasi::RIGHTS_PATH_LINK_TARGET805| wasi::RIGHTS_PATH_OPEN806| wasi::RIGHTS_FD_READDIR807| wasi::RIGHTS_PATH_READLINK808| wasi::RIGHTS_PATH_RENAME_SOURCE809| wasi::RIGHTS_PATH_RENAME_TARGET810| wasi::RIGHTS_PATH_SYMLINK811| wasi::RIGHTS_PATH_REMOVE_DIRECTORY812| wasi::RIGHTS_PATH_UNLINK_FILE813| wasi::RIGHTS_PATH_FILESTAT_GET814| wasi::RIGHTS_PATH_FILESTAT_SET_TIMES815| wasi::RIGHTS_FD_FILESTAT_GET816| wasi::RIGHTS_FD_FILESTAT_SET_TIMES;817818let fs_rights_inheriting = fs_rights_base819| wasi::RIGHTS_FD_DATASYNC820| wasi::RIGHTS_FD_READ821| wasi::RIGHTS_FD_SEEK822| wasi::RIGHTS_FD_FDSTAT_SET_FLAGS823| wasi::RIGHTS_FD_SYNC824| wasi::RIGHTS_FD_TELL825| wasi::RIGHTS_FD_WRITE826| wasi::RIGHTS_FD_ADVISE827| wasi::RIGHTS_FD_ALLOCATE828| wasi::RIGHTS_FD_FILESTAT_GET829| wasi::RIGHTS_FD_FILESTAT_SET_SIZE830| wasi::RIGHTS_FD_FILESTAT_SET_TIMES831| wasi::RIGHTS_POLL_FD_READWRITE;832833unsafe {834stat.write(Fdstat {835fs_filetype: wasi::FILETYPE_DIRECTORY,836fs_flags: 0,837fs_rights_base,838fs_rights_inheriting,839});840}841Ok(())842}843_ => {844let fs_filetype = type_.into();845846let mut fs_flags = 0;847let mut fs_rights_base = !0;848if !flags.contains(filesystem::DescriptorFlags::READ) {849fs_rights_base &= !RIGHTS_FD_READ;850fs_rights_base &= !RIGHTS_FD_READDIR;851}852if !flags.contains(filesystem::DescriptorFlags::WRITE) {853fs_rights_base &= !RIGHTS_FD_WRITE;854}855if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {856fs_flags |= FDFLAGS_DSYNC;857}858if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {859fs_flags |= FDFLAGS_RSYNC;860}861if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {862fs_flags |= FDFLAGS_SYNC;863}864if file.append {865fs_flags |= FDFLAGS_APPEND;866}867if matches!(file.blocking_mode, BlockingMode::NonBlocking) {868fs_flags |= FDFLAGS_NONBLOCK;869}870let fs_rights_inheriting = fs_rights_base;871872unsafe {873stat.write(Fdstat {874fs_filetype,875fs_flags,876fs_rights_base,877fs_rights_inheriting,878});879}880Ok(())881}882}883}884Descriptor::Streams(Streams {885input,886output,887type_: StreamType::Stdio(stdio),888}) => {889let fs_flags = 0;890let mut fs_rights_base = 0;891if input.get().is_some() {892fs_rights_base |= RIGHTS_FD_READ;893}894if output.get().is_some() {895fs_rights_base |= RIGHTS_FD_WRITE;896}897let fs_rights_inheriting = fs_rights_base;898unsafe {899stat.write(Fdstat {900fs_filetype: stdio.filetype(),901fs_flags,902fs_rights_base,903fs_rights_inheriting,904});905}906Ok(())907}908Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),909}910})911}912}913914/// Adjust the flags associated with a file descriptor.915/// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.916#[unsafe(no_mangle)]917pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno {918// Only support changing the NONBLOCK or APPEND flags.919if flags & !(FDFLAGS_NONBLOCK | FDFLAGS_APPEND) != 0 {920return wasi::ERRNO_INVAL;921}922923cfg_filesystem_available! {924State::with(|state| {925let mut ds = state.descriptors_mut();926let file = match ds.get_mut(fd)? {927Descriptor::Streams(Streams {928type_: StreamType::File(file),929..930}) if !file.is_dir() => file,931_ => Err(wasi::ERRNO_BADF)?,932};933file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND;934file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK {935BlockingMode::NonBlocking936} else {937BlockingMode::Blocking938};939Ok(())940})941}942}943944/// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasi::ERRNO_BADF`] otherwise.945#[unsafe(no_mangle)]946pub unsafe extern "C" fn fd_fdstat_set_rights(947fd: Fd,948_fs_rights_base: Rights,949_fs_rights_inheriting: Rights,950) -> Errno {951State::with(|state| {952let ds = state.descriptors();953match ds.get(fd)? {954Descriptor::Streams(..) => Err(wasi::ERRNO_NOTSUP),955Descriptor::Closed(..) | Descriptor::Bad => Err(wasi::ERRNO_BADF),956}957})958}959960/// Return the attributes of an open file.961#[unsafe(no_mangle)]962pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: &mut Filestat) -> Errno {963cfg_filesystem_available! {964State::with(|state| {965let ds = state.descriptors();966match ds.get(fd)? {967Descriptor::Streams(Streams {968type_: StreamType::File(file),969..970}) => {971let stat = file.fd.stat()?;972let metadata_hash = file.fd.metadata_hash()?;973let filetype = stat.type_.into();974*buf = Filestat {975dev: 1,976ino: metadata_hash.lower,977filetype,978nlink: stat.link_count,979size: stat.size,980atim: datetime_to_timestamp(stat.data_access_timestamp),981mtim: datetime_to_timestamp(stat.data_modification_timestamp),982ctim: datetime_to_timestamp(stat.status_change_timestamp),983};984Ok(())985}986// Stdio is all zero fields, except for filetype character device987Descriptor::Streams(Streams {988type_: StreamType::Stdio(stdio),989..990}) => {991*buf = Filestat {992dev: 0,993ino: 0,994filetype: stdio.filetype(),995nlink: 0,996size: 0,997atim: 0,998mtim: 0,999ctim: 0,1000};1001Ok(())1002}1003_ => Err(wasi::ERRNO_BADF),1004}1005})1006}1007}10081009/// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.1010/// Note: This is similar to `ftruncate` in POSIX.1011#[unsafe(no_mangle)]1012pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno {1013cfg_filesystem_available! {1014State::with(|state| {1015let ds = state.descriptors();1016let file = ds.get_file(fd)?;1017file.fd.set_size(size)?;1018Ok(())1019})1020}1021}10221023#[cfg(not(feature = "proxy"))]1024fn systimespec(set: bool, ts: Timestamp, now: bool) -> Result<filesystem::NewTimestamp, Errno> {1025if set && now {1026Err(wasi::ERRNO_INVAL)1027} else if set {1028Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime {1029seconds: ts / 1_000_000_000,1030nanoseconds: (ts % 1_000_000_000) as _,1031}))1032} else if now {1033Ok(filesystem::NewTimestamp::Now)1034} else {1035Ok(filesystem::NewTimestamp::NoChange)1036}1037}10381039/// Adjust the timestamps of an open file or directory.1040/// Note: This is similar to `futimens` in POSIX.1041#[unsafe(no_mangle)]1042pub unsafe extern "C" fn fd_filestat_set_times(1043fd: Fd,1044atim: Timestamp,1045mtim: Timestamp,1046fst_flags: Fstflags,1047) -> Errno {1048cfg_filesystem_available! {1049State::with(|state| {1050let atim = systimespec(1051fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,1052atim,1053fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,1054)?;1055let mtim = systimespec(1056fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,1057mtim,1058fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,1059)?;1060let ds = state.descriptors();1061let file = ds.get_file(fd)?;1062file.fd.set_times(atim, mtim)?;1063Ok(())1064})1065}1066}10671068/// Read from a file descriptor, without using and updating the file descriptor's offset.1069/// Note: This is similar to `preadv` in POSIX.1070#[unsafe(no_mangle)]1071pub unsafe extern "C" fn fd_pread(1072fd: Fd,1073mut iovs_ptr: *const Iovec,1074mut iovs_len: usize,1075offset: Filesize,1076nread: &mut Size,1077) -> Errno {1078cfg_filesystem_available! {1079let (ptr, len) = unsafe {1080// Skip leading non-empty buffers.1081while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {1082iovs_ptr = iovs_ptr.add(1);1083iovs_len -= 1;1084}1085if iovs_len == 0 {1086*nread = 0;1087return ERRNO_SUCCESS;1088}1089((*iovs_ptr).buf, (*iovs_ptr).buf_len)1090};10911092State::with(|state| {10931094let ds = state.descriptors();1095let file = ds.get_file(fd)?;1096let (data, end) = state1097.with_one_import_alloc(ptr, len, || file.fd.read(len as u64, offset))?;1098assert_eq!(data.as_ptr(), ptr);1099assert!(data.len() <= len);11001101let len = data.len();1102forget(data);1103if !end && len == 0 {1104Err(ERRNO_INTR)1105} else {1106*nread = len;1107Ok(())1108}1109})1110}1111}11121113/// Return a description of the given preopened file descriptor.1114#[unsafe(no_mangle)]1115pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {1116if !matches!(1117unsafe { get_allocation_state() },1118AllocationState::StackAllocated | AllocationState::StateAllocated1119) {1120return ERRNO_BADF;1121}11221123// For the proxy adapter don't return `ERRNO_NOTSUP` through below, instead1124// always return `ERRNO_BADF` which is the indicator that prestats aren't1125// available.1126if cfg!(feature = "proxy") {1127return ERRNO_BADF;1128}11291130cfg_filesystem_available! {1131State::with(|state| {1132let ds = state.descriptors();1133match ds.get(fd)? {1134Descriptor::Streams(Streams {1135type_: StreamType::File(File {1136preopen_name_len: Some(len),1137..1138}),1139..1140}) => {1141unsafe {1142buf.write(Prestat {1143tag: 0,1144u: PrestatU {1145dir: PrestatDir {1146pr_name_len: len.get(),1147},1148},1149});1150}11511152Ok(())1153}1154_ => Err(ERRNO_BADF),1155}1156})1157}1158}11591160/// Return a description of the given preopened file descriptor.1161#[unsafe(no_mangle)]1162pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno {1163cfg_filesystem_available! {1164State::with(|state| {1165let ds = state.descriptors();1166let preopen_len = match ds.get(fd)? {1167Descriptor::Streams(Streams {1168type_: StreamType::File(File {1169preopen_name_len: Some(len),1170..1171}),1172..1173}) => len.get(),1174_ => return Err(ERRNO_BADF),1175};1176if preopen_len > path_max_len {1177return Err(ERRNO_NAMETOOLONG)1178}11791180unsafe {1181ds.get_preopen_path(state, fd, path, path_max_len);1182}1183Ok(())1184})1185}1186}11871188/// Write to a file descriptor, without using and updating the file descriptor's offset.1189/// Note: This is similar to `pwritev` in POSIX.1190#[unsafe(no_mangle)]1191pub unsafe extern "C" fn fd_pwrite(1192fd: Fd,1193mut iovs_ptr: *const Ciovec,1194mut iovs_len: usize,1195offset: Filesize,1196nwritten: &mut Size,1197) -> Errno {1198cfg_filesystem_available! {1199let bytes = unsafe {1200// Skip leading non-empty buffers.1201while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {1202iovs_ptr = iovs_ptr.add(1);1203iovs_len -= 1;1204}1205if iovs_len == 0 {1206*nwritten = 0;1207return ERRNO_SUCCESS;1208}12091210let ptr = (*iovs_ptr).buf;1211let len = (*iovs_ptr).buf_len;1212slice::from_raw_parts(ptr, len)1213};12141215State::with(|state| {1216let ds = state.descriptors();1217let file = ds.get_seekable_file(fd)?;1218let bytes = if file.append {1219match file.fd.append_via_stream()?.blocking_write_and_flush(bytes) {1220Ok(()) => bytes.len(),1221Err(streams::StreamError::Closed) => 0,1222Err(streams::StreamError::LastOperationFailed(e)) => {1223return Err(stream_error_to_errno(e))1224}1225}1226} else {1227file.fd.write(bytes, offset)? as usize1228};1229*nwritten = bytes;1230Ok(())1231})1232}1233}12341235/// Read from a file descriptor.1236/// Note: This is similar to `readv` in POSIX.1237#[unsafe(no_mangle)]1238pub unsafe extern "C" fn fd_read(1239fd: Fd,1240mut iovs_ptr: *const Iovec,1241mut iovs_len: usize,1242nread: &mut Size,1243) -> Errno {1244let (ptr, len) = unsafe {1245// Skip leading non-empty buffers.1246while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {1247iovs_ptr = iovs_ptr.add(1);1248iovs_len -= 1;1249}1250if iovs_len == 0 {1251*nread = 0;1252return ERRNO_SUCCESS;1253}12541255((*iovs_ptr).buf, (*iovs_ptr).buf_len)1256};12571258State::with(|state| {1259let ds = state.descriptors();1260match ds.get(fd)? {1261Descriptor::Streams(streams) => {1262#[cfg(not(feature = "proxy"))]1263let blocking_mode = if let StreamType::File(file) = &streams.type_ {1264file.blocking_mode1265} else {1266BlockingMode::Blocking1267};1268#[cfg(feature = "proxy")]1269let blocking_mode = BlockingMode::Blocking;12701271let read_len = u64::try_from(len).trapping_unwrap();1272let wasi_stream = streams.get_read_stream()?;1273let data = match state1274.with_one_import_alloc(ptr, len, || blocking_mode.read(wasi_stream, read_len))1275{1276Ok(data) => data,1277Err(streams::StreamError::Closed) => {1278*nread = 0;1279return Ok(());1280}1281Err(streams::StreamError::LastOperationFailed(e)) => {1282Err(stream_error_to_errno(e))?1283}1284};12851286assert_eq!(data.as_ptr(), ptr);1287assert!(data.len() <= len);12881289// If this is a file, keep the current-position pointer up to date.1290#[cfg(not(feature = "proxy"))]1291if let StreamType::File(file) = &streams.type_ {1292file.position1293.set(file.position.get() + data.len() as filesystem::Filesize);1294}12951296let len = data.len();1297*nread = len;1298forget(data);1299Ok(())1300}1301Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),1302}1303})1304}13051306fn stream_error_to_errno(err: streams::Error) -> Errno {1307#[cfg(feature = "proxy")]1308return ERRNO_IO;1309#[cfg(not(feature = "proxy"))]1310match filesystem::filesystem_error_code(&err) {1311Some(code) => code.into(),1312None => ERRNO_IO,1313}1314}13151316/// Read directory entries from a directory.1317/// When successful, the contents of the output buffer consist of a sequence of1318/// directory entries. Each directory entry consists of a `dirent` object,1319/// followed by `dirent::d_namlen` bytes holding the name of the directory1320/// entry.1321/// This function fills the output buffer as much as possible, potentially1322/// truncating the last directory entry. This allows the caller to grow its1323/// read buffer size in case it's too small to fit a single large directory1324/// entry, or skip the oversized directory entry.1325#[unsafe(no_mangle)]1326#[cfg(feature = "proxy")]1327pub unsafe extern "C" fn fd_readdir(1328fd: Fd,1329buf: *mut u8,1330buf_len: Size,1331cookie: Dircookie,1332bufused: *mut Size,1333) -> Errno {1334wasi::ERRNO_NOTSUP1335}13361337#[unsafe(no_mangle)]1338#[cfg(not(feature = "proxy"))]1339pub unsafe extern "C" fn fd_readdir(1340fd: Fd,1341buf: *mut u8,1342buf_len: Size,1343cookie: Dircookie,1344bufused: &mut Size,1345) -> Errno {1346let mut buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };1347return State::with(|state| {1348// First determine if there's an entry in the dirent cache to use. This1349// is done to optimize the use case where a large directory is being1350// used with a fixed-sized buffer to avoid re-invoking the `readdir`1351// function and continuing to use the same iterator.1352//1353// This is a bit tricky since the requested state in this function call1354// must match the prior state of the dirent stream, if any, so that's1355// all validated here as well.1356//1357// Note that for the duration of this function the `cookie` specifier is1358// the `n`th iteration of the `readdir` stream return value.1359let prev_stream = state.dirent_cache.stream.replace(None);1360let stream =1361if state.dirent_cache.for_fd.get() == fd && state.dirent_cache.cookie.get() == cookie {1362prev_stream1363} else {1364None1365};13661367// Compute the inode of `.` so that the iterator can produce an entry1368// for it.1369let ds = state.descriptors();1370let dir = ds.get_dir(fd)?;13711372let mut iter;1373match stream {1374// All our checks passed and a dirent cache was available with a1375// prior stream. Construct an iterator which will yield its first1376// entry from cache and is additionally resuming at the `cookie`1377// specified.1378Some(stream) => {1379iter = DirectoryEntryIterator {1380stream,1381state,1382cookie,1383use_cache: true,1384dir_descriptor: &dir.fd,1385}1386}13871388// Either a dirent stream wasn't previously available, a different1389// cookie was requested, or a brand new directory is now being read.1390// In these situations fall back to resuming reading the directory1391// from scratch, and the `cookie` value indicates how many items1392// need skipping.1393None => {1394iter = DirectoryEntryIterator {1395state,1396cookie: wasi::DIRCOOKIE_START,1397use_cache: false,1398stream: DirectoryEntryStream(dir.fd.read_directory()?),1399dir_descriptor: &dir.fd,1400};14011402// Skip to the entry that is requested by the `cookie`1403// parameter.1404for _ in wasi::DIRCOOKIE_START..cookie {1405match iter.next() {1406Some(Ok(_)) => {}1407Some(Err(e)) => return Err(e),1408None => return Ok(()),1409}1410}1411}1412};14131414while buf.len() > 0 {1415let (dirent, name) = match iter.next() {1416Some(Ok(pair)) => pair,1417Some(Err(e)) => return Err(e),1418None => break,1419};14201421// Copy a `dirent` describing this entry into the destination `buf`,1422// truncating it if it doesn't fit entirely.1423let bytes = unsafe {1424slice::from_raw_parts(1425(&dirent as *const wasi::Dirent).cast::<u8>(),1426size_of::<Dirent>(),1427)1428};1429let dirent_bytes_to_copy = buf.len().min(bytes.len());1430buf[..dirent_bytes_to_copy].copy_from_slice(&bytes[..dirent_bytes_to_copy]);1431buf = &mut buf[dirent_bytes_to_copy..];14321433// Copy the name bytes into the output `buf`, truncating it if it1434// doesn't fit.1435//1436// Note that this might be a 0-byte copy if the `dirent` was1437// truncated or fit entirely into the destination.1438let name_bytes_to_copy = buf.len().min(name.len());1439unsafe {1440ptr::copy_nonoverlapping(1441name.as_ptr().cast(),1442buf.as_mut_ptr(),1443name_bytes_to_copy,1444);1445}14461447buf = &mut buf[name_bytes_to_copy..];14481449// If the buffer is empty then that means the value may be1450// truncated, so save the state of the iterator in our dirent cache1451// and return.1452//1453// Note that `cookie - 1` is stored here since `iter.cookie` stores1454// the address of the next item, and we're rewinding one item since1455// the current item is truncated and will want to resume from that1456// in the future.1457//1458// Additionally note that this caching step is skipped if the name1459// to store doesn't actually fit in the dirent cache's path storage.1460// In that case there's not much we can do and let the next call to1461// `fd_readdir` start from scratch.1462if buf.len() == 0 && name.len() <= DIRENT_CACHE {1463let DirectoryEntryIterator { stream, cookie, .. } = iter;1464state.dirent_cache.stream.set(Some(stream));1465state.dirent_cache.for_fd.set(fd);1466state.dirent_cache.cookie.set(cookie - 1);1467state.dirent_cache.cached_dirent.set(dirent);1468unsafe {1469ptr::copy(1470name.as_ptr().cast::<u8>(),1471(*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8,1472name.len(),1473);1474}1475break;1476}1477}14781479*bufused = buf_len - buf.len();1480Ok(())1481});14821483struct DirectoryEntryIterator<'a> {1484state: &'a State,1485use_cache: bool,1486cookie: Dircookie,1487stream: DirectoryEntryStream,1488dir_descriptor: &'a filesystem::Descriptor,1489}14901491impl<'a> Iterator for DirectoryEntryIterator<'a> {1492// Note the usage of `UnsafeCell<u8>` here to indicate that the data can1493// alias the storage within `state`.1494type Item = Result<(wasi::Dirent, &'a [UnsafeCell<u8>]), Errno>;14951496fn next(&mut self) -> Option<Self::Item> {1497let current_cookie = self.cookie;14981499self.cookie += 1;15001501// Preview1 programs expect to see `.` and `..` in the traversal, but1502// Preview2 excludes them, so re-add them.1503match current_cookie {15040 => {1505let metadata_hash = match self.dir_descriptor.metadata_hash() {1506Ok(h) => h,1507Err(e) => return Some(Err(e.into())),1508};1509let dirent = wasi::Dirent {1510d_next: self.cookie,1511d_ino: metadata_hash.lower,1512d_type: wasi::FILETYPE_DIRECTORY,1513d_namlen: 1,1514};1515return Some(Ok((dirent, &self.state.dotdot[..1])));1516}15171 => {1518let dirent = wasi::Dirent {1519d_next: self.cookie,1520d_ino: 0,1521d_type: wasi::FILETYPE_DIRECTORY,1522d_namlen: 2,1523};1524return Some(Ok((dirent, &self.state.dotdot[..])));1525}1526_ => {}1527}15281529if self.use_cache {1530self.use_cache = false;1531return Some(unsafe {1532let dirent = self.state.dirent_cache.cached_dirent.as_ptr().read();1533let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr())1534.as_ptr()1535.cast();1536let buffer = slice::from_raw_parts(ptr, dirent.d_namlen as usize);1537Ok((dirent, buffer))1538});1539}1540let entry = self1541.state1542.with_one_temporary_alloc(|| self.stream.0.read_directory_entry());1543let entry = match entry {1544Ok(Some(entry)) => entry,1545Ok(None) => return None,1546Err(e) => return Some(Err(e.into())),1547};15481549let filesystem::DirectoryEntry { type_, name } = entry;1550let d_ino = self1551.dir_descriptor1552.metadata_hash_at(filesystem::PathFlags::empty(), &name)1553.map(|h| h.lower)1554.unwrap_or(0);1555let name = ManuallyDrop::new(name);1556let dirent = wasi::Dirent {1557d_next: self.cookie,1558d_ino,1559d_namlen: u32::try_from(name.len()).trapping_unwrap(),1560d_type: type_.into(),1561};1562// Extend the lifetime of `name` to the `self.state` lifetime for1563// this iterator since the data for the name lives within state.1564let name = unsafe {1565assert_eq!(name.as_ptr(), self.state.temporary_data.get().cast());1566slice::from_raw_parts(name.as_ptr().cast(), name.len())1567};1568Some(Ok((dirent, name)))1569}1570}1571}15721573/// Atomically replace a file descriptor by renumbering another file descriptor.1574/// Due to the strong focus on thread safety, this environment does not provide1575/// a mechanism to duplicate or renumber a file descriptor to an arbitrary1576/// number, like `dup2()`. This would be prone to race conditions, as an actual1577/// file descriptor with the same number could be allocated by a different1578/// thread at the same time.1579/// This function provides a way to atomically renumber file descriptors, which1580/// would disappear if `dup2()` were to be removed entirely.1581#[unsafe(no_mangle)]1582pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno {1583State::with(|state| state.descriptors_mut().renumber(fd, to))1584}15851586/// Move the offset of a file descriptor.1587/// Note: This is similar to `lseek` in POSIX.1588#[unsafe(no_mangle)]1589pub unsafe extern "C" fn fd_seek(1590fd: Fd,1591offset: Filedelta,1592whence: Whence,1593newoffset: &mut Filesize,1594) -> Errno {1595cfg_filesystem_available! {1596State::with(|state| {1597let mut ds = state.descriptors_mut();1598let stream = ds.get_seekable_stream_mut(fd)?;15991600// Seeking only works on files.1601if let StreamType::File(file) = &mut stream.type_ {1602if let filesystem::DescriptorType::Directory = file.descriptor_type {1603// This isn't really the "right" errno, but it is consistient with wasmtime's1604// preview 1 tests.1605return Err(ERRNO_BADF);1606}1607let from = match whence {1608WHENCE_SET if offset >= 0 => offset,1609WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {1610Some(pos) if pos >= 0 => pos,1611_ => return Err(ERRNO_INVAL),1612},1613WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {1614Some(pos) if pos >= 0 => pos,1615_ => return Err(ERRNO_INVAL),1616},1617_ => return Err(ERRNO_INVAL),1618};1619drop(stream.input.take());1620drop(stream.output.take());1621file.position.set(from as filesystem::Filesize);1622*newoffset = from as filesystem::Filesize;1623Ok(())1624} else {1625Err(ERRNO_SPIPE)1626}1627})1628}1629}16301631/// Synchronize the data and metadata of a file to disk.1632/// Note: This is similar to `fsync` in POSIX.1633#[unsafe(no_mangle)]1634pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno {1635cfg_filesystem_available! {1636State::with(|state| {1637let ds = state.descriptors();1638let file = ds.get_file(fd)?;1639file.fd.sync()?;1640Ok(())1641})1642}1643}16441645/// Return the current offset of a file descriptor.1646/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.1647#[unsafe(no_mangle)]1648pub unsafe extern "C" fn fd_tell(fd: Fd, offset: &mut Filesize) -> Errno {1649cfg_filesystem_available! {1650State::with(|state| {1651let ds = state.descriptors();1652let file = ds.get_seekable_file(fd)?;1653*offset = file.position.get();1654Ok(())1655})1656}1657}16581659/// Write to a file descriptor.1660/// Note: This is similar to `writev` in POSIX.1661#[unsafe(no_mangle)]1662pub unsafe extern "C" fn fd_write(1663fd: Fd,1664mut iovs_ptr: *const Ciovec,1665mut iovs_len: usize,1666nwritten: &mut Size,1667) -> Errno {1668if !matches!(1669unsafe { get_allocation_state() },1670AllocationState::StackAllocated | AllocationState::StateAllocated1671) {1672*nwritten = 0;1673return ERRNO_IO;1674}16751676let bytes = unsafe {1677// Skip leading empty buffers.1678while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {1679iovs_ptr = iovs_ptr.add(1);1680iovs_len -= 1;1681}1682if iovs_len == 0 {1683*nwritten = 0;1684return ERRNO_SUCCESS;1685}1686let ptr = (*iovs_ptr).buf;1687let len = (*iovs_ptr).buf_len;1688slice::from_raw_parts(ptr, len)1689};16901691State::with(|state| {1692let ds = state.descriptors();1693match ds.get(fd)? {1694Descriptor::Streams(streams) => {1695let wasi_stream = streams.get_write_stream()?;16961697#[cfg(not(feature = "proxy"))]1698let nbytes = if let StreamType::File(file) = &streams.type_ {1699file.blocking_mode.write(wasi_stream, bytes)?1700} else {1701// Use blocking writes on non-file streams (stdout, stderr, as sockets1702// aren't currently used).1703BlockingMode::Blocking.write(wasi_stream, bytes)?1704};1705#[cfg(feature = "proxy")]1706let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;17071708// If this is a file, keep the current-position pointer up1709// to date. Note that for files that perform appending1710// writes this function will always update the current1711// position to the end of the file.1712//1713// NB: this isn't "atomic" as it doesn't necessarily account1714// for concurrent writes, but there's not much that can be1715// done about that.1716#[cfg(not(feature = "proxy"))]1717if let StreamType::File(file) = &streams.type_ {1718if file.append {1719file.position.set(file.fd.stat()?.size);1720} else {1721file.position.set(file.position.get() + nbytes as u64);1722}1723}17241725*nwritten = nbytes;1726Ok(())1727}1728Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),1729}1730})1731}17321733/// Create a directory.1734/// Note: This is similar to `mkdirat` in POSIX.1735#[unsafe(no_mangle)]1736pub unsafe extern "C" fn path_create_directory(1737fd: Fd,1738path_ptr: *const u8,1739path_len: usize,1740) -> Errno {1741cfg_filesystem_available! {1742let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };17431744State::with(|state| {1745let ds = state.descriptors();1746let file = ds.get_dir(fd)?;1747file.fd.create_directory_at(path)?;1748Ok(())1749})1750}1751}17521753/// Return the attributes of a file or directory.1754/// Note: This is similar to `stat` in POSIX.1755#[unsafe(no_mangle)]1756pub unsafe extern "C" fn path_filestat_get(1757fd: Fd,1758flags: Lookupflags,1759path_ptr: *const u8,1760path_len: usize,1761buf: &mut Filestat,1762) -> Errno {1763cfg_filesystem_available! {1764let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1765let at_flags = at_flags_from_lookupflags(flags);17661767State::with(|state| {1768let ds = state.descriptors();1769let file = ds.get_dir(fd)?;1770let stat = file.fd.stat_at(at_flags, path)?;1771let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;1772let filetype = stat.type_.into();1773*buf = Filestat {1774dev: 1,1775ino: metadata_hash.lower,1776filetype,1777nlink: stat.link_count,1778size: stat.size,1779atim: datetime_to_timestamp(stat.data_access_timestamp),1780mtim: datetime_to_timestamp(stat.data_modification_timestamp),1781ctim: datetime_to_timestamp(stat.status_change_timestamp),1782};1783Ok(())1784})1785}1786}17871788/// Adjust the timestamps of a file or directory.1789/// Note: This is similar to `utimensat` in POSIX.1790#[unsafe(no_mangle)]1791pub unsafe extern "C" fn path_filestat_set_times(1792fd: Fd,1793flags: Lookupflags,1794path_ptr: *const u8,1795path_len: usize,1796atim: Timestamp,1797mtim: Timestamp,1798fst_flags: Fstflags,1799) -> Errno {1800cfg_filesystem_available! {1801let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1802let at_flags = at_flags_from_lookupflags(flags);18031804State::with(|state| {1805let atim = systimespec(1806fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,1807atim,1808fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,1809)?;1810let mtim = systimespec(1811fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,1812mtim,1813fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,1814)?;18151816let ds = state.descriptors();1817let file = ds.get_dir(fd)?;1818file.fd.set_times_at(at_flags, path, atim, mtim)?;1819Ok(())1820})1821}1822}18231824/// Create a hard link.1825/// Note: This is similar to `linkat` in POSIX.1826#[unsafe(no_mangle)]1827pub unsafe extern "C" fn path_link(1828old_fd: Fd,1829old_flags: Lookupflags,1830old_path_ptr: *const u8,1831old_path_len: usize,1832new_fd: Fd,1833new_path_ptr: *const u8,1834new_path_len: usize,1835) -> Errno {1836cfg_filesystem_available! {1837let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };1838let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };1839let at_flags = at_flags_from_lookupflags(old_flags);18401841State::with(|state| {1842let ds = state.descriptors();1843let old = &ds.get_dir(old_fd)?.fd;1844let new = &ds.get_dir(new_fd)?.fd;1845old.link_at(at_flags, old_path, new, new_path)?;1846Ok(())1847})1848}1849}18501851/// Open a file or directory.1852/// The returned file descriptor is not guaranteed to be the lowest-numbered1853/// file descriptor not currently open; it is randomized to prevent1854/// applications from depending on making assumptions about indexes, since this1855/// is error-prone in multi-threaded contexts. The returned file descriptor is1856/// guaranteed to be less than 2**31.1857/// Note: This is similar to `openat` in POSIX.1858#[unsafe(no_mangle)]1859pub unsafe extern "C" fn path_open(1860fd: Fd,1861dirflags: Lookupflags,1862path_ptr: *const u8,1863path_len: usize,1864oflags: Oflags,1865fs_rights_base: Rights,1866fs_rights_inheriting: Rights,1867fdflags: Fdflags,1868opened_fd: &mut Fd,1869) -> Errno {1870cfg_filesystem_available! {1871let _ = fs_rights_inheriting;18721873let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1874let at_flags = at_flags_from_lookupflags(dirflags);1875let o_flags = o_flags_from_oflags(oflags);1876let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);1877let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND;18781879#[cfg(feature = "proxy")]1880return wasi::ERRNO_NOTSUP;18811882#[cfg(not(feature = "proxy"))]1883State::with(|state| {1884let result = state1885.descriptors()1886.get_dir(fd)?1887.fd1888.open_at(at_flags, path, o_flags, flags)?;1889let descriptor_type = result.get_type()?;1890let desc = Descriptor::Streams(Streams {1891input: OnceCell::new(),1892output: OnceCell::new(),1893type_: StreamType::File(File {1894fd: result,1895descriptor_type,1896position: Cell::new(0),1897append,1898blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 {1899BlockingMode::Blocking1900} else {1901BlockingMode::NonBlocking1902},1903preopen_name_len: None,1904}),1905});19061907let fd = state.descriptors_mut().open(desc)?;1908*opened_fd = fd;1909Ok(())1910})1911}1912}19131914/// Read the contents of a symbolic link.1915/// Note: This is similar to `readlinkat` in POSIX.1916#[unsafe(no_mangle)]1917pub unsafe extern "C" fn path_readlink(1918fd: Fd,1919path_ptr: *const u8,1920path_len: usize,1921buf: *mut u8,1922buf_len: Size,1923bufused: &mut Size,1924) -> Errno {1925cfg_filesystem_available! {1926let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };19271928State::with(|state| {1929// If the user gave us a buffer shorter than `PATH_MAX`, it may not be1930// long enough to accept the actual path. `cabi_realloc` can't fail,1931// so instead we handle this case specially.1932let use_state_buf = buf_len < PATH_MAX;19331934let ds = state.descriptors();1935let file = ds.get_dir(fd)?;1936let path = if use_state_buf {1937state1938.with_one_temporary_alloc( || {1939file.fd.readlink_at(path)1940})?1941} else {1942state1943.with_one_import_alloc(buf, buf_len, || file.fd.readlink_at(path))?1944};19451946if use_state_buf {1947// Preview1 follows POSIX in truncating the returned path if it1948// doesn't fit.1949let len = min(path.len(), buf_len);1950unsafe {1951ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);1952}1953*bufused = len;1954} else {1955*bufused = path.len();1956}19571958// The returned string's memory was allocated in `buf`, so don't separately1959// free it.1960forget(path);19611962Ok(())1963})1964}1965}19661967/// Remove a directory.1968/// Return `errno::notempty` if the directory is not empty.1969/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.1970#[unsafe(no_mangle)]1971pub unsafe extern "C" fn path_remove_directory(1972fd: Fd,1973path_ptr: *const u8,1974path_len: usize,1975) -> Errno {1976cfg_filesystem_available! {1977let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };19781979State::with(|state| {1980let ds = state.descriptors();1981let file = ds.get_dir(fd)?;1982file.fd.remove_directory_at(path)?;1983Ok(())1984})1985}1986}19871988/// Rename a file or directory.1989/// Note: This is similar to `renameat` in POSIX.1990#[unsafe(no_mangle)]1991pub unsafe extern "C" fn path_rename(1992old_fd: Fd,1993old_path_ptr: *const u8,1994old_path_len: usize,1995new_fd: Fd,1996new_path_ptr: *const u8,1997new_path_len: usize,1998) -> Errno {1999cfg_filesystem_available! {2000let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };2001let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };20022003State::with(|state| {2004let ds = state.descriptors();2005let old = &ds.get_dir(old_fd)?.fd;2006let new = &ds.get_dir(new_fd)?.fd;2007old.rename_at(old_path, new, new_path)?;2008Ok(())2009})2010}2011}20122013/// Create a symbolic link.2014/// Note: This is similar to `symlinkat` in POSIX.2015#[unsafe(no_mangle)]2016pub unsafe extern "C" fn path_symlink(2017old_path_ptr: *const u8,2018old_path_len: usize,2019fd: Fd,2020new_path_ptr: *const u8,2021new_path_len: usize,2022) -> Errno {2023cfg_filesystem_available! {2024let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };2025let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };20262027State::with(|state| {2028let ds = state.descriptors();2029let file = ds.get_dir(fd)?;2030file.fd.symlink_at(old_path, new_path)?;2031Ok(())2032})2033}2034}20352036/// Unlink a file.2037/// Return `errno::isdir` if the path refers to a directory.2038/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.2039#[unsafe(no_mangle)]2040pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno {2041cfg_filesystem_available! {2042let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };20432044State::with(|state| {2045let ds = state.descriptors();2046let file = ds.get_dir(fd)?;2047file.fd.unlink_file_at(path)?;2048Ok(())2049})2050}2051}20522053struct Pollables {2054pointer: *mut Pollable,2055index: usize,2056length: usize,2057}20582059impl Pollables {2060unsafe fn push(&mut self, pollable: Pollable) {2061assert!(self.index < self.length);2062// Use `ptr::write` instead of `*... = pollable` because `ptr::write`2063// doesn't call drop on the old memory.2064unsafe {2065self.pointer.add(self.index).write(pollable);2066}2067self.index += 1;2068}2069}20702071// We create new pollable handles for each `poll_oneoff` call, so drop them all2072// after the call.2073impl Drop for Pollables {2074fn drop(&mut self) {2075while self.index != 0 {2076self.index -= 1;2077unsafe {2078core::ptr::drop_in_place(self.pointer.add(self.index));2079}2080}2081}2082}20832084/// Concurrently poll for the occurrence of a set of events.2085#[unsafe(no_mangle)]2086pub unsafe extern "C" fn poll_oneoff(2087r#in: *const Subscription,2088out: *mut Event,2089nsubscriptions: Size,2090nevents: &mut Size,2091) -> Errno {2092*nevents = 0;20932094let subscriptions = unsafe { slice::from_raw_parts(r#in, nsubscriptions) };20952096// We're going to split the `nevents` buffer into two non-overlapping2097// buffers: one to store the pollable handles, and the other to store2098// the bool results.2099//2100// First, we assert that this is possible:2101assert!(align_of::<Event>() >= align_of::<Pollable>());2102assert!(align_of::<Pollable>() >= align_of::<u32>());2103assert!(2104nsubscriptions2105.checked_mul(size_of::<Event>())2106.trapping_unwrap()2107>= nsubscriptions2108.checked_mul(size_of::<Pollable>())2109.trapping_unwrap()2110.checked_add(2111nsubscriptions2112.checked_mul(size_of::<u32>())2113.trapping_unwrap()2114)2115.trapping_unwrap()2116);2117// Store the pollable handles at the beginning, and the bool results at the2118// end, so that we don't clobber the bool results when writing the events.2119let pollables = out as *mut c_void as *mut Pollable;2120let results = unsafe { out.add(nsubscriptions).cast::<u32>().sub(nsubscriptions) };21212122// Indefinite sleeping is not supported in preview1.2123if nsubscriptions == 0 {2124return ERRNO_INVAL;2125}21262127State::with(|state| {2128const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw();2129const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw();2130const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw();21312132let mut pollables = Pollables {2133pointer: pollables,2134index: 0,2135length: nsubscriptions,2136};21372138for subscription in subscriptions {2139let pollable = match subscription.u.tag {2140EVENTTYPE_CLOCK => {2141let clock = unsafe { &subscription.u.u.clock };2142let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME)2143== SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME;2144match clock.id {2145CLOCKID_REALTIME => {2146let timeout = if absolute {2147// Convert `clock.timeout` to `Datetime`.2148let mut datetime = wall_clock::Datetime {2149seconds: clock.timeout / 1_000_000_000,2150nanoseconds: (clock.timeout % 1_000_000_000) as _,2151};21522153// Subtract `now`.2154let now = wall_clock::now();2155datetime.seconds -= now.seconds;2156if datetime.nanoseconds < now.nanoseconds {2157datetime.seconds -= 1;2158datetime.nanoseconds += 1_000_000_000;2159}2160datetime.nanoseconds -= now.nanoseconds;21612162// Convert to nanoseconds.2163let nanos = datetime2164.seconds2165.checked_mul(1_000_000_000)2166.ok_or(ERRNO_OVERFLOW)?;2167nanos2168.checked_add(datetime.nanoseconds.into())2169.ok_or(ERRNO_OVERFLOW)?2170} else {2171clock.timeout2172};21732174monotonic_clock::subscribe_duration(timeout)2175}21762177CLOCKID_MONOTONIC => {2178if absolute {2179monotonic_clock::subscribe_instant(clock.timeout)2180} else {2181monotonic_clock::subscribe_duration(clock.timeout)2182}2183}21842185_ => return Err(ERRNO_INVAL),2186}2187}21882189EVENTTYPE_FD_READ => state2190.descriptors()2191.get_read_stream(unsafe { subscription.u.u.fd_read.file_descriptor })2192.map(|stream| stream.subscribe())?,21932194EVENTTYPE_FD_WRITE => state2195.descriptors()2196.get_write_stream(unsafe { subscription.u.u.fd_write.file_descriptor })2197.map(|stream| stream.subscribe())?,21982199_ => return Err(ERRNO_INVAL),2200};2201unsafe {2202pollables.push(pollable);2203}2204}22052206#[link(wasm_import_module = "wasi:io/[email protected]")]2207unsafe extern "C" {2208#[link_name = "poll"]2209fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList);2210}2211let mut ready_list = ReadyList {2212base: std::ptr::null(),2213len: 0,2214};22152216state.with_one_import_alloc(2217results.cast(),2218nsubscriptions2219.checked_mul(size_of::<u32>())2220.trapping_unwrap(),2221|| unsafe {2222poll_import(2223pollables.pointer,2224pollables.length,2225&mut ready_list as *mut _,2226);2227},2228);22292230assert!(ready_list.len <= nsubscriptions);2231assert_eq!(ready_list.base, results as *const u32);22322233drop(pollables);22342235let ready = unsafe { std::slice::from_raw_parts(ready_list.base, ready_list.len) };22362237let mut count = 0;22382239for subscription in ready {2240let subscription = unsafe { *subscriptions.as_ptr().add(*subscription as usize) };22412242let type_;22432244let (error, nbytes, flags) = match subscription.u.tag {2245EVENTTYPE_CLOCK => {2246type_ = wasi::EVENTTYPE_CLOCK;2247(ERRNO_SUCCESS, 0, 0)2248}22492250EVENTTYPE_FD_READ => {2251type_ = wasi::EVENTTYPE_FD_READ;2252let ds = state.descriptors();2253let desc = ds2254.get(unsafe { subscription.u.u.fd_read.file_descriptor })2255.trapping_unwrap();2256match desc {2257Descriptor::Streams(streams) => match &streams.type_ {2258#[cfg(not(feature = "proxy"))]2259StreamType::File(file) => match file.fd.stat() {2260Ok(stat) => {2261let nbytes = stat.size.saturating_sub(file.position.get());2262(2263ERRNO_SUCCESS,2264nbytes,2265if nbytes == 0 {2266EVENTRWFLAGS_FD_READWRITE_HANGUP2267} else {226802269},2270)2271}2272Err(e) => (e.into(), 1, 0),2273},2274StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),2275},2276_ => unreachable!(),2277}2278}2279EVENTTYPE_FD_WRITE => {2280type_ = wasi::EVENTTYPE_FD_WRITE;2281let ds = state.descriptors();2282let desc = ds2283.get(unsafe { subscription.u.u.fd_write.file_descriptor })2284.trapping_unwrap();2285match desc {2286Descriptor::Streams(streams) => match &streams.type_ {2287#[cfg(not(feature = "proxy"))]2288StreamType::File(_) => (ERRNO_SUCCESS, 1, 0),2289StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),2290},2291_ => unreachable!(),2292}2293}22942295_ => unreachable!(),2296};22972298unsafe {2299*out.add(count) = Event {2300userdata: subscription.userdata,2301error,2302type_,2303fd_readwrite: EventFdReadwrite { nbytes, flags },2304};2305}23062307count += 1;2308}23092310*nevents = count;23112312Ok(())2313})2314}23152316/// Terminate the process normally. An exit code of 0 indicates successful2317/// termination of the program. The meanings of other values is dependent on2318/// the environment.2319#[unsafe(no_mangle)]2320pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! {2321#[cfg(feature = "proxy")]2322{2323unreachable!("no other implementation available in proxy world");2324}2325#[cfg(not(feature = "proxy"))]2326{2327let status = if rval == 0 { Ok(()) } else { Err(()) };2328crate::bindings::wasi::cli::exit::exit(status); // does not return2329unreachable!("host exit implementation didn't exit!") // actually unreachable2330}2331}23322333/// Send a signal to the process of the calling thread.2334/// Note: This is similar to `raise` in POSIX.2335#[unsafe(no_mangle)]2336pub unsafe extern "C" fn proc_raise(_sig: Signal) -> Errno {2337unreachable!()2338}23392340/// Temporarily yield execution of the calling thread.2341/// Note: This is similar to `sched_yield` in POSIX.2342#[unsafe(no_mangle)]2343pub unsafe extern "C" fn sched_yield() -> Errno {2344// TODO: This is not yet covered in Preview2.23452346ERRNO_SUCCESS2347}23482349/// Write high-quality random data into a buffer.2350/// This function blocks when the implementation is unable to immediately2351/// provide sufficient high-quality random data.2352/// This function may execute slowly, so when large mounts of random data are2353/// required, it's advisable to use this function to seed a pseudo-random2354/// number generator, rather than to provide the random data directly.2355#[unsafe(no_mangle)]2356pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno {2357if matches!(2358unsafe { get_allocation_state() },2359AllocationState::StackAllocated | AllocationState::StateAllocated2360) {2361State::with(|state| {2362assert_eq!(buf_len as u32 as Size, buf_len);2363let result = state2364.with_one_import_alloc(buf, buf_len, || random::get_random_bytes(buf_len as u64));2365assert_eq!(result.as_ptr(), buf);23662367// The returned buffer's memory was allocated in `buf`, so don't separately2368// free it.2369forget(result);23702371Ok(())2372})2373} else {2374ERRNO_SUCCESS2375}2376}23772378/// Accept a new incoming connection.2379/// Note: This is similar to `accept` in POSIX.2380#[unsafe(no_mangle)]2381pub unsafe extern "C" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno {2382unreachable!()2383}23842385/// Receive a message from a socket.2386/// Note: This is similar to `recv` in POSIX, though it also supports reading2387/// the data into multiple buffers in the manner of `readv`.2388#[unsafe(no_mangle)]2389pub unsafe extern "C" fn sock_recv(2390_fd: Fd,2391_ri_data_ptr: *const Iovec,2392_ri_data_len: usize,2393_ri_flags: Riflags,2394_ro_datalen: *mut Size,2395_ro_flags: *mut Roflags,2396) -> Errno {2397unreachable!()2398}23992400/// Send a message on a socket.2401/// Note: This is similar to `send` in POSIX, though it also supports writing2402/// the data from multiple buffers in the manner of `writev`.2403#[unsafe(no_mangle)]2404pub unsafe extern "C" fn sock_send(2405_fd: Fd,2406_si_data_ptr: *const Ciovec,2407_si_data_len: usize,2408_si_flags: Siflags,2409_so_datalen: *mut Size,2410) -> Errno {2411unreachable!()2412}24132414/// Shut down socket send and receive channels.2415/// Note: This is similar to `shutdown` in POSIX.2416#[unsafe(no_mangle)]2417pub unsafe extern "C" fn sock_shutdown(fd: Fd, _how: Sdflags) -> Errno {2418State::with(|state| {2419state2420.descriptors_mut()2421.get_stream_with_error_mut(fd, wasi::ERRNO_BADF)?;2422Err(wasi::ERRNO_NOTSOCK)2423})2424}24252426#[cfg(not(feature = "proxy"))]2427fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {2428match datetime {2429Some(datetime) => u64::from(datetime.nanoseconds)2430.saturating_add(datetime.seconds.saturating_mul(1_000_000_000)),2431None => 0,2432}2433}24342435#[cfg(not(feature = "proxy"))]2436fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {2437if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW {2438filesystem::PathFlags::SYMLINK_FOLLOW2439} else {2440filesystem::PathFlags::empty()2441}2442}24432444#[cfg(not(feature = "proxy"))]2445fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {2446let mut o_flags = filesystem::OpenFlags::empty();2447if flags & OFLAGS_CREAT == OFLAGS_CREAT {2448o_flags |= filesystem::OpenFlags::CREATE;2449}2450if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY {2451o_flags |= filesystem::OpenFlags::DIRECTORY;2452}2453if flags & OFLAGS_EXCL == OFLAGS_EXCL {2454o_flags |= filesystem::OpenFlags::EXCLUSIVE;2455}2456if flags & OFLAGS_TRUNC == OFLAGS_TRUNC {2457o_flags |= filesystem::OpenFlags::TRUNCATE;2458}2459o_flags2460}24612462#[cfg(not(feature = "proxy"))]2463fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags {2464let mut flags = filesystem::DescriptorFlags::empty();2465if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ {2466flags |= filesystem::DescriptorFlags::READ;2467}2468if rights & wasi::RIGHTS_FD_WRITE == wasi::RIGHTS_FD_WRITE {2469flags |= filesystem::DescriptorFlags::WRITE;2470}2471if fdflags & wasi::FDFLAGS_SYNC == wasi::FDFLAGS_SYNC {2472flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;2473}2474if fdflags & wasi::FDFLAGS_DSYNC == wasi::FDFLAGS_DSYNC {2475flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;2476}2477if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC {2478flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;2479}2480flags2481}24822483#[cfg(not(feature = "proxy"))]2484impl From<filesystem::ErrorCode> for Errno {2485#[inline(never)] // Disable inlining as this is bulky and relatively cold.2486fn from(err: filesystem::ErrorCode) -> Errno {2487match err {2488// Use a black box to prevent the optimizer from generating a2489// lookup table, which would require a static initializer.2490filesystem::ErrorCode::Access => black_box(ERRNO_ACCES),2491filesystem::ErrorCode::WouldBlock => ERRNO_AGAIN,2492filesystem::ErrorCode::Already => ERRNO_ALREADY,2493filesystem::ErrorCode::BadDescriptor => ERRNO_BADF,2494filesystem::ErrorCode::Busy => ERRNO_BUSY,2495filesystem::ErrorCode::Deadlock => ERRNO_DEADLK,2496filesystem::ErrorCode::Quota => ERRNO_DQUOT,2497filesystem::ErrorCode::Exist => ERRNO_EXIST,2498filesystem::ErrorCode::FileTooLarge => ERRNO_FBIG,2499filesystem::ErrorCode::IllegalByteSequence => ERRNO_ILSEQ,2500filesystem::ErrorCode::InProgress => ERRNO_INPROGRESS,2501filesystem::ErrorCode::Interrupted => ERRNO_INTR,2502filesystem::ErrorCode::Invalid => ERRNO_INVAL,2503filesystem::ErrorCode::Io => ERRNO_IO,2504filesystem::ErrorCode::IsDirectory => ERRNO_ISDIR,2505filesystem::ErrorCode::Loop => ERRNO_LOOP,2506filesystem::ErrorCode::TooManyLinks => ERRNO_MLINK,2507filesystem::ErrorCode::MessageSize => ERRNO_MSGSIZE,2508filesystem::ErrorCode::NameTooLong => ERRNO_NAMETOOLONG,2509filesystem::ErrorCode::NoDevice => ERRNO_NODEV,2510filesystem::ErrorCode::NoEntry => ERRNO_NOENT,2511filesystem::ErrorCode::NoLock => ERRNO_NOLCK,2512filesystem::ErrorCode::InsufficientMemory => ERRNO_NOMEM,2513filesystem::ErrorCode::InsufficientSpace => ERRNO_NOSPC,2514filesystem::ErrorCode::Unsupported => ERRNO_NOTSUP,2515filesystem::ErrorCode::NotDirectory => ERRNO_NOTDIR,2516filesystem::ErrorCode::NotEmpty => ERRNO_NOTEMPTY,2517filesystem::ErrorCode::NotRecoverable => ERRNO_NOTRECOVERABLE,2518filesystem::ErrorCode::NoTty => ERRNO_NOTTY,2519filesystem::ErrorCode::NoSuchDevice => ERRNO_NXIO,2520filesystem::ErrorCode::Overflow => ERRNO_OVERFLOW,2521filesystem::ErrorCode::NotPermitted => ERRNO_PERM,2522filesystem::ErrorCode::Pipe => ERRNO_PIPE,2523filesystem::ErrorCode::ReadOnly => ERRNO_ROFS,2524filesystem::ErrorCode::InvalidSeek => ERRNO_SPIPE,2525filesystem::ErrorCode::TextFileBusy => ERRNO_TXTBSY,2526filesystem::ErrorCode::CrossDevice => ERRNO_XDEV,2527}2528}2529}25302531#[cfg(not(feature = "proxy"))]2532impl From<filesystem::DescriptorType> for wasi::Filetype {2533fn from(ty: filesystem::DescriptorType) -> wasi::Filetype {2534match ty {2535filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE,2536filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY,2537filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE,2538filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE,2539// preview1 never had a FIFO code.2540filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN,2541// TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and2542// FILETYPE_SOCKET_DGRAM.2543filesystem::DescriptorType::Socket => unreachable!(),2544filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK,2545filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN,2546}2547}2548}25492550#[derive(Clone, Copy)]2551pub enum BlockingMode {2552NonBlocking,2553Blocking,2554}25552556impl BlockingMode {2557// note: these methods must take self, not &self, to avoid rustc creating a constant2558// out of a BlockingMode literal that it places in .romem, creating a data section and2559// breaking our fragile linking scheme2560fn read(2561self,2562input_stream: &streams::InputStream,2563read_len: u64,2564) -> Result<Vec<u8>, streams::StreamError> {2565match self {2566BlockingMode::NonBlocking => input_stream.read(read_len),2567BlockingMode::Blocking => input_stream.blocking_read(read_len),2568}2569}2570fn write(2571self,2572output_stream: &streams::OutputStream,2573mut bytes: &[u8],2574) -> Result<usize, Errno> {2575match self {2576BlockingMode::Blocking => {2577let total = bytes.len();2578loop {2579let len = bytes.len().min(4096);2580let (chunk, rest) = bytes.split_at(len);2581bytes = rest;2582match output_stream.blocking_write_and_flush(chunk) {2583Ok(()) if bytes.is_empty() => break,2584Ok(()) => {}2585Err(streams::StreamError::Closed) => return Err(ERRNO_IO),2586Err(streams::StreamError::LastOperationFailed(e)) => {2587return Err(stream_error_to_errno(e));2588}2589}2590}2591Ok(total)2592}25932594BlockingMode::NonBlocking => {2595let permit = match output_stream.check_write() {2596Ok(n) => n,2597Err(streams::StreamError::Closed) => 0,2598Err(streams::StreamError::LastOperationFailed(e)) => {2599return Err(stream_error_to_errno(e));2600}2601};26022603let len = bytes.len().min(permit as usize);26042605match output_stream.write(&bytes[..len]) {2606Ok(_) => {}2607Err(streams::StreamError::Closed) => return Ok(0),2608Err(streams::StreamError::LastOperationFailed(e)) => {2609return Err(stream_error_to_errno(e));2610}2611}26122613match output_stream.blocking_flush() {2614Ok(_) => {}2615Err(streams::StreamError::Closed) => return Ok(0),2616Err(streams::StreamError::LastOperationFailed(e)) => {2617return Err(stream_error_to_errno(e));2618}2619}26202621Ok(len)2622}2623}2624}2625}26262627#[repr(C)]2628#[cfg(not(feature = "proxy"))]2629pub struct File {2630/// The handle to the preview2 descriptor that this file is referencing.2631fd: filesystem::Descriptor,26322633/// The descriptor type, as supplied by filesystem::get_type at opening2634descriptor_type: filesystem::DescriptorType,26352636/// The current-position pointer.2637position: Cell<filesystem::Filesize>,26382639/// In append mode, all writes append to the file.2640append: bool,26412642/// In blocking mode, read and write calls dispatch to blocking_read and2643/// blocking_check_write on the underlying streams. When false, read and write2644/// dispatch to stream's plain read and check_write.2645blocking_mode: BlockingMode,26462647/// TODO2648preopen_name_len: Option<NonZeroUsize>,2649}26502651#[cfg(not(feature = "proxy"))]2652impl File {2653fn is_dir(&self) -> bool {2654match self.descriptor_type {2655filesystem::DescriptorType::Directory => true,2656_ => false,2657}2658}2659}26602661const PAGE_SIZE: usize = 65536;26622663/// The maximum path length. WASI doesn't explicitly guarantee this, but all2664/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this2665/// polyfill.2666const PATH_MAX: usize = 4096;26672668/// Maximum number of bytes to cache for a `wasi::Dirent` plus its path name.2669const DIRENT_CACHE: usize = 256;26702671/// A canary value to detect memory corruption within `State`.2672const MAGIC: u32 = u32::from_le_bytes(*b"ugh!");26732674#[repr(C)] // used for now to keep magic1 and magic2 at the start and end2675struct State {2676/// A canary constant value located at the beginning of this structure to2677/// try to catch memory corruption coming from the bottom.2678magic1: u32,26792680/// Used to coordinate allocations of `cabi_import_realloc`2681import_alloc: Cell<ImportAlloc>,26822683/// Storage of mapping from preview1 file descriptors to preview2 file2684/// descriptors.2685///2686/// Do not use this member directly - use State::descriptors() to ensure2687/// lazy initialization happens.2688descriptors: RefCell<Option<Descriptors>>,26892690/// TODO2691temporary_data: UnsafeCell<MaybeUninit<[u8; temporary_data_size()]>>,26922693/// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path2694/// name that didn't fit into the caller's buffer.2695#[cfg(not(feature = "proxy"))]2696dirent_cache: DirentCache,26972698/// The string `..` for use by the directory iterator.2699#[cfg(not(feature = "proxy"))]2700dotdot: [UnsafeCell<u8>; 2],27012702/// Another canary constant located at the end of the structure to catch2703/// memory corruption coming from the bottom.2704magic2: u32,2705}27062707#[cfg(not(feature = "proxy"))]2708struct DirentCache {2709stream: Cell<Option<DirectoryEntryStream>>,2710for_fd: Cell<wasi::Fd>,2711cookie: Cell<wasi::Dircookie>,2712cached_dirent: Cell<wasi::Dirent>,2713path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,2714}27152716#[cfg(not(feature = "proxy"))]2717struct DirectoryEntryStream(filesystem::DirectoryEntryStream);27182719#[repr(C)]2720pub struct WasmStr {2721ptr: *const u8,2722len: usize,2723}27242725#[repr(C)]2726pub struct WasmStrList {2727base: *const WasmStr,2728len: usize,2729}27302731#[repr(C)]2732pub struct StrTuple {2733key: WasmStr,2734value: WasmStr,2735}27362737#[derive(Copy, Clone)]2738#[repr(C)]2739pub struct StrTupleList {2740base: *const StrTuple,2741len: usize,2742}27432744#[derive(Copy, Clone)]2745#[repr(C)]2746pub struct ReadyList {2747base: *const u32,2748len: usize,2749}27502751const fn temporary_data_size() -> usize {2752// The total size of the struct should be a page, so start there2753let mut start = PAGE_SIZE;27542755// Remove big chunks of the struct for its various fields.2756start -= size_of::<Descriptors>();2757#[cfg(not(feature = "proxy"))]2758{2759start -= size_of::<DirentCache>();2760}27612762// Remove miscellaneous metadata also stored in state.2763let misc = if cfg!(feature = "proxy") { 8 } else { 10 };2764start -= misc * size_of::<usize>();27652766// Everything else is the `command_data` allocation.2767start2768}27692770// Statically assert that the `State` structure is the size of a wasm page. This2771// mostly guarantees that it's not larger than one page which is relied upon2772// below.2773#[cfg(target_arch = "wasm32")]2774const _: () = {2775let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];2776};27772778#[expect(unused, reason = "not used in all configurations")]2779#[repr(i32)]2780enum AllocationState {2781StackUnallocated,2782StackAllocating,2783StackAllocated,2784StateAllocating,2785StateAllocated,2786}27872788#[expect(improper_ctypes, reason = "types behind pointers")]2789unsafe extern "C" {2790fn get_state_ptr() -> *mut State;2791fn set_state_ptr(state: *mut State);2792fn get_allocation_state() -> AllocationState;2793fn set_allocation_state(state: AllocationState);2794}27952796impl State {2797fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno {2798let state_ref = State::ptr();2799assert_eq!(state_ref.magic1, MAGIC);2800assert_eq!(state_ref.magic2, MAGIC);2801let ret = f(state_ref);2802match ret {2803Ok(()) => ERRNO_SUCCESS,2804Err(err) => err,2805}2806}28072808fn ptr() -> &'static State {2809unsafe {2810let mut ptr = get_state_ptr();2811if ptr.is_null() {2812ptr = State::new();2813set_state_ptr(ptr);2814}2815&*ptr2816}2817}28182819#[cold]2820fn new() -> *mut State {2821#[link(wasm_import_module = "__main_module__")]2822unsafe extern "C" {2823fn cabi_realloc(2824old_ptr: *mut u8,2825old_len: usize,2826align: usize,2827new_len: usize,2828) -> *mut u8;2829}28302831assert!(matches!(2832unsafe { get_allocation_state() },2833AllocationState::StackAllocated2834));28352836unsafe { set_allocation_state(AllocationState::StateAllocating) };28372838let ret = unsafe {2839cabi_realloc(2840ptr::null_mut(),28410,2842mem::align_of::<UnsafeCell<State>>(),2843mem::size_of::<UnsafeCell<State>>(),2844) as *mut State2845};28462847unsafe { set_allocation_state(AllocationState::StateAllocated) };28482849unsafe {2850Self::init(ret);2851}28522853ret2854}28552856#[cold]2857unsafe fn init(state: *mut State) {2858unsafe {2859state.write(State {2860magic1: MAGIC,2861magic2: MAGIC,2862import_alloc: Cell::new(ImportAlloc::None),2863descriptors: RefCell::new(None),2864temporary_data: UnsafeCell::new(MaybeUninit::uninit()),2865#[cfg(not(feature = "proxy"))]2866dirent_cache: DirentCache {2867stream: Cell::new(None),2868for_fd: Cell::new(0),2869cookie: Cell::new(wasi::DIRCOOKIE_START),2870cached_dirent: Cell::new(wasi::Dirent {2871d_next: 0,2872d_ino: 0,2873d_type: FILETYPE_UNKNOWN,2874d_namlen: 0,2875}),2876path_data: UnsafeCell::new(MaybeUninit::uninit()),2877},2878#[cfg(not(feature = "proxy"))]2879dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],2880});2881}2882}28832884/// Accessor for the descriptors member that ensures it is properly initialized2885fn descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a {2886let mut d = self2887.descriptors2888.try_borrow_mut()2889.unwrap_or_else(|_| unreachable!());2890if d.is_none() {2891*d = Some(Descriptors::new(self));2892}2893RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))2894}28952896/// Mut accessor for the descriptors member that ensures it is properly initialized2897fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a {2898let mut d = self2899.descriptors2900.try_borrow_mut()2901.unwrap_or_else(|_| unreachable!());2902if d.is_none() {2903*d = Some(Descriptors::new(self));2904}2905RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))2906}29072908unsafe fn temporary_alloc(&self) -> BumpAlloc {2909BumpAlloc {2910base: self.temporary_data.get().cast(),2911len: mem::size_of_val(&self.temporary_data),2912}2913}29142915/// Configure that `cabi_import_realloc` will allocate once from2916/// `self.temporary_data` for the duration of the closure `f`.2917///2918/// Panics if the import allocator is already configured.2919fn with_one_temporary_alloc<T>(&self, f: impl FnOnce() -> T) -> T {2920let alloc = unsafe { self.temporary_alloc() };2921self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).02922}29232924/// Configure that `cabi_import_realloc` will allocate once from2925/// `base` with at most `len` bytes for the duration of `f`.2926///2927/// Panics if the import allocator is already configured.2928fn with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T {2929let alloc = BumpAlloc { base, len };2930self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).02931}29322933/// Configures the `alloc` specified to be the allocator for2934/// `cabi_import_realloc` for the duration of `f`.2935///2936/// Panics if the import allocator is already configured.2937fn with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc) {2938match self.import_alloc.replace(alloc) {2939ImportAlloc::None => {}2940_ => unreachable!("import allocator already set"),2941}2942let r = f();2943let alloc = self.import_alloc.replace(ImportAlloc::None);2944(r, alloc)2945}2946}294729482949