Path: blob/main/crates/wasi-preview1-component-adapter/src/lib.rs
3109 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 wasip1::*;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)*) => {138wasip1::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(wasip1::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(wasip1::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 = wasip1::RIGHTS_PATH_CREATE_DIRECTORY802| wasip1::RIGHTS_PATH_CREATE_FILE803| wasip1::RIGHTS_PATH_LINK_SOURCE804| wasip1::RIGHTS_PATH_LINK_TARGET805| wasip1::RIGHTS_PATH_OPEN806| wasip1::RIGHTS_FD_READDIR807| wasip1::RIGHTS_PATH_READLINK808| wasip1::RIGHTS_PATH_RENAME_SOURCE809| wasip1::RIGHTS_PATH_RENAME_TARGET810| wasip1::RIGHTS_PATH_SYMLINK811| wasip1::RIGHTS_PATH_REMOVE_DIRECTORY812| wasip1::RIGHTS_PATH_UNLINK_FILE813| wasip1::RIGHTS_PATH_FILESTAT_GET814| wasip1::RIGHTS_PATH_FILESTAT_SET_TIMES815| wasip1::RIGHTS_FD_FILESTAT_GET816| wasip1::RIGHTS_FD_FILESTAT_SET_TIMES;817818let fs_rights_inheriting = fs_rights_base819| wasip1::RIGHTS_FD_DATASYNC820| wasip1::RIGHTS_FD_READ821| wasip1::RIGHTS_FD_SEEK822| wasip1::RIGHTS_FD_FDSTAT_SET_FLAGS823| wasip1::RIGHTS_FD_SYNC824| wasip1::RIGHTS_FD_TELL825| wasip1::RIGHTS_FD_WRITE826| wasip1::RIGHTS_FD_ADVISE827| wasip1::RIGHTS_FD_ALLOCATE828| wasip1::RIGHTS_FD_FILESTAT_GET829| wasip1::RIGHTS_FD_FILESTAT_SET_SIZE830| wasip1::RIGHTS_FD_FILESTAT_SET_TIMES831| wasip1::RIGHTS_POLL_FD_READWRITE;832833unsafe {834stat.write(Fdstat {835fs_filetype: wasip1::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 wasip1::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(wasip1::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 [`wasip1::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(wasip1::ERRNO_NOTSUP),955Descriptor::Closed(..) | Descriptor::Bad => Err(wasip1::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(wasip1::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(wasip1::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 {1334wasip1::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: wasip1::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 wasip1::DIRCOOKIE_START..cookie {1405match iter.next() {1406Some(Ok(_)) => {}1407Some(Err(e)) => return Err(e),1408None => {1409*bufused = 0;1410return Ok(());1411}1412}1413}1414}1415};14161417while buf.len() > 0 {1418let (dirent, name) = match iter.next() {1419Some(Ok(pair)) => pair,1420Some(Err(e)) => return Err(e),1421None => break,1422};14231424// Copy a `dirent` describing this entry into the destination `buf`,1425// truncating it if it doesn't fit entirely.1426let bytes = unsafe {1427slice::from_raw_parts(1428(&dirent as *const wasip1::Dirent).cast::<u8>(),1429size_of::<Dirent>(),1430)1431};1432let dirent_bytes_to_copy = buf.len().min(bytes.len());1433unsafe {1434ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr(), dirent_bytes_to_copy);1435}1436buf = &mut buf[dirent_bytes_to_copy..];14371438// Copy the name bytes into the output `buf`, truncating it if it1439// doesn't fit.1440//1441// Note that this might be a 0-byte copy if the `dirent` was1442// truncated or fit entirely into the destination.1443let name_bytes_to_copy = buf.len().min(name.len());1444unsafe {1445ptr::copy_nonoverlapping(1446name.as_ptr().cast(),1447buf.as_mut_ptr(),1448name_bytes_to_copy,1449);1450}14511452buf = &mut buf[name_bytes_to_copy..];14531454// If the buffer is empty then that means the value may be1455// truncated, so save the state of the iterator in our dirent cache1456// and return.1457//1458// Note that `cookie - 1` is stored here since `iter.cookie` stores1459// the address of the next item, and we're rewinding one item since1460// the current item is truncated and will want to resume from that1461// in the future.1462//1463// Additionally note that this caching step is skipped if the name1464// to store doesn't actually fit in the dirent cache's path storage.1465// In that case there's not much we can do and let the next call to1466// `fd_readdir` start from scratch.1467if buf.len() == 0 && name.len() <= DIRENT_CACHE {1468let DirectoryEntryIterator { stream, cookie, .. } = iter;1469state.dirent_cache.stream.set(Some(stream));1470state.dirent_cache.for_fd.set(fd);1471state.dirent_cache.cookie.set(cookie - 1);1472state.dirent_cache.cached_dirent.set(dirent);1473unsafe {1474ptr::copy(1475name.as_ptr().cast::<u8>(),1476(*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8,1477name.len(),1478);1479}1480break;1481}1482}14831484*bufused = buf_len - buf.len();1485Ok(())1486});14871488struct DirectoryEntryIterator<'a> {1489state: &'a State,1490use_cache: bool,1491cookie: Dircookie,1492stream: DirectoryEntryStream,1493dir_descriptor: &'a filesystem::Descriptor,1494}14951496impl<'a> Iterator for DirectoryEntryIterator<'a> {1497// Note the usage of `UnsafeCell<u8>` here to indicate that the data can1498// alias the storage within `state`.1499type Item = Result<(wasip1::Dirent, &'a [UnsafeCell<u8>]), Errno>;15001501fn next(&mut self) -> Option<Self::Item> {1502let current_cookie = self.cookie;15031504self.cookie += 1;15051506// Preview1 programs expect to see `.` and `..` in the traversal, but1507// Preview2 excludes them, so re-add them.1508match current_cookie {15090 => {1510let metadata_hash = match self.dir_descriptor.metadata_hash() {1511Ok(h) => h,1512Err(e) => return Some(Err(e.into())),1513};1514let dirent = wasip1::Dirent {1515d_next: self.cookie,1516d_ino: metadata_hash.lower,1517d_type: wasip1::FILETYPE_DIRECTORY,1518d_namlen: 1,1519};1520return Some(Ok((dirent, &self.state.dotdot[..1])));1521}15221 => {1523let dirent = wasip1::Dirent {1524d_next: self.cookie,1525d_ino: 0,1526d_type: wasip1::FILETYPE_DIRECTORY,1527d_namlen: 2,1528};1529return Some(Ok((dirent, &self.state.dotdot[..])));1530}1531_ => {}1532}15331534if self.use_cache {1535self.use_cache = false;1536return Some(unsafe {1537let dirent = self.state.dirent_cache.cached_dirent.as_ptr().read();1538let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr())1539.as_ptr()1540.cast();1541let buffer = slice::from_raw_parts(ptr, dirent.d_namlen as usize);1542Ok((dirent, buffer))1543});1544}1545let entry = self1546.state1547.with_one_temporary_alloc(|| self.stream.0.read_directory_entry());1548let entry = match entry {1549Ok(Some(entry)) => entry,1550Ok(None) => return None,1551Err(e) => return Some(Err(e.into())),1552};15531554let filesystem::DirectoryEntry { type_, name } = entry;1555let d_ino = self1556.dir_descriptor1557.metadata_hash_at(filesystem::PathFlags::empty(), &name)1558.map(|h| h.lower)1559.unwrap_or(0);1560let name = ManuallyDrop::new(name);1561let dirent = wasip1::Dirent {1562d_next: self.cookie,1563d_ino,1564d_namlen: u32::try_from(name.len()).trapping_unwrap(),1565d_type: type_.into(),1566};1567// Extend the lifetime of `name` to the `self.state` lifetime for1568// this iterator since the data for the name lives within state.1569let name = unsafe {1570assert_eq!(name.as_ptr(), self.state.temporary_data.get().cast());1571slice::from_raw_parts(name.as_ptr().cast(), name.len())1572};1573Some(Ok((dirent, name)))1574}1575}1576}15771578/// Atomically replace a file descriptor by renumbering another file descriptor.1579/// Due to the strong focus on thread safety, this environment does not provide1580/// a mechanism to duplicate or renumber a file descriptor to an arbitrary1581/// number, like `dup2()`. This would be prone to race conditions, as an actual1582/// file descriptor with the same number could be allocated by a different1583/// thread at the same time.1584/// This function provides a way to atomically renumber file descriptors, which1585/// would disappear if `dup2()` were to be removed entirely.1586#[unsafe(no_mangle)]1587pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno {1588State::with(|state| state.descriptors_mut().renumber(fd, to))1589}15901591/// Move the offset of a file descriptor.1592/// Note: This is similar to `lseek` in POSIX.1593#[unsafe(no_mangle)]1594pub unsafe extern "C" fn fd_seek(1595fd: Fd,1596offset: Filedelta,1597whence: Whence,1598newoffset: &mut Filesize,1599) -> Errno {1600cfg_filesystem_available! {1601State::with(|state| {1602let mut ds = state.descriptors_mut();1603let stream = ds.get_seekable_stream_mut(fd)?;16041605// Seeking only works on files.1606if let StreamType::File(file) = &mut stream.type_ {1607if let filesystem::DescriptorType::Directory = file.descriptor_type {1608// This isn't really the "right" errno, but it is consistient with wasmtime's1609// preview 1 tests.1610return Err(ERRNO_BADF);1611}1612let from = match whence {1613WHENCE_SET if offset >= 0 => offset,1614WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {1615Some(pos) if pos >= 0 => pos,1616_ => return Err(ERRNO_INVAL),1617},1618WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {1619Some(pos) if pos >= 0 => pos,1620_ => return Err(ERRNO_INVAL),1621},1622_ => return Err(ERRNO_INVAL),1623};1624drop(stream.input.take());1625drop(stream.output.take());1626file.position.set(from as filesystem::Filesize);1627*newoffset = from as filesystem::Filesize;1628Ok(())1629} else {1630Err(ERRNO_SPIPE)1631}1632})1633}1634}16351636/// Synchronize the data and metadata of a file to disk.1637/// Note: This is similar to `fsync` in POSIX.1638#[unsafe(no_mangle)]1639pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno {1640cfg_filesystem_available! {1641State::with(|state| {1642let ds = state.descriptors();1643let file = ds.get_file(fd)?;1644file.fd.sync()?;1645Ok(())1646})1647}1648}16491650/// Return the current offset of a file descriptor.1651/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.1652#[unsafe(no_mangle)]1653pub unsafe extern "C" fn fd_tell(fd: Fd, offset: &mut Filesize) -> Errno {1654cfg_filesystem_available! {1655State::with(|state| {1656let ds = state.descriptors();1657let file = ds.get_seekable_file(fd)?;1658*offset = file.position.get();1659Ok(())1660})1661}1662}16631664/// Write to a file descriptor.1665/// Note: This is similar to `writev` in POSIX.1666#[unsafe(no_mangle)]1667pub unsafe extern "C" fn fd_write(1668fd: Fd,1669mut iovs_ptr: *const Ciovec,1670mut iovs_len: usize,1671nwritten: &mut Size,1672) -> Errno {1673if !matches!(1674unsafe { get_allocation_state() },1675AllocationState::StackAllocated | AllocationState::StateAllocated1676) {1677*nwritten = 0;1678return ERRNO_IO;1679}16801681let bytes = unsafe {1682// Skip leading empty buffers.1683while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {1684iovs_ptr = iovs_ptr.add(1);1685iovs_len -= 1;1686}1687if iovs_len == 0 {1688*nwritten = 0;1689return ERRNO_SUCCESS;1690}1691let ptr = (*iovs_ptr).buf;1692let len = (*iovs_ptr).buf_len;1693slice::from_raw_parts(ptr, len)1694};16951696State::with(|state| {1697let ds = state.descriptors();1698match ds.get(fd)? {1699Descriptor::Streams(streams) => {1700let wasi_stream = streams.get_write_stream()?;17011702#[cfg(not(feature = "proxy"))]1703let nbytes = if let StreamType::File(file) = &streams.type_ {1704file.blocking_mode.write(wasi_stream, bytes)?1705} else {1706// Use blocking writes on non-file streams (stdout, stderr, as sockets1707// aren't currently used).1708BlockingMode::Blocking.write(wasi_stream, bytes)?1709};1710#[cfg(feature = "proxy")]1711let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;17121713// If this is a file, keep the current-position pointer up1714// to date. Note that for files that perform appending1715// writes this function will always update the current1716// position to the end of the file.1717//1718// NB: this isn't "atomic" as it doesn't necessarily account1719// for concurrent writes, but there's not much that can be1720// done about that.1721#[cfg(not(feature = "proxy"))]1722if let StreamType::File(file) = &streams.type_ {1723if file.append {1724file.position.set(file.fd.stat()?.size);1725} else {1726file.position.set(file.position.get() + nbytes as u64);1727}1728}17291730*nwritten = nbytes;1731Ok(())1732}1733Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),1734}1735})1736}17371738/// Create a directory.1739/// Note: This is similar to `mkdirat` in POSIX.1740#[unsafe(no_mangle)]1741pub unsafe extern "C" fn path_create_directory(1742fd: Fd,1743path_ptr: *const u8,1744path_len: usize,1745) -> Errno {1746cfg_filesystem_available! {1747let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };17481749State::with(|state| {1750let ds = state.descriptors();1751let file = ds.get_dir(fd)?;1752file.fd.create_directory_at(path)?;1753Ok(())1754})1755}1756}17571758/// Return the attributes of a file or directory.1759/// Note: This is similar to `stat` in POSIX.1760#[unsafe(no_mangle)]1761pub unsafe extern "C" fn path_filestat_get(1762fd: Fd,1763flags: Lookupflags,1764path_ptr: *const u8,1765path_len: usize,1766buf: &mut Filestat,1767) -> Errno {1768cfg_filesystem_available! {1769let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1770let at_flags = at_flags_from_lookupflags(flags);17711772State::with(|state| {1773let ds = state.descriptors();1774let file = ds.get_dir(fd)?;1775let stat = file.fd.stat_at(at_flags, path)?;1776let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;1777let filetype = stat.type_.into();1778*buf = Filestat {1779dev: 1,1780ino: metadata_hash.lower,1781filetype,1782nlink: stat.link_count,1783size: stat.size,1784atim: datetime_to_timestamp(stat.data_access_timestamp),1785mtim: datetime_to_timestamp(stat.data_modification_timestamp),1786ctim: datetime_to_timestamp(stat.status_change_timestamp),1787};1788Ok(())1789})1790}1791}17921793/// Adjust the timestamps of a file or directory.1794/// Note: This is similar to `utimensat` in POSIX.1795#[unsafe(no_mangle)]1796pub unsafe extern "C" fn path_filestat_set_times(1797fd: Fd,1798flags: Lookupflags,1799path_ptr: *const u8,1800path_len: usize,1801atim: Timestamp,1802mtim: Timestamp,1803fst_flags: Fstflags,1804) -> Errno {1805cfg_filesystem_available! {1806let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1807let at_flags = at_flags_from_lookupflags(flags);18081809State::with(|state| {1810let atim = systimespec(1811fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,1812atim,1813fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,1814)?;1815let mtim = systimespec(1816fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,1817mtim,1818fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,1819)?;18201821let ds = state.descriptors();1822let file = ds.get_dir(fd)?;1823file.fd.set_times_at(at_flags, path, atim, mtim)?;1824Ok(())1825})1826}1827}18281829/// Create a hard link.1830/// Note: This is similar to `linkat` in POSIX.1831#[unsafe(no_mangle)]1832pub unsafe extern "C" fn path_link(1833old_fd: Fd,1834old_flags: Lookupflags,1835old_path_ptr: *const u8,1836old_path_len: usize,1837new_fd: Fd,1838new_path_ptr: *const u8,1839new_path_len: usize,1840) -> Errno {1841cfg_filesystem_available! {1842let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };1843let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };1844let at_flags = at_flags_from_lookupflags(old_flags);18451846State::with(|state| {1847let ds = state.descriptors();1848let old = &ds.get_dir(old_fd)?.fd;1849let new = &ds.get_dir(new_fd)?.fd;1850old.link_at(at_flags, old_path, new, new_path)?;1851Ok(())1852})1853}1854}18551856/// Open a file or directory.1857/// The returned file descriptor is not guaranteed to be the lowest-numbered1858/// file descriptor not currently open; it is randomized to prevent1859/// applications from depending on making assumptions about indexes, since this1860/// is error-prone in multi-threaded contexts. The returned file descriptor is1861/// guaranteed to be less than 2**31.1862/// Note: This is similar to `openat` in POSIX.1863#[unsafe(no_mangle)]1864pub unsafe extern "C" fn path_open(1865fd: Fd,1866dirflags: Lookupflags,1867path_ptr: *const u8,1868path_len: usize,1869oflags: Oflags,1870fs_rights_base: Rights,1871fs_rights_inheriting: Rights,1872fdflags: Fdflags,1873opened_fd: &mut Fd,1874) -> Errno {1875cfg_filesystem_available! {1876let _ = fs_rights_inheriting;18771878let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };1879let at_flags = at_flags_from_lookupflags(dirflags);1880let o_flags = o_flags_from_oflags(oflags);1881let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);1882let append = fdflags & wasip1::FDFLAGS_APPEND == wasip1::FDFLAGS_APPEND;18831884#[cfg(feature = "proxy")]1885return wasip1::ERRNO_NOTSUP;18861887#[cfg(not(feature = "proxy"))]1888State::with(|state| {1889let result = state1890.descriptors()1891.get_dir(fd)?1892.fd1893.open_at(at_flags, path, o_flags, flags)?;1894let descriptor_type = result.get_type()?;1895let desc = Descriptor::Streams(Streams {1896input: OnceCell::new(),1897output: OnceCell::new(),1898type_: StreamType::File(File {1899fd: result,1900descriptor_type,1901position: Cell::new(0),1902append,1903blocking_mode: if fdflags & wasip1::FDFLAGS_NONBLOCK == 0 {1904BlockingMode::Blocking1905} else {1906BlockingMode::NonBlocking1907},1908preopen_name_len: None,1909}),1910});19111912let fd = state.descriptors_mut().open(desc)?;1913*opened_fd = fd;1914Ok(())1915})1916}1917}19181919/// Read the contents of a symbolic link.1920/// Note: This is similar to `readlinkat` in POSIX.1921#[unsafe(no_mangle)]1922pub unsafe extern "C" fn path_readlink(1923fd: Fd,1924path_ptr: *const u8,1925path_len: usize,1926buf: *mut u8,1927buf_len: Size,1928bufused: &mut Size,1929) -> Errno {1930cfg_filesystem_available! {1931let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };19321933State::with(|state| {1934// If the user gave us a buffer shorter than `PATH_MAX`, it may not be1935// long enough to accept the actual path. `cabi_realloc` can't fail,1936// so instead we handle this case specially.1937let use_state_buf = buf_len < PATH_MAX;19381939let ds = state.descriptors();1940let file = ds.get_dir(fd)?;1941let path = if use_state_buf {1942state1943.with_one_temporary_alloc( || {1944file.fd.readlink_at(path)1945})?1946} else {1947state1948.with_one_import_alloc(buf, buf_len, || file.fd.readlink_at(path))?1949};19501951if use_state_buf {1952// Preview1 follows POSIX in truncating the returned path if it1953// doesn't fit.1954let len = min(path.len(), buf_len);1955unsafe {1956ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);1957}1958*bufused = len;1959} else {1960*bufused = path.len();1961}19621963// The returned string's memory was allocated in `buf`, so don't separately1964// free it.1965forget(path);19661967Ok(())1968})1969}1970}19711972/// Remove a directory.1973/// Return `errno::notempty` if the directory is not empty.1974/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.1975#[unsafe(no_mangle)]1976pub unsafe extern "C" fn path_remove_directory(1977fd: Fd,1978path_ptr: *const u8,1979path_len: usize,1980) -> Errno {1981cfg_filesystem_available! {1982let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };19831984State::with(|state| {1985let ds = state.descriptors();1986let file = ds.get_dir(fd)?;1987file.fd.remove_directory_at(path)?;1988Ok(())1989})1990}1991}19921993/// Rename a file or directory.1994/// Note: This is similar to `renameat` in POSIX.1995#[unsafe(no_mangle)]1996pub unsafe extern "C" fn path_rename(1997old_fd: Fd,1998old_path_ptr: *const u8,1999old_path_len: usize,2000new_fd: Fd,2001new_path_ptr: *const u8,2002new_path_len: usize,2003) -> Errno {2004cfg_filesystem_available! {2005let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };2006let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };20072008State::with(|state| {2009let ds = state.descriptors();2010let old = &ds.get_dir(old_fd)?.fd;2011let new = &ds.get_dir(new_fd)?.fd;2012old.rename_at(old_path, new, new_path)?;2013Ok(())2014})2015}2016}20172018/// Create a symbolic link.2019/// Note: This is similar to `symlinkat` in POSIX.2020#[unsafe(no_mangle)]2021pub unsafe extern "C" fn path_symlink(2022old_path_ptr: *const u8,2023old_path_len: usize,2024fd: Fd,2025new_path_ptr: *const u8,2026new_path_len: usize,2027) -> Errno {2028cfg_filesystem_available! {2029let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };2030let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };20312032State::with(|state| {2033let ds = state.descriptors();2034let file = ds.get_dir(fd)?;2035file.fd.symlink_at(old_path, new_path)?;2036Ok(())2037})2038}2039}20402041/// Unlink a file.2042/// Return `errno::isdir` if the path refers to a directory.2043/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.2044#[unsafe(no_mangle)]2045pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno {2046cfg_filesystem_available! {2047let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };20482049State::with(|state| {2050let ds = state.descriptors();2051let file = ds.get_dir(fd)?;2052file.fd.unlink_file_at(path)?;2053Ok(())2054})2055}2056}20572058struct Pollables {2059pointer: *mut Pollable,2060index: usize,2061length: usize,2062}20632064impl Pollables {2065unsafe fn push(&mut self, pollable: Pollable) {2066assert!(self.index < self.length);2067// Use `ptr::write` instead of `*... = pollable` because `ptr::write`2068// doesn't call drop on the old memory.2069unsafe {2070self.pointer.add(self.index).write(pollable);2071}2072self.index += 1;2073}2074}20752076// We create new pollable handles for each `poll_oneoff` call, so drop them all2077// after the call.2078impl Drop for Pollables {2079fn drop(&mut self) {2080while self.index != 0 {2081self.index -= 1;2082unsafe {2083core::ptr::drop_in_place(self.pointer.add(self.index));2084}2085}2086}2087}20882089/// Concurrently poll for the occurrence of a set of events.2090#[unsafe(no_mangle)]2091pub unsafe extern "C" fn poll_oneoff(2092r#in: *const Subscription,2093out: *mut Event,2094nsubscriptions: Size,2095nevents: &mut Size,2096) -> Errno {2097*nevents = 0;20982099let subscriptions = unsafe { slice::from_raw_parts(r#in, nsubscriptions) };21002101// We're going to split the `nevents` buffer into two non-overlapping2102// buffers: one to store the pollable handles, and the other to store2103// the bool results.2104//2105// First, we assert that this is possible:2106assert!(align_of::<Event>() >= align_of::<Pollable>());2107assert!(align_of::<Pollable>() >= align_of::<u32>());2108assert!(2109nsubscriptions2110.checked_mul(size_of::<Event>())2111.trapping_unwrap()2112>= nsubscriptions2113.checked_mul(size_of::<Pollable>())2114.trapping_unwrap()2115.checked_add(2116nsubscriptions2117.checked_mul(size_of::<u32>())2118.trapping_unwrap()2119)2120.trapping_unwrap()2121);2122// Store the pollable handles at the beginning, and the bool results at the2123// end, so that we don't clobber the bool results when writing the events.2124let pollables = out as *mut c_void as *mut Pollable;2125let results = unsafe { out.add(nsubscriptions).cast::<u32>().sub(nsubscriptions) };21262127// Indefinite sleeping is not supported in preview1.2128if nsubscriptions == 0 {2129return ERRNO_INVAL;2130}21312132State::with(|state| {2133const EVENTTYPE_CLOCK: u8 = wasip1::EVENTTYPE_CLOCK.raw();2134const EVENTTYPE_FD_READ: u8 = wasip1::EVENTTYPE_FD_READ.raw();2135const EVENTTYPE_FD_WRITE: u8 = wasip1::EVENTTYPE_FD_WRITE.raw();21362137let mut pollables = Pollables {2138pointer: pollables,2139index: 0,2140length: nsubscriptions,2141};21422143for subscription in subscriptions {2144let pollable = match subscription.u.tag {2145EVENTTYPE_CLOCK => {2146let clock = unsafe { &subscription.u.u.clock };2147let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME)2148== SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME;2149match clock.id {2150CLOCKID_REALTIME => {2151let timeout = if absolute {2152// Convert `clock.timeout` to `Datetime`.2153let mut datetime = wall_clock::Datetime {2154seconds: clock.timeout / 1_000_000_000,2155nanoseconds: (clock.timeout % 1_000_000_000) as _,2156};21572158// Subtract `now`.2159let now = wall_clock::now();2160datetime.seconds -= now.seconds;2161if datetime.nanoseconds < now.nanoseconds {2162datetime.seconds -= 1;2163datetime.nanoseconds += 1_000_000_000;2164}2165datetime.nanoseconds -= now.nanoseconds;21662167// Convert to nanoseconds.2168let nanos = datetime2169.seconds2170.checked_mul(1_000_000_000)2171.ok_or(ERRNO_OVERFLOW)?;2172nanos2173.checked_add(datetime.nanoseconds.into())2174.ok_or(ERRNO_OVERFLOW)?2175} else {2176clock.timeout2177};21782179monotonic_clock::subscribe_duration(timeout)2180}21812182CLOCKID_MONOTONIC => {2183if absolute {2184monotonic_clock::subscribe_instant(clock.timeout)2185} else {2186monotonic_clock::subscribe_duration(clock.timeout)2187}2188}21892190_ => return Err(ERRNO_INVAL),2191}2192}21932194EVENTTYPE_FD_READ => state2195.descriptors()2196.get_read_stream(unsafe { subscription.u.u.fd_read.file_descriptor })2197.map(|stream| stream.subscribe())?,21982199EVENTTYPE_FD_WRITE => state2200.descriptors()2201.get_write_stream(unsafe { subscription.u.u.fd_write.file_descriptor })2202.map(|stream| stream.subscribe())?,22032204_ => return Err(ERRNO_INVAL),2205};2206unsafe {2207pollables.push(pollable);2208}2209}22102211#[link(wasm_import_module = "wasi:io/[email protected]")]2212unsafe extern "C" {2213#[link_name = "poll"]2214fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList);2215}2216let mut ready_list = ReadyList {2217base: std::ptr::null(),2218len: 0,2219};22202221state.with_one_import_alloc(2222results.cast(),2223nsubscriptions2224.checked_mul(size_of::<u32>())2225.trapping_unwrap(),2226|| unsafe {2227poll_import(2228pollables.pointer,2229pollables.length,2230&mut ready_list as *mut _,2231);2232},2233);22342235assert!(ready_list.len <= nsubscriptions);2236assert_eq!(ready_list.base, results as *const u32);22372238drop(pollables);22392240let ready = unsafe { std::slice::from_raw_parts(ready_list.base, ready_list.len) };22412242let mut count = 0;22432244for subscription in ready {2245let subscription = unsafe { *subscriptions.as_ptr().add(*subscription as usize) };22462247let type_;22482249let (error, nbytes, flags) = match subscription.u.tag {2250EVENTTYPE_CLOCK => {2251type_ = wasip1::EVENTTYPE_CLOCK;2252(ERRNO_SUCCESS, 0, 0)2253}22542255EVENTTYPE_FD_READ => {2256type_ = wasip1::EVENTTYPE_FD_READ;2257let ds = state.descriptors();2258let desc = ds2259.get(unsafe { subscription.u.u.fd_read.file_descriptor })2260.trapping_unwrap();2261match desc {2262Descriptor::Streams(streams) => match &streams.type_ {2263#[cfg(not(feature = "proxy"))]2264StreamType::File(file) => match file.fd.stat() {2265Ok(stat) => {2266let nbytes = stat.size.saturating_sub(file.position.get());2267(2268ERRNO_SUCCESS,2269nbytes,2270if nbytes == 0 {2271EVENTRWFLAGS_FD_READWRITE_HANGUP2272} else {227302274},2275)2276}2277Err(e) => (e.into(), 1, 0),2278},2279StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),2280},2281_ => unreachable!(),2282}2283}2284EVENTTYPE_FD_WRITE => {2285type_ = wasip1::EVENTTYPE_FD_WRITE;2286let ds = state.descriptors();2287let desc = ds2288.get(unsafe { subscription.u.u.fd_write.file_descriptor })2289.trapping_unwrap();2290match desc {2291Descriptor::Streams(streams) => match &streams.type_ {2292#[cfg(not(feature = "proxy"))]2293StreamType::File(_) => (ERRNO_SUCCESS, 1, 0),2294StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),2295},2296_ => unreachable!(),2297}2298}22992300_ => unreachable!(),2301};23022303unsafe {2304*out.add(count) = Event {2305userdata: subscription.userdata,2306error,2307type_,2308fd_readwrite: EventFdReadwrite { nbytes, flags },2309};2310}23112312count += 1;2313}23142315*nevents = count;23162317Ok(())2318})2319}23202321/// Terminate the process normally. An exit code of 0 indicates successful2322/// termination of the program. The meanings of other values is dependent on2323/// the environment.2324#[unsafe(no_mangle)]2325pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! {2326#[cfg(feature = "proxy")]2327{2328unreachable!("no other implementation available in proxy world");2329}2330#[cfg(not(feature = "proxy"))]2331{2332let status = if rval == 0 { Ok(()) } else { Err(()) };2333crate::bindings::wasi::cli::exit::exit(status); // does not return2334unreachable!("host exit implementation didn't exit!") // actually unreachable2335}2336}23372338/// Send a signal to the process of the calling thread.2339/// Note: This is similar to `raise` in POSIX.2340#[unsafe(no_mangle)]2341pub unsafe extern "C" fn proc_raise(_sig: Signal) -> Errno {2342unreachable!()2343}23442345/// Temporarily yield execution of the calling thread.2346/// Note: This is similar to `sched_yield` in POSIX.2347#[unsafe(no_mangle)]2348pub unsafe extern "C" fn sched_yield() -> Errno {2349// TODO: This is not yet covered in Preview2.23502351ERRNO_SUCCESS2352}23532354/// Write high-quality random data into a buffer.2355/// This function blocks when the implementation is unable to immediately2356/// provide sufficient high-quality random data.2357/// This function may execute slowly, so when large mounts of random data are2358/// required, it's advisable to use this function to seed a pseudo-random2359/// number generator, rather than to provide the random data directly.2360#[unsafe(no_mangle)]2361pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno {2362if matches!(2363unsafe { get_allocation_state() },2364AllocationState::StackAllocated | AllocationState::StateAllocated2365) {2366State::with(|state| {2367assert_eq!(buf_len as u32 as Size, buf_len);2368let result = state2369.with_one_import_alloc(buf, buf_len, || random::get_random_bytes(buf_len as u64));2370assert_eq!(result.as_ptr(), buf);23712372// The returned buffer's memory was allocated in `buf`, so don't separately2373// free it.2374forget(result);23752376Ok(())2377})2378} else {2379ERRNO_SUCCESS2380}2381}23822383/// Accept a new incoming connection.2384/// Note: This is similar to `accept` in POSIX.2385#[unsafe(no_mangle)]2386pub unsafe extern "C" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno {2387unreachable!()2388}23892390/// Receive a message from a socket.2391/// Note: This is similar to `recv` in POSIX, though it also supports reading2392/// the data into multiple buffers in the manner of `readv`.2393#[unsafe(no_mangle)]2394pub unsafe extern "C" fn sock_recv(2395_fd: Fd,2396_ri_data_ptr: *const Iovec,2397_ri_data_len: usize,2398_ri_flags: Riflags,2399_ro_datalen: *mut Size,2400_ro_flags: *mut Roflags,2401) -> Errno {2402unreachable!()2403}24042405/// Send a message on a socket.2406/// Note: This is similar to `send` in POSIX, though it also supports writing2407/// the data from multiple buffers in the manner of `writev`.2408#[unsafe(no_mangle)]2409pub unsafe extern "C" fn sock_send(2410_fd: Fd,2411_si_data_ptr: *const Ciovec,2412_si_data_len: usize,2413_si_flags: Siflags,2414_so_datalen: *mut Size,2415) -> Errno {2416unreachable!()2417}24182419/// Shut down socket send and receive channels.2420/// Note: This is similar to `shutdown` in POSIX.2421#[unsafe(no_mangle)]2422pub unsafe extern "C" fn sock_shutdown(fd: Fd, _how: Sdflags) -> Errno {2423State::with(|state| {2424state2425.descriptors_mut()2426.get_stream_with_error_mut(fd, wasip1::ERRNO_BADF)?;2427Err(wasip1::ERRNO_NOTSOCK)2428})2429}24302431#[cfg(not(feature = "proxy"))]2432fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {2433match datetime {2434Some(datetime) => u64::from(datetime.nanoseconds)2435.saturating_add(datetime.seconds.saturating_mul(1_000_000_000)),2436None => 0,2437}2438}24392440#[cfg(not(feature = "proxy"))]2441fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {2442if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW {2443filesystem::PathFlags::SYMLINK_FOLLOW2444} else {2445filesystem::PathFlags::empty()2446}2447}24482449#[cfg(not(feature = "proxy"))]2450fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {2451let mut o_flags = filesystem::OpenFlags::empty();2452if flags & OFLAGS_CREAT == OFLAGS_CREAT {2453o_flags |= filesystem::OpenFlags::CREATE;2454}2455if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY {2456o_flags |= filesystem::OpenFlags::DIRECTORY;2457}2458if flags & OFLAGS_EXCL == OFLAGS_EXCL {2459o_flags |= filesystem::OpenFlags::EXCLUSIVE;2460}2461if flags & OFLAGS_TRUNC == OFLAGS_TRUNC {2462o_flags |= filesystem::OpenFlags::TRUNCATE;2463}2464o_flags2465}24662467#[cfg(not(feature = "proxy"))]2468fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags {2469let mut flags = filesystem::DescriptorFlags::empty();2470if rights & wasip1::RIGHTS_FD_READ == wasip1::RIGHTS_FD_READ {2471flags |= filesystem::DescriptorFlags::READ;2472}2473if rights & wasip1::RIGHTS_FD_WRITE == wasip1::RIGHTS_FD_WRITE {2474flags |= filesystem::DescriptorFlags::WRITE;2475}2476if fdflags & wasip1::FDFLAGS_SYNC == wasip1::FDFLAGS_SYNC {2477flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;2478}2479if fdflags & wasip1::FDFLAGS_DSYNC == wasip1::FDFLAGS_DSYNC {2480flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;2481}2482if fdflags & wasip1::FDFLAGS_RSYNC == wasip1::FDFLAGS_RSYNC {2483flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;2484}2485flags2486}24872488#[cfg(not(feature = "proxy"))]2489impl From<filesystem::ErrorCode> for Errno {2490#[inline(never)] // Disable inlining as this is bulky and relatively cold.2491fn from(err: filesystem::ErrorCode) -> Errno {2492match err {2493// Use a black box to prevent the optimizer from generating a2494// lookup table, which would require a static initializer.2495filesystem::ErrorCode::Access => black_box(ERRNO_ACCES),2496filesystem::ErrorCode::WouldBlock => ERRNO_AGAIN,2497filesystem::ErrorCode::Already => ERRNO_ALREADY,2498filesystem::ErrorCode::BadDescriptor => ERRNO_BADF,2499filesystem::ErrorCode::Busy => ERRNO_BUSY,2500filesystem::ErrorCode::Deadlock => ERRNO_DEADLK,2501filesystem::ErrorCode::Quota => ERRNO_DQUOT,2502filesystem::ErrorCode::Exist => ERRNO_EXIST,2503filesystem::ErrorCode::FileTooLarge => ERRNO_FBIG,2504filesystem::ErrorCode::IllegalByteSequence => ERRNO_ILSEQ,2505filesystem::ErrorCode::InProgress => ERRNO_INPROGRESS,2506filesystem::ErrorCode::Interrupted => ERRNO_INTR,2507filesystem::ErrorCode::Invalid => ERRNO_INVAL,2508filesystem::ErrorCode::Io => ERRNO_IO,2509filesystem::ErrorCode::IsDirectory => ERRNO_ISDIR,2510filesystem::ErrorCode::Loop => ERRNO_LOOP,2511filesystem::ErrorCode::TooManyLinks => ERRNO_MLINK,2512filesystem::ErrorCode::MessageSize => ERRNO_MSGSIZE,2513filesystem::ErrorCode::NameTooLong => ERRNO_NAMETOOLONG,2514filesystem::ErrorCode::NoDevice => ERRNO_NODEV,2515filesystem::ErrorCode::NoEntry => ERRNO_NOENT,2516filesystem::ErrorCode::NoLock => ERRNO_NOLCK,2517filesystem::ErrorCode::InsufficientMemory => ERRNO_NOMEM,2518filesystem::ErrorCode::InsufficientSpace => ERRNO_NOSPC,2519filesystem::ErrorCode::Unsupported => ERRNO_NOTSUP,2520filesystem::ErrorCode::NotDirectory => ERRNO_NOTDIR,2521filesystem::ErrorCode::NotEmpty => ERRNO_NOTEMPTY,2522filesystem::ErrorCode::NotRecoverable => ERRNO_NOTRECOVERABLE,2523filesystem::ErrorCode::NoTty => ERRNO_NOTTY,2524filesystem::ErrorCode::NoSuchDevice => ERRNO_NXIO,2525filesystem::ErrorCode::Overflow => ERRNO_OVERFLOW,2526filesystem::ErrorCode::NotPermitted => ERRNO_PERM,2527filesystem::ErrorCode::Pipe => ERRNO_PIPE,2528filesystem::ErrorCode::ReadOnly => ERRNO_ROFS,2529filesystem::ErrorCode::InvalidSeek => ERRNO_SPIPE,2530filesystem::ErrorCode::TextFileBusy => ERRNO_TXTBSY,2531filesystem::ErrorCode::CrossDevice => ERRNO_XDEV,2532}2533}2534}25352536#[cfg(not(feature = "proxy"))]2537impl From<filesystem::DescriptorType> for wasip1::Filetype {2538fn from(ty: filesystem::DescriptorType) -> wasip1::Filetype {2539match ty {2540filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE,2541filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY,2542filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE,2543filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE,2544// preview1 never had a FIFO code.2545filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN,2546// TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and2547// FILETYPE_SOCKET_DGRAM.2548filesystem::DescriptorType::Socket => unreachable!(),2549filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK,2550filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN,2551}2552}2553}25542555#[derive(Clone, Copy)]2556pub enum BlockingMode {2557NonBlocking,2558Blocking,2559}25602561impl BlockingMode {2562// note: these methods must take self, not &self, to avoid rustc creating a constant2563// out of a BlockingMode literal that it places in .romem, creating a data section and2564// breaking our fragile linking scheme2565fn read(2566self,2567input_stream: &streams::InputStream,2568read_len: u64,2569) -> Result<Vec<u8>, streams::StreamError> {2570match self {2571BlockingMode::NonBlocking => input_stream.read(read_len),2572BlockingMode::Blocking => input_stream.blocking_read(read_len),2573}2574}2575fn write(2576self,2577output_stream: &streams::OutputStream,2578mut bytes: &[u8],2579) -> Result<usize, Errno> {2580match self {2581BlockingMode::Blocking => {2582let total = bytes.len();2583loop {2584let len = bytes.len().min(4096);2585let (chunk, rest) = bytes.split_at(len);2586bytes = rest;2587match output_stream.blocking_write_and_flush(chunk) {2588Ok(()) if bytes.is_empty() => break,2589Ok(()) => {}2590Err(streams::StreamError::Closed) => return Err(ERRNO_IO),2591Err(streams::StreamError::LastOperationFailed(e)) => {2592return Err(stream_error_to_errno(e));2593}2594}2595}2596Ok(total)2597}25982599BlockingMode::NonBlocking => {2600let permit = match output_stream.check_write() {2601Ok(n) => n,2602Err(streams::StreamError::Closed) => 0,2603Err(streams::StreamError::LastOperationFailed(e)) => {2604return Err(stream_error_to_errno(e));2605}2606};26072608let len = bytes.len().min(permit as usize);26092610match output_stream.write(&bytes[..len]) {2611Ok(_) => {}2612Err(streams::StreamError::Closed) => return Ok(0),2613Err(streams::StreamError::LastOperationFailed(e)) => {2614return Err(stream_error_to_errno(e));2615}2616}26172618match output_stream.blocking_flush() {2619Ok(_) => {}2620Err(streams::StreamError::Closed) => return Ok(0),2621Err(streams::StreamError::LastOperationFailed(e)) => {2622return Err(stream_error_to_errno(e));2623}2624}26252626Ok(len)2627}2628}2629}2630}26312632#[repr(C)]2633#[cfg(not(feature = "proxy"))]2634pub struct File {2635/// The handle to the preview2 descriptor that this file is referencing.2636fd: filesystem::Descriptor,26372638/// The descriptor type, as supplied by filesystem::get_type at opening2639descriptor_type: filesystem::DescriptorType,26402641/// The current-position pointer.2642position: Cell<filesystem::Filesize>,26432644/// In append mode, all writes append to the file.2645append: bool,26462647/// In blocking mode, read and write calls dispatch to blocking_read and2648/// blocking_check_write on the underlying streams. When false, read and write2649/// dispatch to stream's plain read and check_write.2650blocking_mode: BlockingMode,26512652/// TODO2653preopen_name_len: Option<NonZeroUsize>,2654}26552656#[cfg(not(feature = "proxy"))]2657impl File {2658fn is_dir(&self) -> bool {2659match self.descriptor_type {2660filesystem::DescriptorType::Directory => true,2661_ => false,2662}2663}2664}26652666const PAGE_SIZE: usize = 65536;26672668/// The maximum path length. WASI doesn't explicitly guarantee this, but all2669/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this2670/// polyfill.2671const PATH_MAX: usize = 4096;26722673/// Maximum number of bytes to cache for a `wasip1::Dirent` plus its path name.2674const DIRENT_CACHE: usize = 256;26752676/// A canary value to detect memory corruption within `State`.2677const MAGIC: u32 = u32::from_le_bytes(*b"ugh!");26782679#[repr(C)] // used for now to keep magic1 and magic2 at the start and end2680struct State {2681/// A canary constant value located at the beginning of this structure to2682/// try to catch memory corruption coming from the bottom.2683magic1: u32,26842685/// Used to coordinate allocations of `cabi_import_realloc`2686import_alloc: Cell<ImportAlloc>,26872688/// Storage of mapping from preview1 file descriptors to preview2 file2689/// descriptors.2690///2691/// Do not use this member directly - use State::descriptors() to ensure2692/// lazy initialization happens.2693descriptors: RefCell<Option<Descriptors>>,26942695/// TODO2696temporary_data: UnsafeCell<MaybeUninit<[u8; temporary_data_size()]>>,26972698/// Cache for the `fd_readdir` call for a final `wasip1::Dirent` plus path2699/// name that didn't fit into the caller's buffer.2700#[cfg(not(feature = "proxy"))]2701dirent_cache: DirentCache,27022703/// The string `..` for use by the directory iterator.2704#[cfg(not(feature = "proxy"))]2705dotdot: [UnsafeCell<u8>; 2],27062707/// Another canary constant located at the end of the structure to catch2708/// memory corruption coming from the bottom.2709magic2: u32,2710}27112712#[cfg(not(feature = "proxy"))]2713struct DirentCache {2714stream: Cell<Option<DirectoryEntryStream>>,2715for_fd: Cell<wasip1::Fd>,2716cookie: Cell<wasip1::Dircookie>,2717cached_dirent: Cell<wasip1::Dirent>,2718path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,2719}27202721#[cfg(not(feature = "proxy"))]2722struct DirectoryEntryStream(filesystem::DirectoryEntryStream);27232724#[repr(C)]2725pub struct WasmStr {2726ptr: *const u8,2727len: usize,2728}27292730#[repr(C)]2731pub struct WasmStrList {2732base: *const WasmStr,2733len: usize,2734}27352736#[repr(C)]2737pub struct StrTuple {2738key: WasmStr,2739value: WasmStr,2740}27412742#[derive(Copy, Clone)]2743#[repr(C)]2744pub struct StrTupleList {2745base: *const StrTuple,2746len: usize,2747}27482749#[derive(Copy, Clone)]2750#[repr(C)]2751pub struct ReadyList {2752base: *const u32,2753len: usize,2754}27552756const fn temporary_data_size() -> usize {2757// The total size of the struct should be a page, so start there2758let mut start = PAGE_SIZE;27592760// Remove big chunks of the struct for its various fields.2761start -= size_of::<Descriptors>();2762#[cfg(not(feature = "proxy"))]2763{2764start -= size_of::<DirentCache>();2765}27662767// Remove miscellaneous metadata also stored in state.2768let misc = if cfg!(feature = "proxy") { 8 } else { 10 };2769start -= misc * size_of::<usize>();27702771// Everything else is the `command_data` allocation.2772start2773}27742775// Statically assert that the `State` structure is the size of a wasm page. This2776// mostly guarantees that it's not larger than one page which is relied upon2777// below.2778#[cfg(target_arch = "wasm32")]2779const _: () = {2780let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];2781};27822783#[expect(unused, reason = "not used in all configurations")]2784#[repr(i32)]2785enum AllocationState {2786StackUnallocated,2787StackAllocating,2788StackAllocated,2789StateAllocating,2790StateAllocated,2791}27922793#[expect(improper_ctypes, reason = "types behind pointers")]2794unsafe extern "C" {2795fn get_state_ptr() -> *mut State;2796fn set_state_ptr(state: *mut State);2797fn get_allocation_state() -> AllocationState;2798fn set_allocation_state(state: AllocationState);2799}28002801impl State {2802fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno {2803let state_ref = State::ptr();2804assert_eq!(state_ref.magic1, MAGIC);2805assert_eq!(state_ref.magic2, MAGIC);2806let ret = f(state_ref);2807match ret {2808Ok(()) => ERRNO_SUCCESS,2809Err(err) => err,2810}2811}28122813fn ptr() -> &'static State {2814unsafe {2815let mut ptr = get_state_ptr();2816if ptr.is_null() {2817ptr = State::new();2818set_state_ptr(ptr);2819}2820&*ptr2821}2822}28232824#[cold]2825fn new() -> *mut State {2826#[link(wasm_import_module = "__main_module__")]2827unsafe extern "C" {2828fn cabi_realloc(2829old_ptr: *mut u8,2830old_len: usize,2831align: usize,2832new_len: usize,2833) -> *mut u8;2834}28352836assert!(matches!(2837unsafe { get_allocation_state() },2838AllocationState::StackAllocated2839));28402841unsafe { set_allocation_state(AllocationState::StateAllocating) };28422843let ret = unsafe {2844cabi_realloc(2845ptr::null_mut(),28460,2847mem::align_of::<UnsafeCell<State>>(),2848mem::size_of::<UnsafeCell<State>>(),2849) as *mut State2850};28512852unsafe { set_allocation_state(AllocationState::StateAllocated) };28532854unsafe {2855Self::init(ret);2856}28572858ret2859}28602861#[cold]2862unsafe fn init(state: *mut State) {2863unsafe {2864state.write(State {2865magic1: MAGIC,2866magic2: MAGIC,2867import_alloc: Cell::new(ImportAlloc::None),2868descriptors: RefCell::new(None),2869temporary_data: UnsafeCell::new(MaybeUninit::uninit()),2870#[cfg(not(feature = "proxy"))]2871dirent_cache: DirentCache {2872stream: Cell::new(None),2873for_fd: Cell::new(0),2874cookie: Cell::new(wasip1::DIRCOOKIE_START),2875cached_dirent: Cell::new(wasip1::Dirent {2876d_next: 0,2877d_ino: 0,2878d_type: FILETYPE_UNKNOWN,2879d_namlen: 0,2880}),2881path_data: UnsafeCell::new(MaybeUninit::uninit()),2882},2883#[cfg(not(feature = "proxy"))]2884dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],2885});2886}2887}28882889/// Accessor for the descriptors member that ensures it is properly initialized2890fn descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a {2891let mut d = self2892.descriptors2893.try_borrow_mut()2894.unwrap_or_else(|_| unreachable!());2895if d.is_none() {2896*d = Some(Descriptors::new(self));2897}2898RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))2899}29002901/// Mut accessor for the descriptors member that ensures it is properly initialized2902fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a {2903let mut d = self2904.descriptors2905.try_borrow_mut()2906.unwrap_or_else(|_| unreachable!());2907if d.is_none() {2908*d = Some(Descriptors::new(self));2909}2910RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))2911}29122913unsafe fn temporary_alloc(&self) -> BumpAlloc {2914BumpAlloc {2915base: self.temporary_data.get().cast(),2916len: mem::size_of_val(&self.temporary_data),2917}2918}29192920/// Configure that `cabi_import_realloc` will allocate once from2921/// `self.temporary_data` for the duration of the closure `f`.2922///2923/// Panics if the import allocator is already configured.2924fn with_one_temporary_alloc<T>(&self, f: impl FnOnce() -> T) -> T {2925let alloc = unsafe { self.temporary_alloc() };2926self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).02927}29282929/// Configure that `cabi_import_realloc` will allocate once from2930/// `base` with at most `len` bytes for the duration of `f`.2931///2932/// Panics if the import allocator is already configured.2933fn with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T {2934let alloc = BumpAlloc { base, len };2935self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).02936}29372938/// Configures the `alloc` specified to be the allocator for2939/// `cabi_import_realloc` for the duration of `f`.2940///2941/// Panics if the import allocator is already configured.2942fn with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc) {2943match self.import_alloc.replace(alloc) {2944ImportAlloc::None => {}2945_ => unreachable!("import allocator already set"),2946}2947let r = f();2948let alloc = self.import_alloc.replace(ImportAlloc::None);2949(r, alloc)2950}2951}295229532954