From 3e5b30bd9606460682c5eff7b3237d42d1460b30 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 29 Sep 2023 12:23:54 -0700 Subject: [PATCH] Start to port Wasmtime to the new wasi-io API with resources. (#7029) * Rename `Host*` things to avoid name conflicts with bindings. * Update to the latest resource-enabled wit files. * Adapting the code to the new bindings. * Update wasi-http to the resource-enabled wit deps. * Start adapting the wasi-http code to the new bindings. * Make `get_directories` always return new owned handles. * Simplify the `poll_one` implementation. * Update the wasi-preview1-component-adapter. FIXME: temporarily disable wasi-http tests. Add logging to the cli world, since stderr is now a reseource that can only be claimed once. * Work around a bug hit by poll-list, fix a bug in poll-one. * Comment out `test_fd_readwrite_invalid_fd`, which panics now. * Fix a few FIXMEs. * Use `.as_ref().trapping_unwrap()` instead of `TrappingUnwrapRef`. * Use `drop_in_place`. * Remove `State::with_mut`. * Remove the `RefCell` around the `State`. * Update to wit-bindgen 0.12. * Update wasi-http to use resources for poll and I/O. This required making incoming-body and outgoing-body resourrces too, to work with `push_input_stream_child` and `push_output_stream_child`. * Re-enable disabled tests, remove logging from the worlds. * Remove the `poll_list` workarounds that are no longer needed. * Remove logging from the adapter. That said, there is no replacement yet, so add a FIXME comment. * Reenable a test that now passes. * Remove `.descriptors_mut` and use `with_descriptors_mut` instead. Replace `.descriptors()` and `.descriptors_mut()` with functions that take closures, which limits their scope, to prevent them from invalid aliasing. * Implement dynamic borrow checking for descriptors. * Add a cargo-vet audit for wasmtime-wmemcheck. * Update cargo vet for wit-bindgen 0.12. * Cut down on duplicate sync/async resource types (#1) * Allow calling `get-directories` more than once (#2) For now `Clone` the directories into new descriptor slots as needed. * Start to lift restriction of stdio only once (#3) * Start to lift restriction of stdio only once This commit adds new `{Stdin,Stdout}Stream` traits which take over the job of the stdio streams in `WasiCtxBuilder` and `WasiCtx`. These traits bake in the ability to create a stream at any time to satisfy the API of `wasi:cli`. The TTY functionality is folded into them as while I was at it. The implementation for stdin is relatively trivial since the stdin implementation already handles multiple streams reading it. Built-in impls of the `StdinStream` trait are also provided for helper types in `preview2::pipe` which resulted in the implementation of `MemoryInputPipe` being updated to support `Clone` where all clones read the same original data. * Get tests building * Un-ignore now-passing test * Remove unneeded argument from `WasiCtxBuilder::build` * Fix tests * Remove some workarounds Stdio functions can now be called multiple times. * If `poll_oneoff` fails part-way through, clean up properly. Fix the `Drop` implementation for pollables to only drop the pollables that have been successfully added to the list. This fixes the poll_oneoff_files failure and removes a FIXME. --------- Co-authored-by: Alex Crichton --- Cargo.lock | 21 +- Cargo.toml | 2 +- .../src/bin/stream_pollable_lifetimes.rs | 20 +- crates/test-programs/reactor-tests/src/lib.rs | 29 +- crates/test-programs/tests/command.rs | 104 +- crates/test-programs/tests/reactor.rs | 18 +- .../tests/wasi-http-components-sync.rs | 11 +- .../tests/wasi-http-components.rs | 10 +- crates/test-programs/tests/wasi-http-proxy.rs | 10 +- .../tests/wasi-preview1-host-in-preview2.rs | 10 +- .../tests/wasi-preview2-components-sync.rs | 10 +- .../tests/wasi-preview2-components.rs | 10 +- crates/test-programs/tests/wasi-sockets.rs | 4 +- .../wasi-http-proxy-tests/src/lib.rs | 4 +- .../test-programs/wasi-http-tests/src/lib.rs | 35 +- .../wasi-sockets-tests/src/bin/tcp_v4.rs | 13 +- .../wasi-sockets-tests/src/bin/tcp_v6.rs | 13 +- .../wasi-sockets-tests/src/lib.rs | 113 +- .../test-programs/wasi-tests/src/bin/sleep.rs | 14 +- crates/wasi-http/src/lib.rs | 2 +- crates/wasi-http/src/proxy.rs | 2 +- crates/wasi-http/src/types.rs | 49 +- crates/wasi-http/src/types_impl.rs | 75 +- crates/wasi-http/wit/command-extended.wit | 2 +- crates/wasi-http/wit/deps/cli/reactor.wit | 2 +- crates/wasi-http/wit/deps/cli/terminal.wit | 16 +- .../wit/deps/clocks/monotonic-clock.wit | 4 +- crates/wasi-http/wit/deps/clocks/timezone.wit | 19 +- .../wasi-http/wit/deps/clocks/wall-clock.wit | 2 - crates/wasi-http/wit/deps/clocks/world.wit | 7 + .../wasi-http/wit/deps/filesystem/types.wit | 1018 ++++++------ .../wasi-http/wit/deps/filesystem/world.wit | 2 +- crates/wasi-http/wit/deps/http/types.wit | 31 +- crates/wasi-http/wit/deps/io/poll.wit | 34 + crates/wasi-http/wit/deps/io/streams.wit | 465 +++--- crates/wasi-http/wit/deps/io/world.wit | 6 + crates/wasi-http/wit/deps/logging/world.wit | 5 + crates/wasi-http/wit/deps/poll/poll.wit | 39 - crates/wasi-http/wit/deps/random/random.wit | 26 +- crates/wasi-http/wit/deps/random/world.wit | 7 + .../wit/deps/sockets/ip-name-lookup.wit | 58 +- crates/wasi-http/wit/deps/sockets/network.wit | 13 +- crates/wasi-http/wit/deps/sockets/tcp.wit | 477 +++--- crates/wasi-http/wit/deps/sockets/udp.wit | 394 +++-- crates/wasi-http/wit/deps/sockets/world.wit | 11 + crates/wasi-http/wit/main.wit | 2 +- crates/wasi-http/wit/test.wit | 5 +- .../wasi-preview1-component-adapter/build.rs | 49 +- .../src/descriptors.rs | 181 +- .../src/lib.rs | 1452 +++++++++-------- .../src/macros.rs | 3 +- crates/wasi/Cargo.toml | 2 - crates/wasi/src/preview2/command.rs | 8 +- crates/wasi/src/preview2/ctx.rs | 102 +- crates/wasi/src/preview2/filesystem.rs | 51 +- crates/wasi/src/preview2/host/clocks.rs | 15 +- crates/wasi/src/preview2/host/filesystem.rs | 324 ++-- .../wasi/src/preview2/host/filesystem/sync.rs | 228 +-- .../src/preview2/host/instance_network.rs | 7 +- crates/wasi/src/preview2/host/io.rs | 553 ++++--- crates/wasi/src/preview2/host/network.rs | 7 +- crates/wasi/src/preview2/host/tcp.rs | 188 ++- .../src/preview2/host/tcp_create_socket.rs | 7 +- crates/wasi/src/preview2/mod.rs | 98 +- crates/wasi/src/preview2/network.rs | 30 +- crates/wasi/src/preview2/pipe.rs | 28 +- crates/wasi/src/preview2/poll.rs | 109 +- crates/wasi/src/preview2/preview1.rs | 328 ++-- crates/wasi/src/preview2/stdio.rs | 232 ++- .../src/preview2/stdio/worker_thread_stdin.rs | 11 +- crates/wasi/src/preview2/stream.rs | 160 +- crates/wasi/src/preview2/table.rs | 12 +- crates/wasi/src/preview2/tcp.rs | 58 +- crates/wasi/wit/command-extended.wit | 2 +- crates/wasi/wit/deps/cli/reactor.wit | 2 +- crates/wasi/wit/deps/cli/terminal.wit | 16 +- .../wasi/wit/deps/clocks/monotonic-clock.wit | 4 +- crates/wasi/wit/deps/clocks/timezone.wit | 19 +- crates/wasi/wit/deps/clocks/wall-clock.wit | 2 - crates/wasi/wit/deps/clocks/world.wit | 7 + crates/wasi/wit/deps/filesystem/types.wit | 1018 ++++++------ crates/wasi/wit/deps/filesystem/world.wit | 2 +- crates/wasi/wit/deps/http/types.wit | 31 +- crates/wasi/wit/deps/io/poll.wit | 34 + crates/wasi/wit/deps/io/streams.wit | 465 +++--- crates/wasi/wit/deps/io/world.wit | 6 + crates/wasi/wit/deps/logging/world.wit | 5 + crates/wasi/wit/deps/poll/poll.wit | 39 - crates/wasi/wit/deps/random/random.wit | 26 +- crates/wasi/wit/deps/random/world.wit | 7 + .../wasi/wit/deps/sockets/ip-name-lookup.wit | 58 +- crates/wasi/wit/deps/sockets/network.wit | 13 +- crates/wasi/wit/deps/sockets/tcp.wit | 477 +++--- crates/wasi/wit/deps/sockets/udp.wit | 394 +++-- crates/wasi/wit/deps/sockets/world.wit | 11 + crates/wasi/wit/main.wit | 2 +- crates/wasi/wit/test.wit | 5 +- crates/wasmtime/src/component/resources.rs | 13 +- src/commands/run.rs | 7 +- supply-chain/audits.toml | 6 + supply-chain/imports.lock | 35 + tests/all/cli_tests.rs | 4 +- 102 files changed, 5212 insertions(+), 4965 deletions(-) create mode 100644 crates/wasi-http/wit/deps/clocks/world.wit create mode 100644 crates/wasi-http/wit/deps/io/poll.wit create mode 100644 crates/wasi-http/wit/deps/io/world.wit create mode 100644 crates/wasi-http/wit/deps/logging/world.wit delete mode 100644 crates/wasi-http/wit/deps/poll/poll.wit create mode 100644 crates/wasi-http/wit/deps/random/world.wit create mode 100644 crates/wasi-http/wit/deps/sockets/world.wit create mode 100644 crates/wasi/wit/deps/clocks/world.wit create mode 100644 crates/wasi/wit/deps/io/poll.wit create mode 100644 crates/wasi/wit/deps/io/world.wit create mode 100644 crates/wasi/wit/deps/logging/world.wit delete mode 100644 crates/wasi/wit/deps/poll/poll.wit create mode 100644 crates/wasi/wit/deps/random/world.wit create mode 100644 crates/wasi/wit/deps/sockets/world.wit diff --git a/Cargo.lock b/Cargo.lock index 22d858a280b8..69d27ff848a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3750,7 +3750,6 @@ dependencies = [ "futures", "io-extras", "io-lifetimes 2.0.2", - "is-terminal", "libc", "once_cell", "rustix 0.38.8", @@ -4136,9 +4135,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279" +checksum = "b4f7c5d6f59ae013fc4c013c76eab667844a46e86b51987acb71b1e32953211a" dependencies = [ "bitflags 2.3.3", "wit-bindgen-rust-macro", @@ -4146,9 +4145,9 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e" +checksum = "7f0371c47784e7559efb422f74473e395b49f7101725584e2673657e0b4fc104" dependencies = [ "anyhow", "wit-component", @@ -4157,9 +4156,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae" +checksum = "eeab5a09a85b1641690922ce05d79d868a2f2e78e9415a5302f58b9846fab8f1" dependencies = [ "anyhow", "heck", @@ -4171,9 +4170,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-lib" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb" +checksum = "a13c89c9c1a93e164318745841026f63f889376f38664f86a7f678930280e728" dependencies = [ "heck", "wit-bindgen-core", @@ -4181,9 +4180,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22" +checksum = "a70c97e09751a9a95a592bd8ef84e953e5cdce6ebbfdb35ceefa5cc511da3b71" dependencies = [ "anyhow", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index db8b4ff5836c..ddaffc7d5889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -213,7 +213,7 @@ io-extras = "0.18.0" rustix = "0.38.8" is-terminal = "0.4.0" # wit-bindgen: -wit-bindgen = { version = "0.11.0", default-features = false } +wit-bindgen = { version = "0.12.0", default-features = false } # wasm-tools family: wasmparser = "0.113.2" diff --git a/crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs b/crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs index ebd8071957d1..8693e1f373d1 100644 --- a/crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs +++ b/crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs @@ -1,24 +1,24 @@ use command_tests::wasi::cli::environment; use command_tests::wasi::cli::stdin; +use command_tests::wasi::io::poll; use command_tests::wasi::io::streams; -use command_tests::wasi::poll::poll; fn main() { let args = environment::get_arguments(); if args == &["correct"] { let stdin: streams::InputStream = stdin::get_stdin(); - let stdin_pollable = streams::subscribe_to_input_stream(stdin); - let ready = poll::poll_oneoff(&[stdin_pollable]); - assert_eq!(ready, &[true]); - poll::drop_pollable(stdin_pollable); - streams::drop_input_stream(stdin); + let stdin_pollable = stdin.subscribe(); + let ready = poll::poll_list(&[&stdin_pollable]); + assert_eq!(ready, &[0]); + drop(stdin_pollable); + drop(stdin); } else if args == &["trap"] { let stdin: streams::InputStream = stdin::get_stdin(); - let stdin_pollable = streams::subscribe_to_input_stream(stdin); - let ready = poll::poll_oneoff(&[stdin_pollable]); - assert_eq!(ready, &[true]); - streams::drop_input_stream(stdin); + let stdin_pollable = stdin.subscribe(); + let ready = poll::poll_list(&[&stdin_pollable]); + assert_eq!(ready, &[0]); + drop(stdin); unreachable!( "execution should have trapped in line above when stream dropped before pollable" ); diff --git a/crates/test-programs/reactor-tests/src/lib.rs b/crates/test-programs/reactor-tests/src/lib.rs index c3219d37fc4c..c5514d6a1505 100644 --- a/crates/test-programs/reactor-tests/src/lib.rs +++ b/crates/test-programs/reactor-tests/src/lib.rs @@ -7,21 +7,10 @@ wit_bindgen::generate!({ }); struct T; -use wasi::io::streams; -use wasi::poll::poll; +use wasi::io::poll; static mut STATE: Vec = Vec::new(); -struct DropPollable { - pollable: poll::Pollable, -} - -impl Drop for DropPollable { - fn drop(&mut self) { - poll::drop_pollable(self.pollable); - } -} - impl Guest for T { fn add_strings(ss: Vec) -> u32 { for s in ss { @@ -40,34 +29,32 @@ impl Guest for T { } fn write_strings_to(o: OutputStream) -> Result<(), ()> { - let sub = DropPollable { - pollable: streams::subscribe_to_output_stream(o), - }; + let pollable = o.subscribe(); unsafe { for s in STATE.iter() { let mut out = s.as_bytes(); while !out.is_empty() { - poll::poll_oneoff(&[sub.pollable]); - let n = match streams::check_write(o) { + poll::poll_list(&[&pollable]); + let n = match o.check_write() { Ok(n) => n, Err(_) => return Err(()), }; let len = (n as usize).min(out.len()); - match streams::write(o, &out[..len]) { + match o.write(&out[..len]) { Ok(_) => out = &out[len..], Err(_) => return Err(()), } } } - match streams::flush(o) { + match o.flush() { Ok(_) => {} Err(_) => return Err(()), } - poll::poll_oneoff(&[sub.pollable]); - match streams::check_write(o) { + poll::poll_list(&[&pollable]); + match o.check_write() { Ok(_) => {} Err(_) => return Err(()), } diff --git a/crates/test-programs/tests/command.rs b/crates/test-programs/tests/command.rs index 574380420348..bdba29ff3839 100644 --- a/crates/test-programs/tests/command.rs +++ b/crates/test-programs/tests/command.rs @@ -8,7 +8,7 @@ use wasmtime::{ use wasmtime_wasi::preview2::{ command::{add_to_linker, Command}, pipe::MemoryInputPipe, - DirPerms, FilePerms, HostMonotonicClock, HostWallClock, IsATTY, Table, WasiCtx, WasiCtxBuilder, + DirPerms, FilePerms, HostMonotonicClock, HostWallClock, Table, WasiCtx, WasiCtxBuilder, WasiView, }; @@ -61,10 +61,10 @@ async fn instantiate( #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn hello_stdout() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .args(&["gussie", "sparky", "willa"]) - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("hello_stdout"), CommandCtx { table, wasi }).await?; command @@ -76,7 +76,7 @@ async fn hello_stdout() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn panic() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .args(&[ "diesel", @@ -88,7 +88,7 @@ async fn panic() -> Result<()> { "good", "yesterday", ]) - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("panic"), CommandCtx { table, wasi }).await?; let r = command.wasi_cli_run().call_run(&mut store).await; @@ -99,10 +99,10 @@ async fn panic() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn args() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .args(&["hello", "this", "", "is an argument", "with 🚩 emoji"]) - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("args"), CommandCtx { table, wasi }).await?; command @@ -114,8 +114,8 @@ async fn args() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn random() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("random"), CommandCtx { table, wasi }).await?; @@ -157,11 +157,11 @@ async fn time() -> Result<()> { } } - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .monotonic_clock(FakeMonotonicClock { now: Mutex::new(0) }) .wall_clock(FakeWallClock) - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("time"), CommandCtx { table, wasi }).await?; @@ -175,13 +175,12 @@ async fn time() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn stdin() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() - .stdin( - MemoryInputPipe::new("So rested he by the Tumtum tree".into()), - IsATTY::No, - ) - .build(&mut table)?; + .stdin(MemoryInputPipe::new( + "So rested he by the Tumtum tree".into(), + )) + .build(); let (mut store, command) = instantiate(get_component("stdin"), CommandCtx { table, wasi }).await?; @@ -195,13 +194,12 @@ async fn stdin() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn poll_stdin() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() - .stdin( - MemoryInputPipe::new("So rested he by the Tumtum tree".into()), - IsATTY::No, - ) - .build(&mut table)?; + .stdin(MemoryInputPipe::new( + "So rested he by the Tumtum tree".into(), + )) + .build(); let (mut store, command) = instantiate(get_component("poll_stdin"), CommandCtx { table, wasi }).await?; @@ -215,11 +213,11 @@ async fn poll_stdin() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn env() -> Result<()> { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .env("frabjous", "day") .env("callooh", "callay") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("env"), CommandCtx { table, wasi }).await?; @@ -239,10 +237,10 @@ async fn file_read() -> Result<()> { let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .preopened_dir(open_dir, DirPerms::all(), FilePerms::all(), "/") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("file_read"), CommandCtx { table, wasi }).await?; @@ -263,10 +261,10 @@ async fn file_append() -> Result<()> { let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .preopened_dir(open_dir, DirPerms::all(), FilePerms::all(), "/") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("file_append"), CommandCtx { table, wasi }).await?; @@ -296,10 +294,10 @@ async fn file_dir_sync() -> Result<()> { let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .preopened_dir(open_dir, DirPerms::all(), FilePerms::all(), "/") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("file_dir_sync"), CommandCtx { table, wasi }).await?; @@ -313,8 +311,8 @@ async fn file_dir_sync() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn exit_success() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("exit_success"), CommandCtx { table, wasi }).await?; @@ -330,8 +328,8 @@ async fn exit_success() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn exit_default() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("exit_default"), CommandCtx { table, wasi }).await?; @@ -343,8 +341,8 @@ async fn exit_default() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn exit_failure() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("exit_failure"), CommandCtx { table, wasi }).await?; @@ -360,8 +358,8 @@ async fn exit_failure() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn exit_panic() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("exit_panic"), CommandCtx { table, wasi }).await?; @@ -388,12 +386,12 @@ async fn directory_list() -> Result<()> { let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() .preopened_dir(open_dir, DirPerms::all(), FilePerms::all(), "/") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("directory_list"), CommandCtx { table, wasi }).await?; @@ -407,8 +405,8 @@ async fn directory_list() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn default_clocks() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate(get_component("default_clocks"), CommandCtx { table, wasi }).await?; @@ -422,8 +420,8 @@ async fn default_clocks() -> Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn export_cabi_realloc() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new().build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().build(); let (mut store, command) = instantiate( get_component("export_cabi_realloc"), CommandCtx { table, wasi }, @@ -444,11 +442,11 @@ async fn read_only() -> Result<()> { std::fs::File::create(dir.path().join("bar.txt"))?.write_all(b"And stood awhile in thought")?; std::fs::create_dir(dir.path().join("sub"))?; - let mut table = Table::new(); + let table = Table::new(); let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; let wasi = WasiCtxBuilder::new() .preopened_dir(open_dir, DirPerms::READ, FilePerms::READ, "/") - .build(&mut table)?; + .build(); let (mut store, command) = instantiate(get_component("read_only"), CommandCtx { table, wasi }).await?; @@ -465,11 +463,11 @@ async fn stream_pollable_lifetimes() -> Result<()> { // Test program has two modes, dispatching based on argument. { // Correct execution: should succeed - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .args(&["correct"]) - .stdin(MemoryInputPipe::new(" ".into()), IsATTY::No) - .build(&mut table)?; + .stdin(MemoryInputPipe::new(" ".into())) + .build(); let (mut store, command) = instantiate( get_component("stream_pollable_lifetimes"), @@ -485,11 +483,11 @@ async fn stream_pollable_lifetimes() -> Result<()> { } { // Incorrect execution: should trap with a TableError::HasChildren - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .args(&["trap"]) - .stdin(MemoryInputPipe::new(" ".into()), IsATTY::No) - .build(&mut table)?; + .stdin(MemoryInputPipe::new(" ".into())) + .build(); let (mut store, command) = instantiate( get_component("stream_pollable_lifetimes"), diff --git a/crates/test-programs/tests/reactor.rs b/crates/test-programs/tests/reactor.rs index 555c756d086d..a5cfed2beedd 100644 --- a/crates/test-programs/tests/reactor.rs +++ b/crates/test-programs/tests/reactor.rs @@ -81,10 +81,8 @@ async fn instantiate( #[test_log::test(tokio::test)] async fn reactor_tests() -> Result<()> { - let mut table = Table::new(); - let wasi = WasiCtxBuilder::new() - .env("GOOD_DOG", "gussie") - .build(&mut table)?; + let table = Table::new(); + let wasi = WasiCtxBuilder::new().env("GOOD_DOG", "gussie").build(); let (mut store, reactor) = instantiate(get_component("reactor_tests"), ReactorCtx { table, wasi }).await?; @@ -116,20 +114,20 @@ async fn reactor_tests() -> Result<()> { // Show that the `with` invocation in the macro means we get to re-use the // type definitions from inside the `host` crate for these structures: let ds = filesystem::DescriptorStat { - data_access_timestamp: wall_clock::Datetime { + data_access_timestamp: Some(wall_clock::Datetime { nanoseconds: 123, seconds: 45, - }, - data_modification_timestamp: wall_clock::Datetime { + }), + data_modification_timestamp: Some(wall_clock::Datetime { nanoseconds: 789, seconds: 10, - }, + }), link_count: 0, size: 0, - status_change_timestamp: wall_clock::Datetime { + status_change_timestamp: Some(wall_clock::Datetime { nanoseconds: 0, seconds: 1, - }, + }), type_: filesystem::DescriptorType::Unknown, }; let expected = format!("{ds:?}"); diff --git a/crates/test-programs/tests/wasi-http-components-sync.rs b/crates/test-programs/tests/wasi-http-components-sync.rs index 222fc800edce..b27d1c2eec6d 100644 --- a/crates/test-programs/tests/wasi-http-components-sync.rs +++ b/crates/test-programs/tests/wasi-http-components-sync.rs @@ -4,8 +4,7 @@ use wasmtime::{ Config, Engine, Store, }; use wasmtime_wasi::preview2::{ - command::sync::Command, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder, - WasiView, + command::sync::Command, pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView, }; use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; @@ -71,18 +70,18 @@ fn run(name: &str) -> anyhow::Result<()> { let stdout = MemoryOutputPipe::new(4096); let stderr = MemoryOutputPipe::new(4096); let r = { - let mut table = Table::new(); + let table = Table::new(); let component = get_component(name); // Create our wasi context. let mut builder = WasiCtxBuilder::new(); - builder.stdout(stdout.clone(), IsATTY::No); - builder.stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()); + builder.stderr(stderr.clone()); builder.arg(name); for (var, val) in test_programs::wasi_tests_environment() { builder.env(var, val); } - let wasi = builder.build(&mut table)?; + let wasi = builder.build(); let http = WasiHttpCtx {}; let (mut store, command) = instantiate_component(component, Ctx { table, wasi, http })?; diff --git a/crates/test-programs/tests/wasi-http-components.rs b/crates/test-programs/tests/wasi-http-components.rs index 11fb6bd498ca..51f194800854 100644 --- a/crates/test-programs/tests/wasi-http-components.rs +++ b/crates/test-programs/tests/wasi-http-components.rs @@ -4,7 +4,7 @@ use wasmtime::{ Config, Engine, Store, }; use wasmtime_wasi::preview2::{ - command::Command, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + command::Command, pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView, }; use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; @@ -70,18 +70,18 @@ async fn run(name: &str) -> anyhow::Result<()> { let stdout = MemoryOutputPipe::new(4096); let stderr = MemoryOutputPipe::new(4096); let r = { - let mut table = Table::new(); + let table = Table::new(); let component = get_component(name); // Create our wasi context. let mut builder = WasiCtxBuilder::new(); - builder.stdout(stdout.clone(), IsATTY::No); - builder.stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()); + builder.stderr(stderr.clone()); builder.arg(name); for (var, val) in test_programs::wasi_tests_environment() { builder.env(var, val); } - let wasi = builder.build(&mut table)?; + let wasi = builder.build(); let http = WasiHttpCtx; let (mut store, command) = diff --git a/crates/test-programs/tests/wasi-http-proxy.rs b/crates/test-programs/tests/wasi-http-proxy.rs index f344dc15071f..65c07872de52 100644 --- a/crates/test-programs/tests/wasi-http-proxy.rs +++ b/crates/test-programs/tests/wasi-http-proxy.rs @@ -5,7 +5,7 @@ use wasmtime::{ Config, Engine, Store, }; use wasmtime_wasi::preview2::{ - self, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + self, pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView, }; use wasmtime_wasi_http::{proxy::Proxy, WasiHttpCtx, WasiHttpView}; @@ -70,17 +70,17 @@ async fn wasi_http_proxy_tests() -> anyhow::Result<()> { let stdout = MemoryOutputPipe::new(4096); let stderr = MemoryOutputPipe::new(4096); - let mut table = Table::new(); + let table = Table::new(); let component = get_component("wasi_http_proxy_tests"); // Create our wasi context. let mut builder = WasiCtxBuilder::new(); - builder.stdout(stdout.clone(), IsATTY::No); - builder.stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()); + builder.stderr(stderr.clone()); for (var, val) in test_programs::wasi_tests_environment() { builder.env(var, val); } - let wasi = builder.build(&mut table)?; + let wasi = builder.build(); let http = WasiHttpCtx; let ctx = Ctx { table, wasi, http }; diff --git a/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs b/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs index 836d473f75e6..79f619a8a3eb 100644 --- a/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs +++ b/crates/test-programs/tests/wasi-preview1-host-in-preview2.rs @@ -5,7 +5,7 @@ use wasmtime::{Config, Engine, Linker, Store}; use wasmtime_wasi::preview2::{ pipe::MemoryOutputPipe, preview1::{add_to_linker_async, WasiPreview1Adapter, WasiPreview1View}, - DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView, }; lazy_static::lazy_static! { @@ -43,9 +43,7 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> { if inherit_stdio { builder.inherit_stdio(); } else { - builder - .stdout(stdout.clone(), IsATTY::No) - .stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()).stderr(stderr.clone()); } builder.args(&[name, "."]); println!("preopen: {:?}", workspace); @@ -56,8 +54,8 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> { builder.env(var, val); } - let mut table = Table::new(); - let wasi = builder.build(&mut table)?; + let table = Table::new(); + let wasi = builder.build(); struct Ctx { wasi: WasiCtx, table: Table, diff --git a/crates/test-programs/tests/wasi-preview2-components-sync.rs b/crates/test-programs/tests/wasi-preview2-components-sync.rs index 5e095923e163..9772f9c8473f 100644 --- a/crates/test-programs/tests/wasi-preview2-components-sync.rs +++ b/crates/test-programs/tests/wasi-preview2-components-sync.rs @@ -5,7 +5,7 @@ use wasmtime::{component::Linker, Config, Engine, Store}; use wasmtime_wasi::preview2::{ command::sync::{add_to_linker, Command}, pipe::MemoryOutputPipe, - DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView, }; lazy_static::lazy_static! { @@ -43,9 +43,7 @@ fn run(name: &str, inherit_stdio: bool) -> Result<()> { if inherit_stdio { builder.inherit_stdio(); } else { - builder - .stdout(stdout.clone(), IsATTY::No) - .stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()).stderr(stderr.clone()); } builder.args(&[name, "."]); println!("preopen: {:?}", workspace); @@ -56,8 +54,8 @@ fn run(name: &str, inherit_stdio: bool) -> Result<()> { builder.env(var, val); } - let mut table = Table::new(); - let wasi = builder.build(&mut table)?; + let table = Table::new(); + let wasi = builder.build(); struct Ctx { wasi: WasiCtx, table: Table, diff --git a/crates/test-programs/tests/wasi-preview2-components.rs b/crates/test-programs/tests/wasi-preview2-components.rs index 7834562b687b..a2b30ed0d346 100644 --- a/crates/test-programs/tests/wasi-preview2-components.rs +++ b/crates/test-programs/tests/wasi-preview2-components.rs @@ -5,7 +5,7 @@ use wasmtime::{component::Linker, Config, Engine, Store}; use wasmtime_wasi::preview2::{ command::{add_to_linker, Command}, pipe::MemoryOutputPipe, - DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView, }; lazy_static::lazy_static! { @@ -43,9 +43,7 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> { if inherit_stdio { builder.inherit_stdio(); } else { - builder - .stdout(stdout.clone(), IsATTY::No) - .stderr(stderr.clone(), IsATTY::No); + builder.stdout(stdout.clone()).stderr(stderr.clone()); } builder.args(&[name, "."]); println!("preopen: {:?}", workspace); @@ -56,8 +54,8 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> { builder.env(var, val); } - let mut table = Table::new(); - let wasi = builder.build(&mut table)?; + let table = Table::new(); + let wasi = builder.build(); struct Ctx { wasi: WasiCtx, table: Table, diff --git a/crates/test-programs/tests/wasi-sockets.rs b/crates/test-programs/tests/wasi-sockets.rs index c16e9d4fadf7..71669817bce6 100644 --- a/crates/test-programs/tests/wasi-sockets.rs +++ b/crates/test-programs/tests/wasi-sockets.rs @@ -48,12 +48,12 @@ async fn run(name: &str) -> anyhow::Result<()> { preview2::command::add_to_linker(&mut linker)?; // Create our wasi context. - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .inherit_stdio() .inherit_network(ambient_authority()) .arg(name) - .build(&mut table)?; + .build(); let mut store = Store::new(&ENGINE, SocketsCtx { table, wasi }); diff --git a/crates/test-programs/wasi-http-proxy-tests/src/lib.rs b/crates/test-programs/wasi-http-proxy-tests/src/lib.rs index 239f1754327d..c51be36d7f38 100644 --- a/crates/test-programs/wasi-http-proxy-tests/src/lib.rs +++ b/crates/test-programs/wasi-http-proxy-tests/src/lib.rs @@ -24,10 +24,10 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T { bindings::wasi::http::types::set_response_outparam(outparam, Ok(resp)); let out = bindings::wasi::http::types::outgoing_body_write(body).expect("outgoing stream"); - bindings::wasi::io::streams::blocking_write_and_flush(out, b"hello, world!") + out.blocking_write_and_flush(b"hello, world!") .expect("writing response"); - bindings::wasi::io::streams::drop_output_stream(out); + drop(out); bindings::wasi::http::types::outgoing_body_finish(body, None); } } diff --git a/crates/test-programs/wasi-http-tests/src/lib.rs b/crates/test-programs/wasi-http-tests/src/lib.rs index e2ae84de6998..5aabc976d6b3 100644 --- a/crates/test-programs/wasi-http-tests/src/lib.rs +++ b/crates/test-programs/wasi-http-tests/src/lib.rs @@ -12,8 +12,8 @@ use std::fmt; use std::sync::OnceLock; use bindings::wasi::http::{outgoing_handler, types as http_types}; +use bindings::wasi::io::poll; use bindings::wasi::io::streams; -use bindings::wasi::poll::poll; pub struct Response { pub status: http_types::StatusCode, @@ -79,11 +79,11 @@ pub async fn request( let request_body = http_types::outgoing_body_write(outgoing_body) .map_err(|_| anyhow!("outgoing request write failed"))?; - let pollable = streams::subscribe_to_output_stream(request_body); + let pollable = request_body.subscribe(); while !buf.is_empty() { - poll::poll_oneoff(&[pollable]); + poll::poll_list(&[&pollable]); - let permit = match streams::check_write(request_body) { + let permit = match request_body.check_write() { Ok(n) => n, Err(_) => anyhow::bail!("output stream error"), }; @@ -92,26 +92,23 @@ pub async fn request( let (chunk, rest) = buf.split_at(len); buf = rest; - match streams::write(request_body, chunk) { + match request_body.write(chunk) { Err(_) => anyhow::bail!("output stream error"), _ => {} } } - match streams::flush(request_body) { + match request_body.flush() { Err(_) => anyhow::bail!("output stream error"), _ => {} } - poll::poll_oneoff(&[pollable]); - poll::drop_pollable(pollable); + poll::poll_list(&[&pollable]); - match streams::check_write(request_body) { + match request_body.check_write() { Ok(_) => {} Err(_) => anyhow::bail!("output stream error"), }; - - streams::drop_output_stream(request_body); } let future_response = outgoing_handler::handle(request, None)?; @@ -125,8 +122,7 @@ pub async fn request( Some(result) => result.map_err(|_| anyhow!("incoming response errored"))?, None => { let pollable = http_types::listen_to_future_incoming_response(future_response); - let _ = poll::poll_oneoff(&[pollable]); - poll::drop_pollable(pollable); + let _ = poll::poll_list(&[&pollable]); http_types::future_incoming_response_get(future_response) .expect("incoming response available") .map_err(|_| anyhow!("incoming response errored"))? @@ -149,15 +145,16 @@ pub async fn request( http_types::drop_incoming_response(incoming_response); - let input_stream = http_types::incoming_body_stream(incoming_body).unwrap(); - let input_stream_pollable = streams::subscribe_to_input_stream(input_stream); + let input_stream = incoming_body.stream().unwrap(); + let input_stream_pollable = input_stream.subscribe(); let mut body = Vec::new(); let mut eof = streams::StreamStatus::Open; while eof != streams::StreamStatus::Ended { - poll::poll_oneoff(&[input_stream_pollable]); + poll::poll_list(&[&input_stream_pollable]); - let (mut body_chunk, stream_status) = streams::read(input_stream, 1024 * 1024) + let (mut body_chunk, stream_status) = input_stream + .read(1024 * 1024) .map_err(|_| anyhow!("input_stream read failed"))?; eof = stream_status; @@ -167,10 +164,6 @@ pub async fn request( } } - poll::drop_pollable(input_stream_pollable); - streams::drop_input_stream(input_stream); - http_types::drop_incoming_body(incoming_body); - Ok(Response { status, headers, diff --git a/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v4.rs b/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v4.rs index 2388cb074afe..cf02fdd79663 100644 --- a/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v4.rs +++ b/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v4.rs @@ -1,7 +1,8 @@ //! A simple TCP testcase, using IPv4. +use wasi::io::poll; use wasi::sockets::network::{IpAddressFamily, IpSocketAddress, Ipv4SocketAddress}; -use wasi::sockets::{instance_network, tcp, tcp_create_socket}; +use wasi::sockets::{instance_network, tcp_create_socket}; use wasi_sockets_tests::*; fn main() { @@ -14,14 +15,14 @@ fn main() { address: (127, 0, 0, 1), // localhost }); - let sub = tcp::subscribe(sock); + let sub = sock.subscribe(); - tcp::start_bind(sock, net, addr).unwrap(); + sock.start_bind(&net, addr).unwrap(); - wait(sub); - wasi::poll::poll::drop_pollable(sub); + poll::poll_one(&sub); + drop(sub); - tcp::finish_bind(sock).unwrap(); + sock.finish_bind().unwrap(); example_body(net, sock, IpAddressFamily::Ipv4) } diff --git a/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v6.rs b/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v6.rs index b5ff8358cc09..807db9825f1e 100644 --- a/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v6.rs +++ b/crates/test-programs/wasi-sockets-tests/src/bin/tcp_v6.rs @@ -1,7 +1,8 @@ //! Like v4.rs, but with IPv6. +use wasi::io::poll; use wasi::sockets::network::{IpAddressFamily, IpSocketAddress, Ipv6SocketAddress}; -use wasi::sockets::{instance_network, tcp, tcp_create_socket}; +use wasi::sockets::{instance_network, tcp_create_socket}; use wasi_sockets_tests::*; fn main() { @@ -16,14 +17,14 @@ fn main() { scope_id: 0, }); - let sub = tcp::subscribe(sock); + let sub = sock.subscribe(); - tcp::start_bind(sock, net, addr).unwrap(); + sock.start_bind(&net, addr).unwrap(); - wait(sub); - wasi::poll::poll::drop_pollable(sub); + poll::poll_one(&sub); + drop(sub); - tcp::finish_bind(sock).unwrap(); + sock.finish_bind().unwrap(); example_body(net, sock, IpAddressFamily::Ipv6) } diff --git a/crates/test-programs/wasi-sockets-tests/src/lib.rs b/crates/test-programs/wasi-sockets-tests/src/lib.rs index fb06654c313a..56c32d3e87b7 100644 --- a/crates/test-programs/wasi-sockets-tests/src/lib.rs +++ b/crates/test-programs/wasi-sockets-tests/src/lib.rs @@ -1,40 +1,19 @@ wit_bindgen::generate!("test-command-with-sockets" in "../../wasi/wit"); +use wasi::io::poll; use wasi::io::streams; -use wasi::poll::poll; use wasi::sockets::{network, tcp, tcp_create_socket}; -pub fn wait(sub: poll::Pollable) { - loop { - let wait = poll::poll_oneoff(&[sub]); - if wait[0] { - break; - } - } -} - -pub struct DropPollable { - pub pollable: poll::Pollable, -} - -impl Drop for DropPollable { - fn drop(&mut self) { - poll::drop_pollable(self.pollable); - } -} - -pub fn write(output: streams::OutputStream, mut bytes: &[u8]) -> (usize, streams::StreamStatus) { +pub fn write(output: &streams::OutputStream, mut bytes: &[u8]) -> (usize, streams::StreamStatus) { let total = bytes.len(); let mut written = 0; - let s = DropPollable { - pollable: streams::subscribe_to_output_stream(output), - }; + let pollable = output.subscribe(); while !bytes.is_empty() { - poll::poll_oneoff(&[s.pollable]); + poll::poll_list(&[&pollable]); - let permit = match streams::check_write(output) { + let permit = match output.check_write() { Ok(n) => n, Err(_) => return (written, streams::StreamStatus::Ended), }; @@ -42,12 +21,12 @@ pub fn write(output: streams::OutputStream, mut bytes: &[u8]) -> (usize, streams let len = bytes.len().min(permit as usize); let (chunk, rest) = bytes.split_at(len); - match streams::write(output, chunk) { + match output.write(chunk) { Ok(()) => {} Err(_) => return (written, streams::StreamStatus::Ended), } - match streams::blocking_flush(output) { + match output.blocking_flush() { Ok(()) => {} Err(_) => return (written, streams::StreamStatus::Ended), } @@ -63,81 +42,77 @@ pub fn example_body(net: tcp::Network, sock: tcp::TcpSocket, family: network::Ip let first_message = b"Hello, world!"; let second_message = b"Greetings, planet!"; - let sub = tcp::subscribe(sock); + let sub = sock.subscribe(); - tcp::start_listen(sock).unwrap(); - wait(sub); - tcp::finish_listen(sock).unwrap(); + sock.start_listen().unwrap(); + poll::poll_one(&sub); + sock.finish_listen().unwrap(); - let addr = tcp::local_address(sock).unwrap(); + let addr = sock.local_address().unwrap(); let client = tcp_create_socket::create_tcp_socket(family).unwrap(); - let client_sub = tcp::subscribe(client); + let client_sub = client.subscribe(); - tcp::start_connect(client, net, addr).unwrap(); - wait(client_sub); - let (client_input, client_output) = tcp::finish_connect(client).unwrap(); + client.start_connect(&net, addr).unwrap(); + poll::poll_one(&client_sub); + let (client_input, client_output) = client.finish_connect().unwrap(); - let (n, status) = write(client_output, &[]); + let (n, status) = write(&client_output, &[]); assert_eq!(n, 0); assert_eq!(status, streams::StreamStatus::Open); - let (n, status) = write(client_output, first_message); + let (n, status) = write(&client_output, first_message); assert_eq!(n, first_message.len()); assert_eq!(status, streams::StreamStatus::Open); - streams::drop_input_stream(client_input); - streams::drop_output_stream(client_output); - poll::drop_pollable(client_sub); - tcp::drop_tcp_socket(client); + drop(client_input); + drop(client_output); + drop(client_sub); + drop(client); - wait(sub); - let (accepted, input, output) = tcp::accept(sock).unwrap(); + poll::poll_one(&sub); + let (accepted, input, output) = sock.accept().unwrap(); - let (empty_data, status) = streams::read(input, 0).unwrap(); + let (empty_data, status) = input.read(0).unwrap(); assert!(empty_data.is_empty()); assert_eq!(status, streams::StreamStatus::Open); - let (data, status) = streams::blocking_read(input, first_message.len() as u64).unwrap(); + let (data, status) = input.blocking_read(first_message.len() as u64).unwrap(); assert_eq!(status, streams::StreamStatus::Open); - streams::drop_input_stream(input); - streams::drop_output_stream(output); - tcp::drop_tcp_socket(accepted); + drop(input); + drop(output); + drop(accepted); // Check that we sent and recieved our message! assert_eq!(data, first_message); // Not guaranteed to work but should work in practice. // Another client let client = tcp_create_socket::create_tcp_socket(family).unwrap(); - let client_sub = tcp::subscribe(client); + let client_sub = client.subscribe(); - tcp::start_connect(client, net, addr).unwrap(); - wait(client_sub); - let (client_input, client_output) = tcp::finish_connect(client).unwrap(); + client.start_connect(&net, addr).unwrap(); + poll::poll_one(&client_sub); + let (client_input, client_output) = client.finish_connect().unwrap(); - let (n, status) = write(client_output, second_message); + let (n, status) = write(&client_output, second_message); assert_eq!(n, second_message.len()); assert_eq!(status, streams::StreamStatus::Open); - streams::drop_input_stream(client_input); - streams::drop_output_stream(client_output); - poll::drop_pollable(client_sub); - tcp::drop_tcp_socket(client); + drop(client_input); + drop(client_output); + drop(client_sub); + drop(client); - wait(sub); - let (accepted, input, output) = tcp::accept(sock).unwrap(); - let (data, status) = streams::blocking_read(input, second_message.len() as u64).unwrap(); + poll::poll_one(&sub); + let (accepted, input, output) = sock.accept().unwrap(); + let (data, status) = input.blocking_read(second_message.len() as u64).unwrap(); assert_eq!(status, streams::StreamStatus::Open); - streams::drop_input_stream(input); - streams::drop_output_stream(output); - tcp::drop_tcp_socket(accepted); + drop(input); + drop(output); + drop(accepted); // Check that we sent and recieved our message! assert_eq!(data, second_message); // Not guaranteed to work but should work in practice. - - poll::drop_pollable(sub); - tcp::drop_tcp_socket(sock); - network::drop_network(net); } diff --git a/crates/test-programs/wasi-tests/src/bin/sleep.rs b/crates/test-programs/wasi-tests/src/bin/sleep.rs index a80f11bbff6a..d382a32e1559 100644 --- a/crates/test-programs/wasi-tests/src/bin/sleep.rs +++ b/crates/test-programs/wasi-tests/src/bin/sleep.rs @@ -1,4 +1,4 @@ -use crate::wasi::{clocks::monotonic_clock, poll::poll}; +use crate::wasi::{clocks::monotonic_clock, io::poll}; wit_bindgen::generate!({ path: "../../wasi/wit", @@ -9,8 +9,12 @@ fn main() { // Sleep ten milliseconds. Note that we call the relevant host functions directly rather than go through // libstd, since we want to ensure we're calling `monotonic_clock::subscribe` with an `absolute` parameter of // `true`, which libstd won't necessarily do (but which e.g. CPython _will_ do). - poll::poll_oneoff(&[monotonic_clock::subscribe( - monotonic_clock::now() + 10_000_000, - true, - )]); + eprintln!("calling subscribe"); + let p = monotonic_clock::subscribe(monotonic_clock::now() + 10_000_000, true); + dbg!(&p as *const _); + let list = &[&p]; + dbg!(list.as_ptr()); + eprintln!("calling poll"); + poll::poll_list(list); + eprintln!("done"); } diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs index 8501a0cabb81..1fc7bc3736d7 100644 --- a/crates/wasi-http/src/lib.rs +++ b/crates/wasi-http/src/lib.rs @@ -18,7 +18,7 @@ pub mod bindings { async: false, with: { "wasi:io/streams": wasmtime_wasi::preview2::bindings::io::streams, - "wasi:poll/poll": wasmtime_wasi::preview2::bindings::poll::poll, + "wasi:io/poll": wasmtime_wasi::preview2::bindings::io::poll, } }); diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs index b276a6545265..1a7bca511c29 100644 --- a/crates/wasi-http/src/proxy.rs +++ b/crates/wasi-http/src/proxy.rs @@ -16,7 +16,7 @@ wasmtime::component::bindgen!({ "wasi:http/outgoing-handler": bindings::http::outgoing_handler, "wasi:http/types": bindings::http::types, "wasi:io/streams": preview2::bindings::io::streams, - "wasi:poll/poll": preview2::bindings::poll::poll, + "wasi:io/poll": preview2::bindings::io::poll, "wasi:random/random": preview2::bindings::random::random, }, }); diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs index 49baba25aac1..391c5d91851e 100644 --- a/crates/wasi-http/src/types.rs +++ b/crates/wasi-http/src/types.rs @@ -14,6 +14,7 @@ use crate::{ use std::any::Any; use std::pin::Pin; use std::task; +use wasmtime::component::Resource; use wasmtime_wasi::preview2::{AbortOnDropJoinHandle, Table, TableError}; /// Capture the state necessary for use in the wasi-http API implementation. @@ -262,13 +263,22 @@ pub trait TableHttpExt { id: u32, ) -> Result; - fn push_incoming_body(&mut self, body: HostIncomingBody) -> Result; - fn get_incoming_body(&mut self, id: IncomingBody) -> Result<&mut HostIncomingBody, TableError>; - fn delete_incoming_body(&mut self, id: IncomingBody) -> Result; + fn push_incoming_body( + &mut self, + body: HostIncomingBody, + ) -> Result, TableError>; + fn get_incoming_body( + &mut self, + id: &Resource, + ) -> Result<&mut HostIncomingBody, TableError>; + fn delete_incoming_body( + &mut self, + id: Resource, + ) -> Result; - fn push_outgoing_body(&mut self, body: HostOutgoingBody) -> Result; - fn get_outgoing_body(&mut self, id: OutgoingBody) -> Result<&mut HostOutgoingBody, TableError>; - fn delete_outgoing_body(&mut self, id: OutgoingBody) -> Result; + fn push_outgoing_body(&mut self, body: HostOutgoingBody) -> Result; + fn get_outgoing_body(&mut self, id: u32) -> Result<&mut HostOutgoingBody, TableError>; + fn delete_outgoing_body(&mut self, id: u32) -> Result; fn push_future_trailers( &mut self, @@ -377,27 +387,36 @@ impl TableHttpExt for Table { self.delete(id) } - fn push_incoming_body(&mut self, body: HostIncomingBody) -> Result { - self.push(Box::new(body)) + fn push_incoming_body( + &mut self, + body: HostIncomingBody, + ) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(body))?)) } - fn get_incoming_body(&mut self, id: IncomingBody) -> Result<&mut HostIncomingBody, TableError> { - self.get_mut(id) + fn get_incoming_body( + &mut self, + id: &Resource, + ) -> Result<&mut HostIncomingBody, TableError> { + self.get_mut(id.rep()) } - fn delete_incoming_body(&mut self, id: IncomingBody) -> Result { - self.delete(id) + fn delete_incoming_body( + &mut self, + id: Resource, + ) -> Result { + self.delete(id.rep()) } fn push_outgoing_body(&mut self, body: HostOutgoingBody) -> Result { - self.push(Box::new(body)) + Ok(self.push(Box::new(body))?) } - fn get_outgoing_body(&mut self, id: OutgoingBody) -> Result<&mut HostOutgoingBody, TableError> { + fn get_outgoing_body(&mut self, id: u32) -> Result<&mut HostOutgoingBody, TableError> { self.get_mut(id) } - fn delete_outgoing_body(&mut self, id: OutgoingBody) -> Result { + fn delete_outgoing_body(&mut self, id: u32) -> Result { self.delete(id) } diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs index 4baea8f364bf..a2abad080ebd 100644 --- a/crates/wasi-http/src/types_impl.rs +++ b/crates/wasi-http/src/types_impl.rs @@ -15,9 +15,10 @@ use crate::{ }; use anyhow::Context; use std::any::Any; +use wasmtime::component::Resource; use wasmtime_wasi::preview2::{ + bindings::io::poll::Pollable, bindings::io::streams::{InputStream, OutputStream}, - bindings::poll::poll::Pollable, HostPollable, PollableFuture, TablePollableExt, TableStreamExt, }; @@ -207,7 +208,7 @@ impl crate::bindings::http::types::Host for T { fn incoming_request_consume( &mut self, id: IncomingRequest, - ) -> wasmtime::Result> { + ) -> wasmtime::Result, ()>> { let req = types::IncomingRequestLens::from(id).get_mut(self.table())?; match req.body.take() { Some(builder) => { @@ -326,7 +327,7 @@ impl crate::bindings::http::types::Host for T { fn incoming_response_consume( &mut self, response: IncomingResponse, - ) -> wasmtime::Result> { + ) -> wasmtime::Result, ()>> { let table = self.table(); let r = table .get_incoming_response_mut(response) @@ -348,7 +349,10 @@ impl crate::bindings::http::types::Host for T { Ok(()) } - fn future_trailers_subscribe(&mut self, index: FutureTrailers) -> wasmtime::Result { + fn future_trailers_subscribe( + &mut self, + index: FutureTrailers, + ) -> wasmtime::Result> { // Eagerly force errors about the validity of the index. let _ = self.table().get_future_trailers(index)?; @@ -475,7 +479,7 @@ impl crate::bindings::http::types::Host for T { fn listen_to_future_incoming_response( &mut self, id: FutureIncomingResponse, - ) -> wasmtime::Result { + ) -> wasmtime::Result> { let _ = self.table().get_future_incoming_response(id)?; fn make_future<'a>(elem: &'a mut dyn Any) -> PollableFuture<'a> { @@ -493,40 +497,14 @@ impl crate::bindings::http::types::Host for T { Ok(pollable) } - fn incoming_body_stream( - &mut self, - id: IncomingBody, - ) -> wasmtime::Result> { - let body = self.table().get_incoming_body(id)?; - - if let Some(stream) = body.stream.take() { - let stream = self.table().push_input_stream_child(Box::new(stream), id)?; - return Ok(Ok(stream)); - } - - Ok(Err(())) - } - - fn incoming_body_finish(&mut self, id: IncomingBody) -> wasmtime::Result { - let body = self.table().delete_incoming_body(id)?; - let trailers = self - .table() - .push_future_trailers(body.into_future_trailers())?; - Ok(trailers) - } - - fn drop_incoming_body(&mut self, id: IncomingBody) -> wasmtime::Result<()> { - let _ = self.table().delete_incoming_body(id)?; - Ok(()) - } - fn outgoing_body_write( &mut self, id: OutgoingBody, - ) -> wasmtime::Result> { + ) -> wasmtime::Result, ()>> { let body = self.table().get_outgoing_body(id)?; if let Some(stream) = body.body_output_stream.take() { - let id = self.table().push_output_stream_child(stream, id)?; + let dummy = Resource::::new_own(id); + let id = self.table().push_output_stream_child(stream, dummy)?; Ok(Ok(id)) } else { Ok(Err(())) @@ -571,3 +549,32 @@ impl crate::bindings::http::types::Host for T { Ok(()) } } + +impl crate::bindings::http::types::HostIncomingBody for T { + fn stream( + &mut self, + id: Resource, + ) -> wasmtime::Result, ()>> { + let body = self.table().get_incoming_body(&id)?; + + if let Some(stream) = body.stream.take() { + let stream = self.table().push_input_stream_child(Box::new(stream), id)?; + return Ok(Ok(stream)); + } + + Ok(Err(())) + } + + fn finish(&mut self, id: Resource) -> wasmtime::Result { + let body = self.table().delete_incoming_body(id)?; + let trailers = self + .table() + .push_future_trailers(body.into_future_trailers())?; + Ok(trailers) + } + + fn drop(&mut self, id: Resource) -> wasmtime::Result<()> { + let _ = self.table().delete_incoming_body(id)?; + Ok(()) + } +} diff --git a/crates/wasi-http/wit/command-extended.wit b/crates/wasi-http/wit/command-extended.wit index 314e26dfce1b..3c56808e4abe 100644 --- a/crates/wasi-http/wit/command-extended.wit +++ b/crates/wasi-http/wit/command-extended.wit @@ -16,7 +16,7 @@ world command-extended { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/exit diff --git a/crates/wasi-http/wit/deps/cli/reactor.wit b/crates/wasi-http/wit/deps/cli/reactor.wit index 46e3186e5636..274d0644dc41 100644 --- a/crates/wasi-http/wit/deps/cli/reactor.wit +++ b/crates/wasi-http/wit/deps/cli/reactor.wit @@ -16,7 +16,7 @@ world reactor { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import environment diff --git a/crates/wasi-http/wit/deps/cli/terminal.wit b/crates/wasi-http/wit/deps/cli/terminal.wit index f32e74437484..b0a5bec2a2c7 100644 --- a/crates/wasi-http/wit/deps/cli/terminal.wit +++ b/crates/wasi-http/wit/deps/cli/terminal.wit @@ -1,31 +1,19 @@ interface terminal-input { /// The input side of a terminal. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type terminal-input = u32 + resource terminal-input // In the future, this may include functions for disabling echoing, // disabling input buffering so that keyboard events are sent through // immediately, querying supported features, and so on. - - /// Dispose of the specified terminal-input after which it may no longer - /// be used. - drop-terminal-input: func(this: terminal-input) } interface terminal-output { /// The output side of a terminal. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type terminal-output = u32 + resource terminal-output // In the future, this may include functions for querying the terminal // size, being notified of terminal size changes, querying supported // features, and so on. - - /// Dispose of the specified terminal-output, after which it may no longer - /// be used. - drop-terminal-output: func(this: terminal-output) } /// An interface providing an optional `terminal-input` for stdin as a diff --git a/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit b/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit index 50eb4de111af..703a5fb7a503 100644 --- a/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit +++ b/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit @@ -1,5 +1,3 @@ -package wasi:clocks - /// WASI Monotonic Clock is a clock API intended to let users measure elapsed /// time. /// @@ -11,7 +9,7 @@ package wasi:clocks /// /// It is intended for measuring elapsed time. interface monotonic-clock { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} /// A timestamp in nanoseconds. type instant = u64 diff --git a/crates/wasi-http/wit/deps/clocks/timezone.wit b/crates/wasi-http/wit/deps/clocks/timezone.wit index 2b6855668e1d..a872bffc7414 100644 --- a/crates/wasi-http/wit/deps/clocks/timezone.wit +++ b/crates/wasi-http/wit/deps/clocks/timezone.wit @@ -1,17 +1,6 @@ -package wasi:clocks - interface timezone { use wall-clock.{datetime} - /// A timezone. - /// - /// In timezones that recognize daylight saving time, also known as daylight - /// time and summer time, the information returned from the functions varies - /// over time to reflect these adjustments. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type timezone = u32 - /// Return information needed to display the given `datetime`. This includes /// the UTC offset, the time zone name, and a flag indicating whether /// daylight saving time is active. @@ -19,14 +8,10 @@ interface timezone { /// If the timezone cannot be determined for the given `datetime`, return a /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight /// saving time. - display: func(this: timezone, when: datetime) -> timezone-display + display: func(when: datetime) -> timezone-display /// The same as `display`, but only return the UTC offset. - utc-offset: func(this: timezone, when: datetime) -> s32 - - /// Dispose of the specified input-stream, after which it may no longer - /// be used. - drop-timezone: func(this: timezone) + utc-offset: func(when: datetime) -> s32 /// Information useful for displaying the timezone of a specific `datetime`. /// diff --git a/crates/wasi-http/wit/deps/clocks/wall-clock.wit b/crates/wasi-http/wit/deps/clocks/wall-clock.wit index 6137724f60b1..dae44a7308cd 100644 --- a/crates/wasi-http/wit/deps/clocks/wall-clock.wit +++ b/crates/wasi-http/wit/deps/clocks/wall-clock.wit @@ -1,5 +1,3 @@ -package wasi:clocks - /// WASI Wall Clock is a clock API intended to let users query the current /// time. The name "wall" makes an analogy to a "clock on the wall", which /// is not necessarily monotonic as it may be reset. diff --git a/crates/wasi-http/wit/deps/clocks/world.wit b/crates/wasi-http/wit/deps/clocks/world.wit new file mode 100644 index 000000000000..5c2dd411d2d0 --- /dev/null +++ b/crates/wasi-http/wit/deps/clocks/world.wit @@ -0,0 +1,7 @@ +package wasi:clocks + +world imports { + import monotonic-clock + import wall-clock + import timezone +} diff --git a/crates/wasi-http/wit/deps/filesystem/types.wit b/crates/wasi-http/wit/deps/filesystem/types.wit index e72a742de6c8..3f69bf997a29 100644 --- a/crates/wasi-http/wit/deps/filesystem/types.wit +++ b/crates/wasi-http/wit/deps/filesystem/types.wit @@ -17,6 +17,11 @@ /// `..` and symbolic link steps, reaches a directory outside of the base /// directory, or reaches a symlink to an absolute or rooted path in the /// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { use wasi:io/streams.{input-stream, output-stream} use wasi:clocks/wall-clock.{datetime} @@ -102,11 +107,20 @@ interface types { /// length in bytes of the pathname contained in the symbolic link. size: filesize, /// Last data access timestamp. - data-access-timestamp: datetime, + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, /// Last data modification timestamp. - data-modification-timestamp: datetime, - /// Last file status change timestamp. - status-change-timestamp: datetime, + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, } /// Flags determining the method of how paths are resolved. @@ -277,13 +291,6 @@ interface types { no-reuse, } - /// A descriptor is a reference to a filesystem object, which may be a file, - /// directory, named pipe, special file, or other object on which filesystem - /// calls may be made. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type descriptor = u32 - /// A 128-bit hash value, split into parts because wasm doesn't have a /// 128-bit integer type. record metadata-hash-value { @@ -293,532 +300,499 @@ interface types { upper: u64, } - /// Return a stream for reading from a file, if available. - /// - /// May fail with an error-code describing why the file cannot be read. - /// - /// Multiple read, write, and append streams may be active on the same open - /// file and they do not interfere with each other. - /// - /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. - read-via-stream: func( - this: descriptor, - /// The offset within the file at which to start reading. - offset: filesize, - ) -> result - - /// Return a stream for writing to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be written. - /// - /// Note: This allows using `write-stream`, which is similar to `write` in - /// POSIX. - write-via-stream: func( - this: descriptor, - /// The offset within the file at which to start writing. - offset: filesize, - ) -> result - - /// Return a stream for appending to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be appended. - /// - /// Note: This allows using `write-stream`, which is similar to `write` with - /// `O_APPEND` in in POSIX. - append-via-stream: func( - this: descriptor, - ) -> result + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result - /// Provide file advisory information on a descriptor. - /// - /// This is similar to `posix_fadvise` in POSIX. - advise: func( - this: descriptor, - /// The offset within the file to which the advisory applies. - offset: filesize, - /// The length of the region to which the advisory applies. - length: filesize, - /// The advice. - advice: advice - ) -> result<_, error-code> - - /// Synchronize the data of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fdatasync` in POSIX. - sync-data: func(this: descriptor) -> result<_, error-code> + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result - /// Get flags associated with a descriptor. - /// - /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. - /// - /// Note: This returns the value that was the `fs_flags` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-flags: func(this: descriptor) -> result + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code> + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code> - /// Get the dynamic type of a descriptor. - /// - /// Note: This returns the same value as the `type` field of the `fd-stat` - /// returned by `stat`, `stat-at` and similar. - /// - /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided - /// by `fstat` in POSIX. - /// - /// Note: This returns the value that was the `fs_filetype` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-type: func(this: descriptor) -> result + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result - /// Adjust the size of an open file. If this increases the file's size, the - /// extra bytes are filled with zeros. - /// - /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. - set-size: func(this: descriptor, size: filesize) -> result<_, error-code> + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result - /// Adjust the timestamps of an open file or directory. - /// - /// Note: This is similar to `futimens` in POSIX. - /// - /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. - set-times: func( - this: descriptor, - /// The desired values of the data access timestamp. - data-access-timestamp: new-timestamp, - /// The desired values of the data modification timestamp. - data-modification-timestamp: new-timestamp, - ) -> result<_, error-code> - - /// Read from a descriptor, without using and updating the descriptor's offset. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a bool which, when true, indicates that the end of the - /// file was reached. The returned list will contain up to `length` bytes; it - /// may return fewer than requested, if the end of the file is reached or - /// if the I/O operation is interrupted. - /// - /// In the future, this may change to return a `stream`. - /// - /// Note: This is similar to `pread` in POSIX. - read: func( - this: descriptor, - /// The maximum number of bytes to read. - length: filesize, - /// The offset within the file at which to read. - offset: filesize, - ) -> result, bool>, error-code> - - /// Write to a descriptor, without using and updating the descriptor's offset. - /// - /// It is valid to write past the end of a file; the file is extended to the - /// extent of the write, with bytes between the previous end and the start of - /// the write set to zero. - /// - /// In the future, this may change to take a `stream`. - /// - /// Note: This is similar to `pwrite` in POSIX. - write: func( - this: descriptor, - /// Data to write - buffer: list, - /// The offset within the file at which to write. - offset: filesize, - ) -> result - - /// Read directory entries from a directory. - /// - /// On filesystems where directories contain entries referring to themselves - /// and their parents, often named `.` and `..` respectively, these entries - /// are omitted. - /// - /// This always returns a new stream which starts at the beginning of the - /// directory. Multiple streams may be active on the same directory, and they - /// do not interfere with each other. - read-directory: func( - this: descriptor - ) -> result - - /// Synchronize the data and metadata of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fsync` in POSIX. - sync: func(this: descriptor) -> result<_, error-code> + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code> - /// Create a directory. - /// - /// Note: This is similar to `mkdirat` in POSIX. - create-directory-at: func( - this: descriptor, - /// The relative path at which to create the directory. - path: string, - ) -> result<_, error-code> - - /// Return the attributes of an open file or directory. - /// - /// Note: This is similar to `fstat` in POSIX, except that it does not return - /// device and inode information. For testing whether two descriptors refer to - /// the same underlying filesystem object, use `is-same-object`. To obtain - /// additional data that can be used do determine whether a file has been - /// modified, use `metadata-hash`. - /// - /// Note: This was called `fd_filestat_get` in earlier versions of WASI. - stat: func(this: descriptor) -> result + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code> + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result - /// Return the attributes of a file or directory. - /// - /// Note: This is similar to `fstatat` in POSIX, except that it does not - /// return device and inode information. See the `stat` description for a - /// discussion of alternatives. - /// - /// Note: This was called `path_filestat_get` in earlier versions of WASI. - stat-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to inspect. - path: string, - ) -> result - - /// Adjust the timestamps of a file or directory. - /// - /// Note: This is similar to `utimensat` in POSIX. - /// - /// Note: This was called `path_filestat_set_times` in earlier versions of - /// WASI. - set-times-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to operate on. - path: string, - /// The desired values of the data access timestamp. - data-access-timestamp: new-timestamp, - /// The desired values of the data modification timestamp. - data-modification-timestamp: new-timestamp, - ) -> result<_, error-code> - - /// Create a hard link. - /// - /// Note: This is similar to `linkat` in POSIX. - link-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - old-path-flags: path-flags, - /// The relative source path from which to link. - old-path: string, - /// The base directory for `new-path`. - new-descriptor: descriptor, - /// The relative destination path at which to create the hard link. - new-path: string, - ) -> result<_, error-code> - - /// Open a file or directory. - /// - /// The returned descriptor is not guaranteed to be the lowest-numbered - /// descriptor not currently open/ it is randomized to prevent applications - /// from depending on making assumptions about indexes, since this is - /// error-prone in multi-threaded contexts. The returned descriptor is - /// guaranteed to be less than 2**31. - /// - /// If `flags` contains `descriptor-flags::mutate-directory`, and the base - /// descriptor doesn't have `descriptor-flags::mutate-directory` set, - /// `open-at` fails with `error-code::read-only`. - /// - /// If `flags` contains `write` or `mutate-directory`, or `open-flags` - /// contains `truncate` or `create`, and the base descriptor doesn't have - /// `descriptor-flags::mutate-directory` set, `open-at` fails with - /// `error-code::read-only`. - /// - /// Note: This is similar to `openat` in POSIX. - open-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the object to open. - path: string, - /// The method by which to open the file. - open-flags: open-flags, - /// Flags to use for the resulting descriptor. - %flags: descriptor-flags, - /// Permissions to use when creating a new file. - modes: modes - ) -> result - - /// Read the contents of a symbolic link. - /// - /// If the contents contain an absolute or rooted path in the underlying - /// filesystem, this function fails with `error-code::not-permitted`. - /// - /// Note: This is similar to `readlinkat` in POSIX. - readlink-at: func( - this: descriptor, - /// The relative path of the symbolic link from which to read. - path: string, - ) -> result - - /// Remove a directory. - /// - /// Return `error-code::not-empty` if the directory is not empty. - /// - /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - remove-directory-at: func( - this: descriptor, - /// The relative path to a directory to remove. - path: string, - ) -> result<_, error-code> - - /// Rename a filesystem object. - /// - /// Note: This is similar to `renameat` in POSIX. - rename-at: func( - this: descriptor, - /// The relative source path of the file or directory to rename. - old-path: string, - /// The base directory for `new-path`. - new-descriptor: descriptor, - /// The relative destination path to which to rename the file or directory. - new-path: string, - ) -> result<_, error-code> - - /// Create a symbolic link (also known as a "symlink"). - /// - /// If `old-path` starts with `/`, the function fails with - /// `error-code::not-permitted`. - /// - /// Note: This is similar to `symlinkat` in POSIX. - symlink-at: func( - this: descriptor, - /// The contents of the symbolic link. - old-path: string, - /// The relative destination path at which to create the symbolic link. - new-path: string, - ) -> result<_, error-code> - - /// Check accessibility of a filesystem path. - /// - /// Check whether the given filesystem path names an object which is - /// readable, writable, or executable, or whether it exists. - /// - /// This does not a guarantee that subsequent accesses will succeed, as - /// filesystem permissions may be modified asynchronously by external - /// entities. - /// - /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. - access-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to check. - path: string, - /// The type of check to perform. - %type: access-type - ) -> result<_, error-code> - - /// Unlink a filesystem object that is not a directory. - /// - /// Return `error-code::is-directory` if the path refers to a directory. - /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. - unlink-file-at: func( - this: descriptor, - /// The relative path to a file to unlink. - path: string, - ) -> result<_, error-code> - - /// Change the permissions of a filesystem object that is not a directory. - /// - /// Note that the ultimate meanings of these permissions is - /// filesystem-specific. - /// - /// Note: This is similar to `fchmodat` in POSIX. - change-file-permissions-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to operate on. - path: string, - /// The new permissions for the filesystem object. - modes: modes, - ) -> result<_, error-code> - - /// Change the permissions of a directory. - /// - /// Note that the ultimate meanings of these permissions is - /// filesystem-specific. - /// - /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" - /// flag. `read` on a directory implies readability and searchability, and - /// `execute` is not valid for directories. - /// - /// Note: This is similar to `fchmodat` in POSIX. - change-directory-permissions-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to operate on. - path: string, - /// The new permissions for the directory. - modes: modes, - ) -> result<_, error-code> - - /// Request a shared advisory lock for an open file. - /// - /// This requests a *shared* lock; more than one shared lock can be held for - /// a file at the same time. - /// - /// If the open file has an exclusive lock, this function downgrades the lock - /// to a shared lock. If it has a shared lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified how shared locks interact with locks acquired by - /// non-WASI programs. - /// - /// This function blocks until the lock can be acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. - lock-shared: func(this: descriptor) -> result<_, error-code> + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code> - /// Request an exclusive advisory lock for an open file. - /// - /// This requests an *exclusive* lock; no other locks may be held for the - /// file while an exclusive lock is held. - /// - /// If the open file has a shared lock and there are no exclusive locks held - /// for the file, this function upgrades the lock to an exclusive lock. If the - /// open file already has an exclusive lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified whether this function succeeds if the file descriptor - /// is not opened for writing. It is unspecified how exclusive locks interact - /// with locks acquired by non-WASI programs. - /// - /// This function blocks until the lock can be acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. - lock-exclusive: func(this: descriptor) -> result<_, error-code> + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code> - /// Request a shared advisory lock for an open file. - /// - /// This requests a *shared* lock; more than one shared lock can be held for - /// a file at the same time. - /// - /// If the open file has an exclusive lock, this function downgrades the lock - /// to a shared lock. If it has a shared lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified how shared locks interact with locks acquired by - /// non-WASI programs. - /// - /// This function returns `error-code::would-block` if the lock cannot be - /// acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. - try-lock-shared: func(this: descriptor) -> result<_, error-code> + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result - /// Request an exclusive advisory lock for an open file. - /// - /// This requests an *exclusive* lock; no other locks may be held for the - /// file while an exclusive lock is held. - /// - /// If the open file has a shared lock and there are no exclusive locks held - /// for the file, this function upgrades the lock to an exclusive lock. If the - /// open file already has an exclusive lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified whether this function succeeds if the file descriptor - /// is not opened for writing. It is unspecified how exclusive locks interact - /// with locks acquired by non-WASI programs. - /// - /// This function returns `error-code::would-block` if the lock cannot be - /// acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. - try-lock-exclusive: func(this: descriptor) -> result<_, error-code> + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code> + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + /// Permissions to use when creating a new file. + modes: modes + ) -> result + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result - /// Release a shared or exclusive lock on an open file. - /// - /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. - unlock: func(this: descriptor) -> result<_, error-code> + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code> - /// Dispose of the specified `descriptor`, after which it may no longer - /// be used. - drop-descriptor: func(this: descriptor) + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code> + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code> + + /// Check accessibility of a filesystem path. + /// + /// Check whether the given filesystem path names an object which is + /// readable, writable, or executable, or whether it exists. + /// + /// This does not a guarantee that subsequent accesses will succeed, as + /// filesystem permissions may be modified asynchronously by external + /// entities. + /// + /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. + access-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to check. + path: string, + /// The type of check to perform. + %type: access-type + ) -> result<_, error-code> + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code> + + /// Change the permissions of a filesystem object that is not a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-file-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + modes: modes, + ) -> result<_, error-code> + + /// Change the permissions of a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + /// flag. `read` on a directory implies readability and searchability, and + /// `execute` is not valid for directories. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-directory-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + modes: modes, + ) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + lock-shared: func() -> result<_, error-code> - /// A stream of directory entries. - /// - /// This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). - type directory-entry-stream = u32 + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + lock-exclusive: func() -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + try-lock-shared: func() -> result<_, error-code> - /// Read a single directory entry from a `directory-entry-stream`. - read-directory-entry: func( - this: directory-entry-stream - ) -> result, error-code> + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + try-lock-exclusive: func() -> result<_, error-code> - /// Dispose of the specified `directory-entry-stream`, after which it may no longer - /// be used. - drop-directory-entry-stream: func(this: directory-entry-stream) + /// Release a shared or exclusive lock on an open file. + /// + /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + unlock: func() -> result<_, error-code> - /// Test whether two descriptors refer to the same filesystem object. - /// - /// In POSIX, this corresponds to testing whether the two descriptors have the - /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. - /// wasi-filesystem does not expose device and inode numbers, so this function - /// may be used instead. - is-same-object: func(this: descriptor, other: descriptor) -> bool - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a descriptor. - /// - /// This returns a hash of the last-modification timestamp and file size, and - /// may also include the inode number, device number, birth timestamp, and - /// other metadata fields that may change when the file is modified or - /// replaced. It may also include a secret value chosen by the - /// implementation and not otherwise exposed. - /// - /// Implementations are encourated to provide the following properties: - /// - /// - If the file is not modified or replaced, the computed hash value should - /// usually not change. - /// - If the object is modified or replaced, the computed hash value should - /// usually change. - /// - The inputs to the hash should not be easily computable from the - /// computed hash. - /// - /// However, none of these is required. - metadata-hash: func( - this: descriptor, - ) -> result + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a directory descriptor and a relative path. - /// - /// This performs the same hash computation as `metadata-hash`. - metadata-hash-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to inspect. - path: string, - ) -> result + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code> + } } diff --git a/crates/wasi-http/wit/deps/filesystem/world.wit b/crates/wasi-http/wit/deps/filesystem/world.wit index b51f484f8383..5fa7eafdb850 100644 --- a/crates/wasi-http/wit/deps/filesystem/world.wit +++ b/crates/wasi-http/wit/deps/filesystem/world.wit @@ -1,6 +1,6 @@ package wasi:filesystem -world example-world { +world imports { import types import preopens } diff --git a/crates/wasi-http/wit/deps/http/types.wit b/crates/wasi-http/wit/deps/http/types.wit index dfcacd8feb73..1abc7a1ff2c4 100644 --- a/crates/wasi-http/wit/deps/http/types.wit +++ b/crates/wasi-http/wit/deps/http/types.wit @@ -3,7 +3,7 @@ // imported and exported interfaces. interface types { use wasi:io/streams.{input-stream, output-stream} - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} // This type corresponds to HTTP standard Methods. variant method { @@ -100,7 +100,7 @@ interface types { // Additional optional parameters that can be set when making a request. record request-options { // The following timeouts are specific to the HTTP protocol and work - // independently of the overall timeouts passed to `io.poll.poll-oneoff`. + // independently of the overall timeouts passed to `io.poll.poll-list`. // The timeout for the initial connect. connect-timeout-ms: option, @@ -142,19 +142,18 @@ interface types { // incoming-body. incoming-response-consume: func(response: /* borrow */ incoming-response) -> result - type incoming-body = u32 - drop-incoming-body: func(this: /* own */ incoming-body) - - // returned input-stream is a child - the implementation may trap if - // incoming-body is dropped (or consumed by call to - // incoming-body-finish) before the input-stream is dropped. - // May be called at most once. returns error if called additional times. - incoming-body-stream: func(this: /* borrow */ incoming-body) -> - result - // takes ownership of incoming-body. this will trap if the - // incoming-body-stream child is still alive! - incoming-body-finish: func(this: /* own */ incoming-body) -> - /* transitive child of the incoming-response of incoming-body */ future-trailers + resource incoming-body { + // returned input-stream is a child - the implementation may trap if + // incoming-body is dropped (or consumed by call to + // incoming-body-finish) before the input-stream is dropped. + // May be called at most once. returns error if called additional times. + %stream: func() -> + result + // takes ownership of incoming-body. this will trap if the + // incoming-body-stream child is still alive! + finish: func() -> + /* transitive child of the incoming-response of incoming-body */ future-trailers + } type future-trailers = u32 drop-future-trailers: func(this: /* own */ future-trailers) @@ -193,7 +192,7 @@ interface types { /// `future-incoming-response`, the client can call the non-blocking `get` /// method to get the result if it is available. If the result is not available, /// the client can call `listen` to get a `pollable` that can be passed to - /// `io.poll.poll-oneoff`. + /// `wasi:io/poll.poll-list`. type future-incoming-response = u32 drop-future-incoming-response: func(f: /* own */ future-incoming-response) /// option indicates readiness. diff --git a/crates/wasi-http/wit/deps/io/poll.wit b/crates/wasi-http/wit/deps/io/poll.wit new file mode 100644 index 000000000000..e95762b915db --- /dev/null +++ b/crates/wasi-http/wit/deps/io/poll.wit @@ -0,0 +1,34 @@ +package wasi:io + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// A "pollable" handle. + resource pollable + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll-list: func(in: list>) -> list + + /// Poll for completion on a single pollable. + /// + /// This function is similar to `poll-list`, but operates on only a single + /// pollable. When it returns, the handle is ready for I/O. + poll-one: func(in: borrow) +} diff --git a/crates/wasi-http/wit/deps/io/streams.wit b/crates/wasi-http/wit/deps/io/streams.wit index e2631f66a569..eeeff505890a 100644 --- a/crates/wasi-http/wit/deps/io/streams.wit +++ b/crates/wasi-http/wit/deps/io/streams.wit @@ -6,7 +6,7 @@ package wasi:io /// In the future, the component model is expected to add built-in stream types; /// when it does, they are expected to subsume this API. interface streams { - use wasi:poll/poll.{pollable} + use poll.{pollable} /// Streams provide a sequence of data and then end; once they end, they /// no longer provide any further data. @@ -24,115 +24,81 @@ interface streams { ended, } - /// An input bytestream. In the future, this will be replaced by handle - /// types. + /// An input bytestream. /// /// `input-stream`s are *non-blocking* to the extent practical on underlying /// platforms. I/O operations always return promptly; if fewer bytes are /// promptly available than requested, they return the number of bytes promptly /// available, which could even be zero. To wait for data to be available, - /// use the `subscribe-to-input-stream` function to obtain a `pollable` which - /// can be polled for using `wasi:poll/poll.poll_oneoff`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type input-stream = u32 - - /// Perform a non-blocking read from the stream. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a `stream-status` which, indicates whether further - /// reads are expected to produce data. The returned list will contain up to - /// `len` bytes; it may return fewer than requested, but not more. An - /// empty list and `stream-status:open` indicates no more data is - /// available at this time, and that the pollable given by - /// `subscribe-to-input-stream` will be ready when more data is available. - /// - /// Once a stream has reached the end, subsequent calls to `read` or - /// `skip` will always report `stream-status:ended` rather than producing more - /// data. - /// - /// When the caller gives a `len` of 0, it represents a request to read 0 - /// bytes. This read should always succeed and return an empty list and - /// the current `stream-status`. - /// - /// The `len` parameter is a `u64`, which could represent a list of u8 which - /// is not possible to allocate in wasm32, or not desirable to allocate as - /// as a return value by the callee. The callee may return a list of bytes - /// less than `len` in size while more bytes are available for reading. - read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>> - - /// Read bytes from a stream, after blocking until at least one byte can - /// be read. Except for blocking, identical to `read`. - blocking-read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>> - - /// Skip bytes from a stream. - /// - /// This is similar to the `read` function, but avoids copying the - /// bytes into the instance. - /// - /// Once a stream has reached the end, subsequent calls to read or - /// `skip` will always report end-of-stream rather than producing more - /// data. - /// - /// This function returns the number of bytes skipped, along with a - /// `stream-status` indicating whether the end of the stream was - /// reached. The returned value will be at most `len`; it may be less. - skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result> + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a `stream-status` which, indicates whether further + /// reads are expected to produce data. The returned list will contain up to + /// `len` bytes; it may return fewer than requested, but not more. An + /// empty list and `stream-status:open` indicates no more data is + /// available at this time, and that the pollable given by `subscribe` + /// will be ready when more data is available. + /// + /// Once a stream has reached the end, subsequent calls to `read` or + /// `skip` will always report `stream-status:ended` rather than producing more + /// data. + /// + /// When the caller gives a `len` of 0, it represents a request to read 0 + /// bytes. This read should always succeed and return an empty list and + /// the current `stream-status`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-status>> - /// Skip bytes from a stream, after blocking until at least one byte - /// can be skipped. Except for blocking behavior, identical to `skip`. - blocking-skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result> + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-status>> - /// Create a `pollable` which will resolve once either the specified stream - /// has bytes available to read or the other end of the stream has been - /// closed. - /// The created `pollable` is a child resource of the `input-stream`. - /// Implementations may trap if the `input-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe-to-input-stream: func(this: input-stream) -> pollable + /// Skip bytes from a stream. + /// + /// This is similar to the `read` function, but avoids copying the + /// bytes into the instance. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// This function returns the number of bytes skipped, along with a + /// `stream-status` indicating whether the end of the stream was + /// reached. The returned value will be at most `len`; it may be less. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result> - /// Dispose of the specified `input-stream`, after which it may no longer - /// be used. - /// Implementations may trap if this `input-stream` is dropped while child - /// `pollable` resources are still alive. - /// After this `input-stream` is dropped, implementations may report any - /// corresponding `output-stream` has `stream-state.closed`. - drop-input-stream: func(this: input-stream) + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result> - /// An output bytestream. In the future, this will be replaced by handle - /// types. - /// - /// `output-stream`s are *non-blocking* to the extent practical on - /// underlying platforms. Except where specified otherwise, I/O operations also - /// always return promptly, after the number of bytes that can be written - /// promptly, which could even be zero. To wait for the stream to be ready to - /// accept data, the `subscribe-to-output-stream` function to obtain a - /// `pollable` which can be polled for using `wasi:poll`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type output-stream = u32 + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable + } /// An error for output-stream operations. /// @@ -146,155 +112,174 @@ interface streams { /// future operations. closed } - /// Check readiness for writing. This function never blocks. - /// - /// Returns the number of bytes permitted for the next call to `write`, - /// or an error. Calling `write` with more bytes than this function has - /// permitted will trap. - /// - /// When this function returns 0 bytes, the `subscribe-to-output-stream` - /// pollable will become ready when this function will report at least - /// 1 byte, or an error. - check-write: func( - this: output-stream - ) -> result - /// Perform a write. This function never blocks. - /// - /// Precondition: check-write gave permit of Ok(n) and contents has a - /// length of less than or equal to n. Otherwise, this function will trap. + /// An output bytestream. /// - /// returns Err(closed) without writing if the stream has closed since - /// the last call to check-write provided a permit. - write: func( - this: output-stream, - contents: list - ) -> result<_, write-error> + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result - /// Perform a write of up to 4096 bytes, and then flush the stream. Block - /// until all of these operations are complete, or an error occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe-to-output-stream`, `write`, and `flush`, and is implemented - /// with the following pseudo-code: - /// - /// ```text - /// let pollable = subscribe-to-output-stream(this); - /// while !contents.is_empty() { - /// // Wait for the stream to become writable - /// poll-oneoff(pollable); - /// let Ok(n) = check-write(this); // eliding error handling - /// let len = min(n, contents.len()); - /// let (chunk, rest) = contents.split_at(len); - /// write(this, chunk); // eliding error handling - /// contents = rest; - /// } - /// flush(this); - /// // Wait for completion of `flush` - /// poll-oneoff(pollable); - /// // Check for any errors that arose during `flush` - /// let _ = check-write(this); // eliding error handling - /// ``` - blocking-write-and-flush: func( - this: output-stream, - contents: list - ) -> result<_, write-error> + /// Perform a write. This function never blocks. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, write-error> - /// Request to flush buffered output. This function never blocks. - /// - /// This tells the output-stream that the caller intends any buffered - /// output to be flushed. the output which is expected to be flushed - /// is all that has been passed to `write` prior to this call. - /// - /// Upon calling this function, the `output-stream` will not accept any - /// writes (`check-write` will return `ok(0)`) until the flush has - /// completed. The `subscribe-to-output-stream` pollable will become ready - /// when the flush has completed and the stream can accept more writes. - flush: func( - this: output-stream, - ) -> result<_, write-error> + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, write-error> - /// Request to flush buffered output, and block until flush completes - /// and stream is ready for writing again. - blocking-flush: func( - this: output-stream, - ) -> result<_, write-error> + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, write-error> - /// Create a `pollable` which will resolve once the output-stream - /// is ready for more writing, or an error has occured. When this - /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an - /// error. - /// - /// If the stream is closed, this pollable is always ready immediately. - /// - /// The created `pollable` is a child resource of the `output-stream`. - /// Implementations may trap if the `output-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe-to-output-stream: func(this: output-stream) -> pollable + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, write-error> - /// Write zeroes to a stream. - /// - /// this should be used precisely like `write` with the exact same - /// preconditions (must use check-write first), but instead of - /// passing a list of bytes, you simply pass the number of zero-bytes - /// that should be written. - write-zeroes: func( - this: output-stream, - /// The number of zero-bytes to write - len: u64 - ) -> result<_, write-error> + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable - /// Read from one stream and write to another. - /// - /// This function returns the number of bytes transferred; it may be less - /// than `len`. - /// - /// Unlike other I/O functions, this function blocks until all the data - /// read from the input stream has been written to the output stream. - splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result> + /// Write zeroes to a stream. + /// + /// this should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, write-error> - /// Read from one stream and write to another, with blocking. - /// - /// This is similar to `splice`, except that it blocks until at least - /// one byte can be read. - blocking-splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result> + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, write-error> - /// Forward the entire contents of an input stream to an output stream. - /// - /// This function repeatedly reads from the input stream and writes - /// the data to the output stream, until the end of the input stream - /// is reached, or an error is encountered. - /// - /// Unlike other I/O functions, this function blocks until the end - /// of the input stream is seen and all the data has been written to - /// the output stream. - /// - /// This function returns the number of bytes transferred, and the status of - /// the output stream. - forward: func( - this: output-stream, - /// The stream to read from - src: input-stream - ) -> result> + /// Read from one stream and write to another. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + /// + /// Unlike other I/O functions, this function blocks until all the data + /// read from the input stream has been written to the output stream. + splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result> + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until at least + /// one byte can be read. + blocking-splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result> - /// Dispose of the specified `output-stream`, after which it may no longer - /// be used. - /// Implementations may trap if this `output-stream` is dropped while - /// child `pollable` resources are still alive. - /// After this `output-stream` is dropped, implementations may report any - /// corresponding `input-stream` has `stream-state.closed`. - drop-output-stream: func(this: output-stream) + /// Forward the entire contents of an input stream to an output stream. + /// + /// This function repeatedly reads from the input stream and writes + /// the data to the output stream, until the end of the input stream + /// is reached, or an error is encountered. + /// + /// Unlike other I/O functions, this function blocks until the end + /// of the input stream is seen and all the data has been written to + /// the output stream. + /// + /// This function returns the number of bytes transferred, and the status of + /// the output stream. + forward: func( + /// The stream to read from + src: input-stream + ) -> result> + } } diff --git a/crates/wasi-http/wit/deps/io/world.wit b/crates/wasi-http/wit/deps/io/world.wit new file mode 100644 index 000000000000..8738dba756d9 --- /dev/null +++ b/crates/wasi-http/wit/deps/io/world.wit @@ -0,0 +1,6 @@ +package wasi:io + +world imports { + import streams + import poll +} diff --git a/crates/wasi-http/wit/deps/logging/world.wit b/crates/wasi-http/wit/deps/logging/world.wit new file mode 100644 index 000000000000..7d49acfaddaa --- /dev/null +++ b/crates/wasi-http/wit/deps/logging/world.wit @@ -0,0 +1,5 @@ +package wasi:logging + +world imports { + import logging +} diff --git a/crates/wasi-http/wit/deps/poll/poll.wit b/crates/wasi-http/wit/deps/poll/poll.wit deleted file mode 100644 index a6334c5570fc..000000000000 --- a/crates/wasi-http/wit/deps/poll/poll.wit +++ /dev/null @@ -1,39 +0,0 @@ -package wasi:poll - -/// A poll API intended to let users wait for I/O events on multiple handles -/// at once. -interface poll { - /// A "pollable" handle. - /// - /// This is conceptually represents a `stream<_, _>`, or in other words, - /// a stream that one can wait on, repeatedly, but which does not itself - /// produce any data. It's temporary scaffolding until component-model's - /// async features are ready. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// `pollable` lifetimes are not automatically managed. Users must ensure - /// that they do not outlive the resource they reference. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type pollable = u32 - - /// Dispose of the specified `pollable`, after which it may no longer - /// be used. - drop-pollable: func(this: pollable) - - /// Poll for completion on a set of pollables. - /// - /// The "oneoff" in the name refers to the fact that this function must do a - /// linear scan through the entire list of subscriptions, which may be - /// inefficient if the number is large and the same subscriptions are used - /// many times. In the future, this is expected to be obsoleted by the - /// component model async proposal, which will include a scalable waiting - /// facility. - /// - /// The result list is the same length as the argument - /// list, and indicates the readiness of each corresponding - /// element in that / list, with true indicating ready. - poll-oneoff: func(in: list) -> list -} diff --git a/crates/wasi-http/wit/deps/random/random.wit b/crates/wasi-http/wit/deps/random/random.wit index f2bd6358c139..2a282dab7eb9 100644 --- a/crates/wasi-http/wit/deps/random/random.wit +++ b/crates/wasi-http/wit/deps/random/random.wit @@ -1,25 +1,25 @@ -package wasi:random - /// WASI Random is a random data API. /// /// It is intended to be portable at least between Unix-family platforms and /// Windows. interface random { - /// Return `len` cryptographically-secure pseudo-random bytes. + /// Return `len` cryptographically-secure random or pseudo-random bytes. /// - /// This function must produce data from an adequately seeded - /// cryptographically-secure pseudo-random number generator (CSPRNG), so it - /// must not block, from the perspective of the calling program, and the - /// returned data is always unpredictable. + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. /// - /// This function must always return fresh pseudo-random data. Deterministic - /// environments must omit this function, rather than implementing it with - /// deterministic data. + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. get-random-bytes: func(len: u64) -> list - /// Return a cryptographically-secure pseudo-random `u64` value. + /// Return a cryptographically-secure random or pseudo-random `u64` value. /// - /// This function returns the same type of pseudo-random data as - /// `get-random-bytes`, represented as a `u64`. + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. get-random-u64: func() -> u64 } diff --git a/crates/wasi-http/wit/deps/random/world.wit b/crates/wasi-http/wit/deps/random/world.wit new file mode 100644 index 000000000000..41dc9ed10353 --- /dev/null +++ b/crates/wasi-http/wit/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random + +world imports { + import random + import insecure + import insecure-seed +} diff --git a/crates/wasi-http/wit/deps/sockets/ip-name-lookup.wit b/crates/wasi-http/wit/deps/sockets/ip-name-lookup.wit index f15d19d037da..f998aae140ab 100644 --- a/crates/wasi-http/wit/deps/sockets/ip-name-lookup.wit +++ b/crates/wasi-http/wit/deps/sockets/ip-name-lookup.wit @@ -1,6 +1,6 @@ interface ip-name-lookup { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-address, ip-address-family} @@ -34,36 +34,28 @@ interface ip-name-lookup { /// - /// - /// - - resolve-addresses: func(network: network, name: string, address-family: option, include-unavailable: bool) -> result - - - - type resolve-address-stream = u32 - - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// After which, you should release the stream with `drop-resolve-address-stream`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - resolve-next-address: func(this: resolve-address-stream) -> result, error-code> - - /// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-resolve-address-stream: func(this: resolve-address-stream) - - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: resolve-address-stream) -> pollable + resolve-addresses: func(network: borrow, name: string, address-family: option, include-unavailable: bool) -> result + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code> + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + } } diff --git a/crates/wasi-http/wit/deps/sockets/network.wit b/crates/wasi-http/wit/deps/sockets/network.wit index a198ea8017de..8214eaaf7211 100644 --- a/crates/wasi-http/wit/deps/sockets/network.wit +++ b/crates/wasi-http/wit/deps/sockets/network.wit @@ -1,18 +1,9 @@ -package wasi:sockets interface network { - /// An opaque resource that represents access to (a subset of) the network. + /// An opaque resource that represents access to (a subset of) the network. /// This enables context-based security for networking. /// There is no need for this to map 1:1 to a physical network interface. - /// - /// FYI, In the future this will be replaced by handle types. - type network = u32 - - /// Dispose of the specified `network`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-network: func(this: network) - + resource network /// Error codes. /// diff --git a/crates/wasi-http/wit/deps/sockets/tcp.wit b/crates/wasi-http/wit/deps/sockets/tcp.wit index 3922769b308e..175626cc7620 100644 --- a/crates/wasi-http/wit/deps/sockets/tcp.wit +++ b/crates/wasi-http/wit/deps/sockets/tcp.wit @@ -1,13 +1,9 @@ interface tcp { use wasi:io/streams.{input-stream, output-stream} - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-socket-address, ip-address-family} - /// A TCP socket handle. - type tcp-socket = u32 - - enum shutdown-type { /// Similar to `SHUT_RD` in POSIX. receive, @@ -20,249 +16,234 @@ interface tcp { } - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will - /// implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) - /// - `already-bound`: The socket is already bound. (EINVAL) - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(this: tcp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> - finish-bind: func(this: tcp-socket) -> result<_, error-code> - - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the Connection state - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `already-connected`: The socket is already in the Connection state. (EISCONN) - /// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(this: tcp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> - /// Note: the returned `input-stream` and `output-stream` are child - /// resources of the `tcp-socket`. Implementations may trap if the - /// `tcp-socket` is dropped before both of these streams are dropped. - finish-connect: func(this: tcp-socket) -> result, error-code> - - /// Start listening for new connections. - /// - /// Transitions the socket into the Listener state. - /// - /// Unlike POSIX: - /// - this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - the socket must already be explicitly bound. - /// - /// # Typical `start` errors - /// - `not-bound`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) - /// - `already-listening`: The socket is already in the Listener state. - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A `listen` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-listen: func(this: tcp-socket) -> result<_, error-code> - finish-listen: func(this: tcp-socket) -> result<_, error-code> - - /// Accept a new client socket. - /// - /// The returned socket is bound and in the Connection state. - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// Note: the returned `input-stream` and `output-stream` are child - /// resources of the returned `tcp-socket`. Implementations may trap if the - /// `tcp-socket` is dropped before its child streams are dropped. - /// - /// # Typical errors - /// - `not-listening`: Socket is not in the Listener state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - /// Host implementations must skip over transient errors returned by the native accept syscall. - /// - /// # References - /// - - /// - - /// - - /// - - accept: func(this: tcp-socket) -> result, error-code> - - /// Get the bound local address. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func(this: tcp-socket) -> result - - /// Get the bound remote address. - /// - /// # Typical errors - /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func(this: tcp-socket) -> result - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func(this: tcp-socket) -> ip-address-family + /// A TCP socket handle. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func() -> result<_, error-code> + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `already-connected`: The socket is already in the Connection state. (EISCONN) + /// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func() -> result, error-code> + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike POSIX: + /// - this function is async. This enables interactive WASI hosts to inject permission prompts. + /// - the socket must already be explicitly bound. + /// + /// # Typical `start` errors + /// - `not-bound`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `already-listening`: The socket is already in the Listener state. + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code> + finish-listen: func() -> result<_, error-code> + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `not-listening`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// Host implementations must skip over transient errors returned by the native accept syscall. + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code> + + /// Get the bound local address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result + + /// Get the bound remote address. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. - /// - `already-bound`: (set) The socket is already bound. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - ipv6-only: func(this: tcp-socket) -> result - set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error-code> - - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - set-listen-backlog-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - - /// Equivalent to the SO_KEEPALIVE socket option. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - keep-alive: func(this: tcp-socket) -> result - set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error-code> - - /// Equivalent to the TCP_NODELAY socket option. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - no-delay: func(this: tcp-socket) -> result - set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error-code> + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + ipv6-only: func() -> result + set-ipv6-only: func(value: bool) -> result<_, error-code> + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + set-listen-backlog-size: func(value: u64) -> result<_, error-code> + + /// Equivalent to the SO_KEEPALIVE socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + keep-alive: func() -> result + set-keep-alive: func(value: bool) -> result<_, error-code> + + /// Equivalent to the TCP_NODELAY socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + no-delay: func() -> result + set-no-delay: func(value: bool) -> result<_, error-code> - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `already-listening`: (set) The socket is already in the Listener state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - unicast-hop-limit: func(this: tcp-socket) -> result - set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error-code> - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `already-listening`: (set) The socket is already in the Listener state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - receive-buffer-size: func(this: tcp-socket) -> result - set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - send-buffer-size: func(this: tcp-socket) -> result - set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// The created `pollable` is a child resource of the `tcp-socket`. - /// Implementations may trap if the `tcp-socket` is dropped before all - /// derived `pollable`s created with this function are dropped. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: tcp-socket) -> pollable - - /// Initiate a graceful shutdown. - /// - /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read - /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. - /// Any data still in the receive queue at time of calling `shutdown` will be discarded. - /// - send: the socket is not expecting to send any more data to the peer. All subsequent write - /// operations on the `output-stream` associated with this socket will return an error. - /// - both: same effect as receive & send combined. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error-code> - - /// Dispose of the specified `tcp-socket`, after which it may no longer be used. - /// - /// Similar to the POSIX `close` function. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-tcp-socket: func(this: tcp-socket) + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + unicast-hop-limit: func() -> result + set-unicast-hop-limit: func(value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + receive-buffer-size: func() -> result + set-receive-buffer-size: func(value: u64) -> result<_, error-code> + send-buffer-size: func() -> result + set-send-buffer-size: func(value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + + /// Initiate a graceful shutdown. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> + } } diff --git a/crates/wasi-http/wit/deps/sockets/udp.wit b/crates/wasi-http/wit/deps/sockets/udp.wit index 700b9e247692..01e5b95b97b7 100644 --- a/crates/wasi-http/wit/deps/sockets/udp.wit +++ b/crates/wasi-http/wit/deps/sockets/udp.wit @@ -1,13 +1,9 @@ interface udp { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-socket-address, ip-address-family} - /// A UDP socket handle. - type udp-socket = u32 - - record datagram { data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. remote-address: ip-socket-address, @@ -22,199 +18,197 @@ interface udp { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) - /// - `already-bound`: The socket is already bound. (EINVAL) - /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(this: udp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> - finish-bind: func(this: udp-socket) -> result<_, error-code> - - /// Set the destination address. - /// - /// The local-address is updated based on the best network path to `remote-address`. - /// - /// When a destination address is set: - /// - all receive operations will only return datagrams sent from the provided `remote-address`. - /// - the `send` function can only be used to send to this destination. - /// - /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(this: udp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> - finish-connect: func(this: udp-socket) -> result<_, error-code> - - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// If `max-results` is 0, this function returns successfully with an empty list. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. (EINVAL) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - receive: func(this: udp-socket, max-results: u64) -> result, error-code> - - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without blocking and - /// returns how many messages were actually sent (or queued for sending). - /// - /// This function semantically behaves the same as iterating the `datagrams` list and sequentially - /// sending each individual datagram until either the end of the list has been reached or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// The remote address option is required. To send a message to the "connected" peer, - /// call `remote-address` to get their address. - /// - /// # Typical errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) - /// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - send: func(this: udp-socket, datagrams: list) -> result - - /// Get the current bound address. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func(this: udp-socket) -> result - - /// Get the address set with `connect`. - /// - /// # Typical errors - /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func(this: udp-socket) -> result - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func(this: udp-socket) -> ip-address-family - - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. - /// - `already-bound`: (set) The socket is already bound. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - ipv6-only: func(this: udp-socket) -> result - set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error-code> - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - unicast-hop-limit: func(this: udp-socket) -> result - set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error-code> - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - receive-buffer-size: func(this: udp-socket) -> result - set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> - send-buffer-size: func(this: udp-socket) -> result - set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: udp-socket) -> pollable - - /// Dispose of the specified `udp-socket`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-udp-socket: func(this: udp-socket) + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func() -> result<_, error-code> + + /// Set the destination address. + /// + /// The local-address is updated based on the best network path to `remote-address`. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can only be used to send to this destination. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func() -> result<_, error-code> + + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// If `max-results` is 0, this function returns successfully with an empty list. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. (EINVAL) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code> + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// # Typical errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) + /// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result + + /// Get the current bound address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result + + /// Get the address set with `connect`. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + ipv6-only: func() -> result + set-ipv6-only: func(value: bool) -> result<_, error-code> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + unicast-hop-limit: func() -> result + set-unicast-hop-limit: func(value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + receive-buffer-size: func() -> result + set-receive-buffer-size: func(value: u64) -> result<_, error-code> + send-buffer-size: func() -> result + set-send-buffer-size: func(value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + } } diff --git a/crates/wasi-http/wit/deps/sockets/world.wit b/crates/wasi-http/wit/deps/sockets/world.wit new file mode 100644 index 000000000000..12f3c2868177 --- /dev/null +++ b/crates/wasi-http/wit/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets + +world imports { + import instance-network + import network + import udp + import udp-create-socket + import tcp + import tcp-create-socket + import ip-name-lookup +} diff --git a/crates/wasi-http/wit/main.wit b/crates/wasi-http/wit/main.wit index 753770ad22ab..739e1bd4ac48 100644 --- a/crates/wasi-http/wit/main.wit +++ b/crates/wasi-http/wit/main.wit @@ -18,7 +18,7 @@ world preview1-adapter-reactor { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/exit diff --git a/crates/wasi-http/wit/test.wit b/crates/wasi-http/wit/test.wit index 4543cb194af1..0b6bd28e997d 100644 --- a/crates/wasi-http/wit/test.wit +++ b/crates/wasi-http/wit/test.wit @@ -2,6 +2,7 @@ world test-reactor { import wasi:cli/environment + import wasi:io/poll import wasi:io/streams import wasi:filesystem/types import wasi:filesystem/preopens @@ -19,7 +20,7 @@ world test-reactor { } world test-command { - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/stdin @@ -28,7 +29,7 @@ world test-command { } world test-command-with-sockets { - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/stdin diff --git a/crates/wasi-preview1-component-adapter/build.rs b/crates/wasi-preview1-component-adapter/build.rs index a901cc7037d6..09f56df1526c 100644 --- a/crates/wasi-preview1-component-adapter/build.rs +++ b/crates/wasi-preview1-component-adapter/build.rs @@ -84,8 +84,6 @@ fn build_raw_intrinsics() -> Vec { funcs.function(1); funcs.function(0); funcs.function(1); - funcs.function(0); - funcs.function(1); module.section(&funcs); // Declare the globals. @@ -106,14 +104,6 @@ fn build_raw_intrinsics() -> Vec { }, &ConstExpr::i32_const(0), ); - // stderr_stream - globals.global( - GlobalType { - val_type: ValType::I32, - mutable: true, - }, - &ConstExpr::i32_const(123), - ); module.section(&globals); // Here the `code` section is defined. This is tricky because an offset is @@ -125,7 +115,7 @@ fn build_raw_intrinsics() -> Vec { // code section. let mut code = Vec::new(); - 6u32.encode(&mut code); // number of functions + 4u32.encode(&mut code); // number of functions let global_get = 0x23; let global_set = 0x24; @@ -152,8 +142,6 @@ fn build_raw_intrinsics() -> Vec { let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state - let stderr_stream_ref1 = encode(&mut code, 2, global_get); // get_stderr_stream - let stderr_stream_ref2 = encode(&mut code, 2, global_set); // set_stderr_stream module.section(&RawSection { id: SectionId::Code as u8, @@ -171,7 +159,7 @@ fn build_raw_intrinsics() -> Vec { linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 9u32.encode(&mut subsection); // 9 symbols (6 functions + 3 globals) + 6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals) subsection.push(0x00); // SYMTAB_FUNCTION 0x00.encode(&mut subsection); // flags @@ -193,16 +181,6 @@ fn build_raw_intrinsics() -> Vec { 3u32.encode(&mut subsection); // function index "set_allocation_state".encode(&mut subsection); // symbol name - subsection.push(0x00); // SYMTAB_FUNCTION - 0x00.encode(&mut subsection); // flags - 4u32.encode(&mut subsection); // function index - "get_stderr_stream".encode(&mut subsection); // symbol name - - subsection.push(0x00); // SYMTAB_FUNCTION - 0x00.encode(&mut subsection); // flags - 5u32.encode(&mut subsection); // function index - "set_stderr_stream".encode(&mut subsection); // symbol name - subsection.push(0x02); // SYMTAB_GLOBAL 0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL) 0u32.encode(&mut subsection); // global index @@ -213,11 +191,6 @@ fn build_raw_intrinsics() -> Vec { 1u32.encode(&mut subsection); // global index "allocation_state".encode(&mut subsection); // symbol name - subsection.push(0x02); // SYMTAB_GLOBAL - 0x00.encode(&mut subsection); // flags - 2u32.encode(&mut subsection); // global index - "stderr_stream".encode(&mut subsection); // symbol name - subsection.encode(&mut linking); module.section(&CustomSection { name: "linking".into(), @@ -230,31 +203,23 @@ fn build_raw_intrinsics() -> Vec { { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 6u32.encode(&mut reloc); // 6 relocations + 4u32.encode(&mut reloc); // 4 relocations reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB internal_state_ptr_ref1.encode(&mut reloc); // offset - 6u32.encode(&mut reloc); // symbol index + 4u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB internal_state_ptr_ref2.encode(&mut reloc); // offset - 6u32.encode(&mut reloc); // symbol index + 4u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB allocation_state_ref1.encode(&mut reloc); // offset - 7u32.encode(&mut reloc); // symbol index + 5u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB allocation_state_ref2.encode(&mut reloc); // offset - 7u32.encode(&mut reloc); // symbol index - - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - stderr_stream_ref1.encode(&mut reloc); // offset - 8u32.encode(&mut reloc); // symbol index - - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - stderr_stream_ref2.encode(&mut reloc); // offset - 8u32.encode(&mut reloc); // symbol index + 5u32.encode(&mut reloc); // symbol index module.section(&CustomSection { name: "reloc.CODE".into(), diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs index 6045358bde36..354de6f96801 100644 --- a/crates/wasi-preview1-component-adapter/src/descriptors.rs +++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs @@ -1,13 +1,10 @@ use crate::bindings::wasi::cli::{ - stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin, - terminal_stdout, + stderr, stdin, stdout, terminal_stderr, terminal_stdin, terminal_stdout, }; use crate::bindings::wasi::filesystem::types as filesystem; -use crate::bindings::wasi::io::streams::{self, InputStream, OutputStream}; +use crate::bindings::wasi::io::streams::{InputStream, OutputStream}; use crate::bindings::wasi::sockets::tcp; -use crate::{ - set_stderr_stream, BlockingMode, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr, -}; +use crate::{BlockingMode, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr}; use core::cell::{Cell, UnsafeCell}; use core::mem::MaybeUninit; use wasi::{Errno, Fd}; @@ -24,36 +21,15 @@ pub enum Descriptor { Streams(Streams), } -impl Drop for Descriptor { - fn drop(&mut self) { - match self { - Descriptor::Streams(stream) => { - if let Some(input) = stream.input.get() { - streams::drop_input_stream(input); - } - if let Some(output) = stream.output.get() { - streams::drop_output_stream(output); - } - match &stream.type_ { - StreamType::File(file) => filesystem::drop_descriptor(file.fd), - StreamType::Socket(_) => unreachable!(), - StreamType::Stdio(_) => {} - } - } - Descriptor::Closed(_) => {} - } - } -} - /// Input and/or output wasi-streams, along with a stream type that /// identifies what kind of stream they are and possibly supporting /// type-specific operations like seeking. pub struct Streams { /// The input stream, if present. - pub input: Cell>, + pub input: UnsafeCell>, /// The output stream, if present. - pub output: Cell>, + pub output: UnsafeCell>, /// Information about the source of the stream. pub type_: StreamType, @@ -61,59 +37,69 @@ pub struct Streams { impl Streams { /// Return the input stream, initializing it on the fly if needed. - pub fn get_read_stream(&self) -> Result { - match &self.input.get() { - Some(wasi_stream) => Ok(*wasi_stream), - None => match &self.type_ { - // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read - // or write. - StreamType::File(File { - descriptor_type: filesystem::DescriptorType::Directory, - .. - }) => Err(wasi::ERRNO_BADF), - // For files, we may have adjusted the position for seeking, so - // create a new stream. - StreamType::File(file) => { - let input = filesystem::read_via_stream(file.fd, file.position.get())?; - self.input.set(Some(input)); - Ok(input) + pub fn get_read_stream(&self) -> Result<&InputStream, Errno> { + match unsafe { &*self.input.get() } { + Some(wasi_stream) => Ok(wasi_stream), + None => { + let input = match &self.type_ { + // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read + // or write. + StreamType::File(File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }) => return Err(wasi::ERRNO_BADF), + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let input = file.fd.read_via_stream(file.position.get())?; + input + } + _ => return Err(wasi::ERRNO_BADF), + }; + unsafe { + *self.input.get() = Some(input); + Ok((*self.input.get()).as_ref().trapping_unwrap()) } - _ => Err(wasi::ERRNO_BADF), - }, + } } } /// Return the output stream, initializing it on the fly if needed. - pub fn get_write_stream(&self) -> Result { - match &self.output.get() { - Some(wasi_stream) => Ok(*wasi_stream), - None => match &self.type_ { - // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read - // or write. - StreamType::File(File { - descriptor_type: filesystem::DescriptorType::Directory, - .. - }) => Err(wasi::ERRNO_BADF), - // For files, we may have adjusted the position for seeking, so - // create a new stream. - StreamType::File(file) => { - let output = if file.append { - filesystem::append_via_stream(file.fd)? - } else { - filesystem::write_via_stream(file.fd, file.position.get())? - }; - self.output.set(Some(output)); - Ok(output) + pub fn get_write_stream(&self) -> Result<&OutputStream, Errno> { + match unsafe { &*self.output.get() } { + Some(wasi_stream) => Ok(wasi_stream), + None => { + let output = match &self.type_ { + // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read + // or write. + StreamType::File(File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }) => return Err(wasi::ERRNO_BADF), + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let output = if file.append { + file.fd.append_via_stream()? + } else { + file.fd.write_via_stream(file.position.get())? + }; + output + } + _ => return Err(wasi::ERRNO_BADF), + }; + unsafe { + *self.output.get() = Some(output); + Ok((*self.output.get()).as_ref().trapping_unwrap()) } - _ => Err(wasi::ERRNO_BADF), - }, + } } } } #[allow(dead_code)] // until Socket is implemented pub enum StreamType { - /// Stream is used for implementing stdio. + /// Streams for implementing stdio. Stdio(IsATTY), /// Streaming data with a file. @@ -161,47 +147,34 @@ impl Descriptors { preopens: Cell::new(None), }; - let stdin = stdin::get_stdin(); let stdin_isatty = match terminal_stdin::get_terminal_stdin() { - Some(t) => { - terminal_input::drop_terminal_input(t); - IsATTY::Yes - } + Some(t) => IsATTY::Yes, None => IsATTY::No, }; - let stdout = stdout::get_stdout(); let stdout_isatty = match terminal_stdout::get_terminal_stdout() { - Some(t) => { - terminal_output::drop_terminal_output(t); - IsATTY::Yes - } + Some(t) => IsATTY::Yes, None => IsATTY::No, }; - let stderr = stderr::get_stderr(); - unsafe { set_stderr_stream(stderr) }; let stderr_isatty = match terminal_stderr::get_terminal_stderr() { - Some(t) => { - terminal_output::drop_terminal_output(t); - IsATTY::Yes - } + Some(t) => IsATTY::Yes, None => IsATTY::No, }; d.push(Descriptor::Streams(Streams { - input: Cell::new(Some(stdin)), - output: Cell::new(None), + input: UnsafeCell::new(Some(stdin::get_stdin())), + output: UnsafeCell::new(None), type_: StreamType::Stdio(stdin_isatty), })) .trapping_unwrap(); d.push(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stdout)), + input: UnsafeCell::new(None), + output: UnsafeCell::new(Some(stdout::get_stdout())), type_: StreamType::Stdio(stdout_isatty), })) .trapping_unwrap(); d.push(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stderr)), + input: UnsafeCell::new(None), + output: UnsafeCell::new(Some(stderr::get_stderr())), type_: StreamType::Stdio(stderr_isatty), })) .trapping_unwrap(); @@ -224,14 +197,18 @@ impl Descriptors { std::slice::from_raw_parts(list.base, list.len) }; for preopen in preopens { + // Acquire ownership of the descriptor, leaving the rest of the + // `Preopen` struct in place. + let descriptor = unsafe { preopen.descriptor.assume_init_read() }; // Expectation is that the descriptor index is initialized with // stdio (0,1,2) and no others, so that preopens are 3.. + let descriptor_type = descriptor.get_type().trapping_unwrap(); d.push(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), + input: UnsafeCell::new(None), + output: UnsafeCell::new(None), type_: StreamType::File(File { - fd: preopen.descriptor, - descriptor_type: filesystem::get_type(preopen.descriptor).trapping_unwrap(), + fd: descriptor, + descriptor_type, position: Cell::new(0), append: false, blocking_mode: BlockingMode::Blocking, @@ -387,12 +364,12 @@ impl Descriptors { } #[allow(dead_code)] // until Socket is implemented - pub fn get_socket(&self, fd: Fd) -> Result { + pub fn get_socket(&self, fd: Fd) -> Result<&tcp::TcpSocket, Errno> { match self.get(fd)? { Descriptor::Streams(Streams { type_: StreamType::Socket(socket), .. - }) => Ok(*socket), + }) => Ok(&*socket), Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), _ => Err(wasi::ERRNO_INVAL), } @@ -435,14 +412,14 @@ impl Descriptors { self.get_stream_with_error(fd, wasi::ERRNO_SPIPE) } - pub fn get_read_stream(&self, fd: Fd) -> Result { + pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_read_stream(), Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), } } - pub fn get_write_stream(&self, fd: Fd) -> Result { + pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_write_stream(), Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), @@ -452,7 +429,9 @@ impl Descriptors { #[repr(C)] pub struct Preopen { - pub descriptor: u32, + /// This is `MaybeUninit` because we take ownership of the `Descriptor` to + /// put it in our own table. + pub descriptor: MaybeUninit, pub path: WasmStr, } diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index d75e8caa5ef0..e5096ce21633 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -1,18 +1,15 @@ -#![allow(unused_variables)] // TODO: remove this when more things are implemented - use crate::bindings::wasi::cli::exit; use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock}; use crate::bindings::wasi::filesystem::types as filesystem; +use crate::bindings::wasi::io::poll; use crate::bindings::wasi::io::streams; -use crate::bindings::wasi::poll::poll; use crate::bindings::wasi::random::random; use crate::bindings::wasi::sockets::network; -use core::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use core::cell::{Cell, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; use core::hint::black_box; use core::mem::{self, align_of, forget, size_of, ManuallyDrop, MaybeUninit}; -use core::ops::{Deref, DerefMut}; use core::ptr::{self, null_mut}; use core::slice; use poll::Pollable; @@ -40,7 +37,7 @@ pub mod bindings { // can't support in these special core-wasm adapters. // Instead, we manually define the bindings for these functions in // terms of raw pointers. - skip: ["run", "get-environment", "poll-oneoff"], + skip: ["run", "get-environment", "poll-list"], }); #[cfg(feature = "reactor")] @@ -55,7 +52,7 @@ pub mod bindings { // can't support in these special core-wasm adapters. // Instead, we manually define the bindings for these functions in // terms of raw pointers. - skip: ["get-environment", "poll-oneoff"], + skip: ["get-environment", "poll-list"], }); } @@ -420,10 +417,11 @@ pub unsafe extern "C" fn fd_advise( _ => return ERRNO_INVAL, }; State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - filesystem::advise(file.fd, offset, len, advice)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_seekable_file(fd)?; + file.fd.advise(offset, len, advice)?; + Ok(()) + }) }) } @@ -432,11 +430,12 @@ pub unsafe extern "C" fn fd_advise( #[no_mangle] pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { State::with(|state| { - let ds = state.descriptors(); - // For not-files, fail with BADF - let file = ds.get_file(fd)?; - // For all files, fail with NOTSUP, because this call does not exist in preview 2. - Err(wasi::ERRNO_NOTSUP) + state.with_descriptors(|ds| { + // For not-files, fail with BADF + let file = ds.get_file(fd)?; + // For all files, fail with NOTSUP, because this call does not exist in preview 2. + Err(wasi::ERRNO_NOTSUP) + }) }) } @@ -452,7 +451,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { drop(state.dirent_cache.stream.replace(None)); } - let desc = state.descriptors_mut().close(fd)?; + let _ = state.with_descriptors_mut(|ds: &mut Descriptors| ds.close(fd))?; Ok(()) }) } @@ -462,10 +461,11 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - filesystem::sync_data(file.fd)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_file(fd)?; + file.fd.sync_data()?; + Ok(()) + }) }) } @@ -473,122 +473,126 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - State::with(|state| match state.descriptors().get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => { - let flags = filesystem::get_flags(file.fd)?; - let type_ = filesystem::get_type(file.fd)?; - match type_ { - filesystem::DescriptorType::Directory => { - // Hard-coded set of rights expected by many userlands: - let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY - | wasi::RIGHTS_PATH_CREATE_FILE - | wasi::RIGHTS_PATH_LINK_SOURCE - | wasi::RIGHTS_PATH_LINK_TARGET - | wasi::RIGHTS_PATH_OPEN - | wasi::RIGHTS_FD_READDIR - | wasi::RIGHTS_PATH_READLINK - | wasi::RIGHTS_PATH_RENAME_SOURCE - | wasi::RIGHTS_PATH_RENAME_TARGET - | wasi::RIGHTS_PATH_SYMLINK - | wasi::RIGHTS_PATH_REMOVE_DIRECTORY - | wasi::RIGHTS_PATH_UNLINK_FILE - | wasi::RIGHTS_PATH_FILESTAT_GET - | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES - | wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_FILESTAT_SET_TIMES; - - let fs_rights_inheriting = fs_rights_base - | wasi::RIGHTS_FD_DATASYNC - | wasi::RIGHTS_FD_READ - | wasi::RIGHTS_FD_SEEK - | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS - | wasi::RIGHTS_FD_SYNC - | wasi::RIGHTS_FD_TELL - | wasi::RIGHTS_FD_WRITE - | wasi::RIGHTS_FD_ADVISE - | wasi::RIGHTS_FD_ALLOCATE - | wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_FILESTAT_SET_SIZE - | wasi::RIGHTS_FD_FILESTAT_SET_TIMES - | wasi::RIGHTS_POLL_FD_READWRITE; - - stat.write(Fdstat { - fs_filetype: wasi::FILETYPE_DIRECTORY, - fs_flags: 0, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) - } - _ => { - let fs_filetype = type_.into(); + State::with(|state| { + state.with_descriptors(|ds| { + match ds.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { + let flags = file.fd.get_flags()?; + let type_ = file.fd.get_type()?; + match type_ { + filesystem::DescriptorType::Directory => { + // Hard-coded set of rights expected by many userlands: + let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY + | wasi::RIGHTS_PATH_CREATE_FILE + | wasi::RIGHTS_PATH_LINK_SOURCE + | wasi::RIGHTS_PATH_LINK_TARGET + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_PATH_READLINK + | wasi::RIGHTS_PATH_RENAME_SOURCE + | wasi::RIGHTS_PATH_RENAME_TARGET + | wasi::RIGHTS_PATH_SYMLINK + | wasi::RIGHTS_PATH_REMOVE_DIRECTORY + | wasi::RIGHTS_PATH_UNLINK_FILE + | wasi::RIGHTS_PATH_FILESTAT_GET + | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES; + + let fs_rights_inheriting = fs_rights_base + | wasi::RIGHTS_FD_DATASYNC + | wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_SEEK + | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS + | wasi::RIGHTS_FD_SYNC + | wasi::RIGHTS_FD_TELL + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_ADVISE + | wasi::RIGHTS_FD_ALLOCATE + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_SIZE + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES + | wasi::RIGHTS_POLL_FD_READWRITE; + + stat.write(Fdstat { + fs_filetype: wasi::FILETYPE_DIRECTORY, + fs_flags: 0, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) + } + _ => { + let fs_filetype = type_.into(); - let mut fs_flags = 0; - let mut fs_rights_base = !0; - if !flags.contains(filesystem::DescriptorFlags::READ) { - fs_rights_base &= !RIGHTS_FD_READ; - } - if !flags.contains(filesystem::DescriptorFlags::WRITE) { - fs_rights_base &= !RIGHTS_FD_WRITE; - } - if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { - fs_flags |= FDFLAGS_DSYNC; - } - if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { - fs_flags |= FDFLAGS_RSYNC; - } - if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { - fs_flags |= FDFLAGS_SYNC; + let mut fs_flags = 0; + let mut fs_rights_base = !0; + if !flags.contains(filesystem::DescriptorFlags::READ) { + fs_rights_base &= !RIGHTS_FD_READ; + } + if !flags.contains(filesystem::DescriptorFlags::WRITE) { + fs_rights_base &= !RIGHTS_FD_WRITE; + } + if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { + fs_flags |= FDFLAGS_DSYNC; + } + if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { + fs_flags |= FDFLAGS_RSYNC; + } + if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { + fs_flags |= FDFLAGS_SYNC; + } + if file.append { + fs_flags |= FDFLAGS_APPEND; + } + if matches!(file.blocking_mode, BlockingMode::NonBlocking) { + fs_flags |= FDFLAGS_NONBLOCK; + } + let fs_rights_inheriting = fs_rights_base; + + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) + } } - if file.append { - fs_flags |= FDFLAGS_APPEND; + } + Descriptor::Streams(Streams { + input, + output, + type_: StreamType::Stdio(isatty), + }) => { + let fs_flags = 0; + let mut fs_rights_base = 0; + if (*input.get()).is_some() { + fs_rights_base |= RIGHTS_FD_READ; } - if matches!(file.blocking_mode, BlockingMode::NonBlocking) { - fs_flags |= FDFLAGS_NONBLOCK; + if (*output.get()).is_some() { + fs_rights_base |= RIGHTS_FD_WRITE; } let fs_rights_inheriting = fs_rights_base; - stat.write(Fdstat { - fs_filetype, + fs_filetype: isatty.filetype(), fs_flags, fs_rights_base, fs_rights_inheriting, }); Ok(()) } + Descriptor::Closed(_) => Err(ERRNO_BADF), + Descriptor::Streams(Streams { + input: _, + output: _, + type_: StreamType::Socket(_), + }) => unreachable!(), } - } - Descriptor::Streams(Streams { - input, - output, - type_: StreamType::Stdio(isatty), - }) => { - let fs_flags = 0; - let mut fs_rights_base = 0; - if input.get().is_some() { - fs_rights_base |= RIGHTS_FD_READ; - } - if output.get().is_some() { - fs_rights_base |= RIGHTS_FD_WRITE; - } - let fs_rights_inheriting = fs_rights_base; - stat.write(Fdstat { - fs_filetype: isatty.filetype(), - fs_flags, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) - } - Descriptor::Closed(_) => Err(ERRNO_BADF), - Descriptor::Streams(Streams { - input, - output, - type_: StreamType::Socket(_), - }) => unreachable!(), + }) }) } @@ -602,21 +606,22 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { } State::with(|state| { - let mut ds = state.descriptors_mut(); - let file = match ds.get_mut(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) if !file.is_dir() => file, - _ => Err(wasi::ERRNO_BADF)?, - }; - file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND; - file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { - BlockingMode::NonBlocking - } else { - BlockingMode::Blocking - }; - Ok(()) + state.with_descriptors_mut(|ds: &mut Descriptors| { + let file = match ds.get_mut(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) if !file.is_dir() => file, + _ => Err(wasi::ERRNO_BADF)?, + }; + file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND; + file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { + BlockingMode::NonBlocking + } else { + BlockingMode::Blocking + }; + Ok(()) + }) }) } @@ -627,9 +632,11 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( fs_rights_base: Rights, fs_rights_inheriting: Rights, ) -> Errno { - State::with(|state| match state.descriptors().get(fd)? { - Descriptor::Streams(..) => Ok(()), - Descriptor::Closed(..) => Err(wasi::ERRNO_BADF), + State::with(|state| { + state.with_descriptors(|ds| match ds.get(fd)? { + Descriptor::Streams(..) => Ok(()), + Descriptor::Closed(..) => Err(wasi::ERRNO_BADF), + }) }) } @@ -637,46 +644,47 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { - let ds = state.descriptors(); - match ds.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => { - let stat = filesystem::stat(file.fd)?; - let metadata_hash = filesystem::metadata_hash(file.fd)?; - let filetype = stat.type_.into(); - *buf = Filestat { - dev: 1, - ino: metadata_hash.lower, - filetype, - nlink: stat.link_count, - size: stat.size, - atim: datetime_to_timestamp(stat.data_access_timestamp), - mtim: datetime_to_timestamp(stat.data_modification_timestamp), - ctim: datetime_to_timestamp(stat.status_change_timestamp), - }; - Ok(()) - } - // Stdio is all zero fields, except for filetype character device - Descriptor::Streams(Streams { - type_: StreamType::Stdio(isatty), - .. - }) => { - *buf = Filestat { - dev: 0, - ino: 0, - filetype: isatty.filetype(), - nlink: 0, - size: 0, - atim: 0, - mtim: 0, - ctim: 0, - }; - Ok(()) + state.with_descriptors(|ds| { + match ds.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { + let stat = file.fd.stat()?; + let metadata_hash = file.fd.metadata_hash()?; + let filetype = stat.type_.into(); + *buf = Filestat { + dev: 1, + ino: metadata_hash.lower, + filetype, + nlink: stat.link_count, + size: stat.size, + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), + }; + Ok(()) + } + // Stdio is all zero fields, except for filetype character device + Descriptor::Streams(Streams { + type_: StreamType::Stdio(isatty), + .. + }) => { + *buf = Filestat { + dev: 0, + ino: 0, + filetype: isatty.filetype(), + nlink: 0, + size: 0, + atim: 0, + mtim: 0, + ctim: 0, + }; + Ok(()) + } + _ => Err(wasi::ERRNO_BADF), } - _ => Err(wasi::ERRNO_BADF), - } + }) }) } @@ -685,10 +693,11 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - filesystem::set_size(file.fd, size)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_file(fd)?; + file.fd.set_size(size)?; + Ok(()) + }) }) } @@ -727,10 +736,11 @@ pub unsafe extern "C" fn fd_filestat_set_times( mtim, fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, )?; - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - filesystem::set_times(file.fd, atim, mtim)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_file(fd)?; + file.fd.set_times(atim, mtim)?; + Ok(()) + }) }) } @@ -758,22 +768,23 @@ pub unsafe extern "C" fn fd_pread( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - let (data, end) = state - .import_alloc - .with_buffer(ptr, len, || filesystem::read(file.fd, len as u64, offset))?; - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); - - let len = data.len(); - forget(data); - if !end && len == 0 { - Err(ERRNO_INTR) - } else { - *nread = len; - Ok(()) - } + state.with_descriptors(|ds| { + let file = ds.get_file(fd)?; + let (data, end) = state + .import_alloc + .with_buffer(ptr, len, || file.fd.read(len as u64, offset))?; + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + + let len = data.len(); + forget(data); + if !end && len == 0 { + Err(ERRNO_INTR) + } else { + *nread = len; + Ok(()) + } + }) }) } @@ -785,20 +796,22 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { AllocationState::StackAllocated | AllocationState::StateAllocated ) { State::with(|state| { - if let Some(preopen) = state.descriptors().get_preopen(fd) { - buf.write(Prestat { - tag: 0, - u: PrestatU { - dir: PrestatDir { - pr_name_len: preopen.path.len, + state.with_descriptors(|ds| { + if let Some(preopen) = ds.get_preopen(fd) { + buf.write(Prestat { + tag: 0, + u: PrestatU { + dir: PrestatDir { + pr_name_len: preopen.path.len, + }, }, - }, - }); + }); - Ok(()) - } else { - Err(ERRNO_BADF) - } + Ok(()) + } else { + Err(ERRNO_BADF) + } + }) }) } else { ERRNO_BADF @@ -809,16 +822,18 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno { State::with(|state| { - if let Some(preopen) = state.descriptors().get_preopen(fd) { - if preopen.path.len > path_max_len as usize { - Err(ERRNO_NAMETOOLONG) + state.with_descriptors(|ds| { + if let Some(preopen) = ds.get_preopen(fd) { + if preopen.path.len > path_max_len as usize { + Err(ERRNO_NAMETOOLONG) + } else { + ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len); + Ok(()) + } } else { - ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len); - Ok(()) + Err(ERRNO_NOTDIR) } - } else { - Err(ERRNO_NOTDIR) - } + }) }) } @@ -846,11 +861,12 @@ pub unsafe extern "C" fn fd_pwrite( let len = (*iovs_ptr).buf_len; State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - let bytes = filesystem::write(file.fd, slice::from_raw_parts(ptr, len), offset)?; - *nwritten = bytes as usize; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_seekable_file(fd)?; + let bytes = file.fd.write(slice::from_raw_parts(ptr, len), offset)?; + *nwritten = bytes as usize; + Ok(()) + }) }) } @@ -877,41 +893,43 @@ pub unsafe extern "C" fn fd_read( let len = (*iovs_ptr).buf_len; State::with(|state| { - match state.descriptors().get(fd)? { - Descriptor::Streams(streams) => { - let blocking_mode = if let StreamType::File(file) = &streams.type_ { - file.blocking_mode - } else { - BlockingMode::Blocking - }; + state.with_descriptors(|ds| { + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let blocking_mode = if let StreamType::File(file) = &streams.type_ { + file.blocking_mode + } else { + BlockingMode::Blocking + }; - let read_len = u64::try_from(len).trapping_unwrap(); - let wasi_stream = streams.get_read_stream()?; - let (data, stream_stat) = state - .import_alloc - .with_buffer(ptr, len, || blocking_mode.read(wasi_stream, read_len)) - .map_err(|_| ERRNO_IO)?; + let read_len = u64::try_from(len).trapping_unwrap(); + let wasi_stream = streams.get_read_stream()?; + let (data, stream_stat) = state + .import_alloc + .with_buffer(ptr, len, || blocking_mode.read(wasi_stream, read_len)) + .map_err(|_| ERRNO_IO)?; - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); - // If this is a file, keep the current-position pointer up to date. - if let StreamType::File(file) = &streams.type_ { - file.position - .set(file.position.get() + data.len() as filesystem::Filesize); - } + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + file.position + .set(file.position.get() + data.len() as filesystem::Filesize); + } - let len = data.len(); - forget(data); - if stream_stat == crate::streams::StreamStatus::Open && len == 0 { - Err(ERRNO_INTR) - } else { - *nread = len; - Ok(()) + let len = data.len(); + forget(data); + if stream_stat == crate::streams::StreamStatus::Open && len == 0 { + Err(ERRNO_INTR) + } else { + *nread = len; + Ok(()) + } } + Descriptor::Closed(_) => Err(ERRNO_BADF), } - Descriptor::Closed(_) => Err(ERRNO_BADF), - } + }) }) } @@ -955,108 +973,113 @@ pub unsafe extern "C" fn fd_readdir( // Compute the inode of `.` so that the iterator can produce an entry // for it. - let ds = state.descriptors(); - let dir = ds.get_dir(fd)?; - - let mut iter; - match stream { - // All our checks passed and a dirent cache was available with a - // prior stream. Construct an iterator which will yield its first - // entry from cache and is additionally resuming at the `cookie` - // specified. - Some(stream) => { - iter = DirectoryEntryIterator { - stream, - state, - cookie, - use_cache: true, - dir_descriptor: dir.fd, + state.with_descriptors(|ds| { + let dir = ds.get_dir(fd)?; + + let mut iter; + match stream { + // All our checks passed and a dirent cache was available with a + // prior stream. Construct an iterator which will yield its first + // entry from cache and is additionally resuming at the `cookie` + // specified. + Some(stream) => { + iter = DirectoryEntryIterator { + stream, + state, + cookie, + use_cache: true, + dir_descriptor: &dir.fd, + } } - } - // Either a dirent stream wasn't previously available, a different - // cookie was requested, or a brand new directory is now being read. - // In these situations fall back to resuming reading the directory - // from scratch, and the `cookie` value indicates how many items - // need skipping. - None => { - iter = DirectoryEntryIterator { - state, - cookie: wasi::DIRCOOKIE_START, - use_cache: false, - stream: DirectoryEntryStream(filesystem::read_directory(dir.fd)?), - dir_descriptor: dir.fd, - }; + // Either a dirent stream wasn't previously available, a different + // cookie was requested, or a brand new directory is now being read. + // In these situations fall back to resuming reading the directory + // from scratch, and the `cookie` value indicates how many items + // need skipping. + None => { + iter = DirectoryEntryIterator { + state, + cookie: wasi::DIRCOOKIE_START, + use_cache: false, + stream: DirectoryEntryStream(dir.fd.read_directory()?), + dir_descriptor: &dir.fd, + }; - // Skip to the entry that is requested by the `cookie` - // parameter. - for _ in wasi::DIRCOOKIE_START..cookie { - match iter.next() { - Some(Ok(_)) => {} - Some(Err(e)) => return Err(e), - None => return Ok(()), + // Skip to the entry that is requested by the `cookie` + // parameter. + for _ in wasi::DIRCOOKIE_START..cookie { + match iter.next() { + Some(Ok(_)) => {} + Some(Err(e)) => return Err(e), + None => return Ok(()), + } } } - } - }; - - while buf.len() > 0 { - let (dirent, name) = match iter.next() { - Some(Ok(pair)) => pair, - Some(Err(e)) => return Err(e), - None => break, }; - // Copy a `dirent` describing this entry into the destination `buf`, - // truncating it if it doesn't fit entirely. - let bytes = slice::from_raw_parts( - (&dirent as *const wasi::Dirent).cast::(), - size_of::(), - ); - let dirent_bytes_to_copy = buf.len().min(bytes.len()); - buf[..dirent_bytes_to_copy].copy_from_slice(&bytes[..dirent_bytes_to_copy]); - buf = &mut buf[dirent_bytes_to_copy..]; - - // Copy the name bytes into the output `buf`, truncating it if it - // doesn't fit. - // - // Note that this might be a 0-byte copy if the `dirent` was - // truncated or fit entirely into the destination. - let name_bytes_to_copy = buf.len().min(name.len()); - ptr::copy_nonoverlapping(name.as_ptr().cast(), buf.as_mut_ptr(), name_bytes_to_copy); - - buf = &mut buf[name_bytes_to_copy..]; - - // If the buffer is empty then that means the value may be - // truncated, so save the state of the iterator in our dirent cache - // and return. - // - // Note that `cookie - 1` is stored here since `iter.cookie` stores - // the address of the next item, and we're rewinding one item since - // the current item is truncated and will want to resume from that - // in the future. - // - // Additionally note that this caching step is skipped if the name - // to store doesn't actually fit in the dirent cache's path storage. - // In that case there's not much we can do and let the next call to - // `fd_readdir` start from scratch. - if buf.len() == 0 && name.len() <= DIRENT_CACHE { - let DirectoryEntryIterator { stream, cookie, .. } = iter; - state.dirent_cache.stream.set(Some(stream)); - state.dirent_cache.for_fd.set(fd); - state.dirent_cache.cookie.set(cookie - 1); - state.dirent_cache.cached_dirent.set(dirent); - ptr::copy( - name.as_ptr().cast::(), - (*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8, - name.len(), + while buf.len() > 0 { + let (dirent, name) = match iter.next() { + Some(Ok(pair)) => pair, + Some(Err(e)) => return Err(e), + None => break, + }; + + // Copy a `dirent` describing this entry into the destination `buf`, + // truncating it if it doesn't fit entirely. + let bytes = slice::from_raw_parts( + (&dirent as *const wasi::Dirent).cast::(), + size_of::(), ); - break; + let dirent_bytes_to_copy = buf.len().min(bytes.len()); + buf[..dirent_bytes_to_copy].copy_from_slice(&bytes[..dirent_bytes_to_copy]); + buf = &mut buf[dirent_bytes_to_copy..]; + + // Copy the name bytes into the output `buf`, truncating it if it + // doesn't fit. + // + // Note that this might be a 0-byte copy if the `dirent` was + // truncated or fit entirely into the destination. + let name_bytes_to_copy = buf.len().min(name.len()); + ptr::copy_nonoverlapping( + name.as_ptr().cast(), + buf.as_mut_ptr(), + name_bytes_to_copy, + ); + + buf = &mut buf[name_bytes_to_copy..]; + + // If the buffer is empty then that means the value may be + // truncated, so save the state of the iterator in our dirent cache + // and return. + // + // Note that `cookie - 1` is stored here since `iter.cookie` stores + // the address of the next item, and we're rewinding one item since + // the current item is truncated and will want to resume from that + // in the future. + // + // Additionally note that this caching step is skipped if the name + // to store doesn't actually fit in the dirent cache's path storage. + // In that case there's not much we can do and let the next call to + // `fd_readdir` start from scratch. + if buf.len() == 0 && name.len() <= DIRENT_CACHE { + let DirectoryEntryIterator { stream, cookie, .. } = iter; + state.dirent_cache.stream.set(Some(stream)); + state.dirent_cache.for_fd.set(fd); + state.dirent_cache.cookie.set(cookie - 1); + state.dirent_cache.cached_dirent.set(dirent); + ptr::copy( + name.as_ptr().cast::(), + (*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8, + name.len(), + ); + break; + } } - } - *bufused = buf_len - buf.len(); - Ok(()) + *bufused = buf_len - buf.len(); + Ok(()) + }) }); struct DirectoryEntryIterator<'a> { @@ -1064,7 +1087,7 @@ pub unsafe extern "C" fn fd_readdir( use_cache: bool, cookie: Dircookie, stream: DirectoryEntryStream, - dir_descriptor: filesystem::Descriptor, + dir_descriptor: &'a filesystem::Descriptor, } impl<'a> Iterator for DirectoryEntryIterator<'a> { @@ -1081,7 +1104,7 @@ pub unsafe extern "C" fn fd_readdir( // Preview2 excludes them, so re-add them. match current_cookie { 0 => { - let metadata_hash = match filesystem::metadata_hash(self.dir_descriptor) { + let metadata_hash = match self.dir_descriptor.metadata_hash() { Ok(h) => h, Err(e) => return Some(Err(e.into())), }; @@ -1119,7 +1142,7 @@ pub unsafe extern "C" fn fd_readdir( let entry = self.state.import_alloc.with_buffer( self.state.path_buf.get().cast(), PATH_MAX, - || filesystem::read_directory_entry(self.stream.0), + || self.stream.0.read_directory_entry(), ); let entry = match entry { Ok(Some(entry)) => entry, @@ -1128,13 +1151,11 @@ pub unsafe extern "C" fn fd_readdir( }; let filesystem::DirectoryEntry { type_, name } = entry; - let d_ino = filesystem::metadata_hash_at( - self.dir_descriptor, - filesystem::PathFlags::empty(), - &name, - ) - .map(|h| h.lower) - .unwrap_or(0); + let d_ino = self + .dir_descriptor + .metadata_hash_at(filesystem::PathFlags::empty(), &name) + .map(|h| h.lower) + .unwrap_or(0); let name = ManuallyDrop::new(name); let dirent = wasi::Dirent { d_next: self.cookie, @@ -1163,7 +1184,7 @@ pub unsafe extern "C" fn fd_readdir( /// would disappear if `dup2()` were to be removed entirely. #[no_mangle] pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { - State::with(|state| state.descriptors_mut().renumber(fd, to)) + State::with(|state| state.with_descriptors_mut(|ds| ds.renumber(fd, to))) } /// Move the offset of a file descriptor. @@ -1176,36 +1197,37 @@ pub unsafe extern "C" fn fd_seek( newoffset: *mut Filesize, ) -> Errno { State::with(|state| { - let ds = state.descriptors(); - let stream = ds.get_seekable_stream(fd)?; - - // Seeking only works on files. - if let StreamType::File(file) = &stream.type_ { - if let filesystem::DescriptorType::Directory = file.descriptor_type { - // This isn't really the "right" errno, but it is consistient with wasmtime's - // preview 1 tests. - return Err(ERRNO_BADF); - } - let from = match whence { - WHENCE_SET if offset >= 0 => offset, - WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) { - Some(pos) if pos >= 0 => pos, - _ => return Err(ERRNO_INVAL), - }, - WHENCE_END => match (filesystem::stat(file.fd)?.size as i64).checked_add(offset) { - Some(pos) if pos >= 0 => pos, + state.with_descriptors(|ds| { + let stream = ds.get_seekable_stream(fd)?; + + // Seeking only works on files. + if let StreamType::File(file) = &stream.type_ { + if let filesystem::DescriptorType::Directory = file.descriptor_type { + // This isn't really the "right" errno, but it is consistient with wasmtime's + // preview 1 tests. + return Err(ERRNO_BADF); + } + let from = match whence { + WHENCE_SET if offset >= 0 => offset, + WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, + WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, _ => return Err(ERRNO_INVAL), - }, - _ => return Err(ERRNO_INVAL), - }; - stream.input.set(None); - stream.output.set(None); - file.position.set(from as filesystem::Filesize); - *newoffset = from as filesystem::Filesize; - Ok(()) - } else { - Err(ERRNO_SPIPE) - } + }; + *stream.input.get() = None; + *stream.output.get() = None; + file.position.set(from as filesystem::Filesize); + *newoffset = from as filesystem::Filesize; + Ok(()) + } else { + Err(ERRNO_SPIPE) + } + }) }) } @@ -1214,10 +1236,11 @@ pub unsafe extern "C" fn fd_seek( #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - filesystem::sync(file.fd)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_file(fd)?; + file.fd.sync()?; + Ok(()) + }) }) } @@ -1226,10 +1249,11 @@ pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - *offset = file.position.get() as Filesize; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_seekable_file(fd)?; + *offset = file.position.get() as Filesize; + Ok(()) + }) }) } @@ -1261,34 +1285,35 @@ pub unsafe extern "C" fn fd_write( let bytes = slice::from_raw_parts(ptr, len); State::with(|state| { - let ds = state.descriptors(); - match ds.get(fd)? { - Descriptor::Streams(streams) => { - let wasi_stream = streams.get_write_stream()?; - - let nbytes = if let StreamType::File(file) = &streams.type_ { - file.blocking_mode.write(wasi_stream, bytes)? - } else { - // Use blocking writes on non-file streams (stdout, stderr, as sockets - // aren't currently used). - BlockingMode::Blocking.write(wasi_stream, bytes)? - }; - - // If this is a file, keep the current-position pointer up to date. - if let StreamType::File(file) = &streams.type_ { - // But don't update if we're in append mode. Strictly speaking, - // we should set the position to the new end of the file, but - // we don't have an API to do that atomically. - if !file.append { - file.position.set(file.position.get() + nbytes as u64); + state.with_descriptors(|ds| { + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; + + let nbytes = if let StreamType::File(file) = &streams.type_ { + file.blocking_mode.write(wasi_stream, bytes)? + } else { + // Use blocking writes on non-file streams (stdout, stderr, as sockets + // aren't currently used). + BlockingMode::Blocking.write(wasi_stream, bytes)? + }; + + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + // But don't update if we're in append mode. Strictly speaking, + // we should set the position to the new end of the file, but + // we don't have an API to do that atomically. + if !file.append { + file.position.set(file.position.get() + nbytes as u64); + } } - } - *nwritten = nbytes; - Ok(()) + *nwritten = nbytes; + Ok(()) + } + Descriptor::Closed(_) => Err(ERRNO_BADF), } - Descriptor::Closed(_) => Err(ERRNO_BADF), - } + }) }) } else { *nwritten = 0; @@ -1307,10 +1332,11 @@ pub unsafe extern "C" fn path_create_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - filesystem::create_directory_at(file.fd, path)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + file.fd.create_directory_at(path)?; + Ok(()) + }) }) } @@ -1328,22 +1354,23 @@ pub unsafe extern "C" fn path_filestat_get( let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - let stat = filesystem::stat_at(file.fd, at_flags, path)?; - let metadata_hash = filesystem::metadata_hash_at(file.fd, at_flags, path)?; - let filetype = stat.type_.into(); - *buf = Filestat { - dev: 1, - ino: metadata_hash.lower, - filetype, - nlink: stat.link_count, - size: stat.size, - atim: datetime_to_timestamp(stat.data_access_timestamp), - mtim: datetime_to_timestamp(stat.data_modification_timestamp), - ctim: datetime_to_timestamp(stat.status_change_timestamp), - }; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + let stat = file.fd.stat_at(at_flags, path)?; + let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?; + let filetype = stat.type_.into(); + *buf = Filestat { + dev: 1, + ino: metadata_hash.lower, + filetype, + nlink: stat.link_count, + size: stat.size, + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), + }; + Ok(()) + }) }) } @@ -1374,10 +1401,11 @@ pub unsafe extern "C" fn path_filestat_set_times( fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, )?; - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + file.fd.set_times_at(at_flags, path, atim, mtim)?; + Ok(()) + }) }) } @@ -1398,10 +1426,12 @@ pub unsafe extern "C" fn path_link( let at_flags = at_flags_from_lookupflags(old_flags); State::with(|state| { - let old = state.descriptors().get_dir(old_fd)?.fd; - let new = state.descriptors().get_dir(new_fd)?.fd; - filesystem::link_at(old, at_flags, old_path, new, new_path)?; - Ok(()) + state.with_descriptors(|ds| { + let old = &ds.get_dir(old_fd)?.fd; + let new = &ds.get_dir(new_fd)?.fd; + old.link_at(at_flags, old_path, new, new_path)?; + Ok(()) + }) }) } @@ -1434,29 +1464,30 @@ pub unsafe extern "C" fn path_open( let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; State::with(|state| { - let mut ds = state.descriptors_mut(); - let file = ds.get_dir(fd)?; - let result = filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; - let descriptor_type = filesystem::get_type(result)?; - let desc = Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: result, - descriptor_type, - position: Cell::new(0), - append, - blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 { - BlockingMode::Blocking - } else { - BlockingMode::NonBlocking - }, - }), - }); + state.with_descriptors_mut(|ds: &mut Descriptors| { + let file = ds.get_dir(fd)?; + let result = file.fd.open_at(at_flags, path, o_flags, flags, mode)?; + let descriptor_type = result.get_type()?; + let desc = Descriptor::Streams(Streams { + input: UnsafeCell::new(None), + output: UnsafeCell::new(None), + type_: StreamType::File(File { + fd: result, + descriptor_type, + position: Cell::new(0), + append, + blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 { + BlockingMode::Blocking + } else { + BlockingMode::NonBlocking + }, + }), + }); - let fd = ds.open(desc)?; - *opened_fd = fd; - Ok(()) + let fd = ds.open(desc)?; + *opened_fd = fd; + Ok(()) + }) }) } @@ -1479,35 +1510,36 @@ pub unsafe extern "C" fn path_readlink( // so instead we handle this case specially. let use_state_buf = buf_len < PATH_MAX; - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - let path = if use_state_buf { - state - .import_alloc - .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { - filesystem::readlink_at(file.fd, path) - })? - } else { - state - .import_alloc - .with_buffer(buf, buf_len, || filesystem::readlink_at(file.fd, path))? - }; + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + let path = if use_state_buf { + state + .import_alloc + .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { + file.fd.readlink_at(path) + })? + } else { + state + .import_alloc + .with_buffer(buf, buf_len, || file.fd.readlink_at(path))? + }; - if use_state_buf { - // Preview1 follows POSIX in truncating the returned path if it - // doesn't fit. - let len = min(path.len(), buf_len); - ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); - *bufused = len; - } else { - *bufused = path.len(); - } + if use_state_buf { + // Preview1 follows POSIX in truncating the returned path if it + // doesn't fit. + let len = min(path.len(), buf_len); + ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); + *bufused = len; + } else { + *bufused = path.len(); + } - // The returned string's memory was allocated in `buf`, so don't separately - // free it. - forget(path); + // The returned string's memory was allocated in `buf`, so don't separately + // free it. + forget(path); - Ok(()) + Ok(()) + }) }) } @@ -1523,10 +1555,11 @@ pub unsafe extern "C" fn path_remove_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - filesystem::remove_directory_at(file.fd, path)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + file.fd.remove_directory_at(path)?; + Ok(()) + }) }) } @@ -1545,11 +1578,12 @@ pub unsafe extern "C" fn path_rename( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let ds = state.descriptors(); - let old = ds.get_dir(old_fd)?.fd; - let new = ds.get_dir(new_fd)?.fd; - filesystem::rename_at(old, old_path, new, new_path)?; - Ok(()) + state.with_descriptors(|ds| { + let old = &ds.get_dir(old_fd)?.fd; + let new = &ds.get_dir(new_fd)?.fd; + old.rename_at(old_path, new, new_path)?; + Ok(()) + }) }) } @@ -1567,10 +1601,11 @@ pub unsafe extern "C" fn path_symlink( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - filesystem::symlink_at(file.fd, old_path, new_path)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + file.fd.symlink_at(old_path, new_path)?; + Ok(()) + }) }) } @@ -1582,10 +1617,11 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - filesystem::unlink_file_at(file.fd, path)?; - Ok(()) + state.with_descriptors(|ds| { + let file = ds.get_dir(fd)?; + file.fd.unlink_file_at(path)?; + Ok(()) + }) }) } @@ -1598,15 +1634,22 @@ struct Pollables { impl Pollables { unsafe fn push(&mut self, pollable: Pollable) { assert!(self.index < self.length); - *self.pointer.add(self.index) = pollable; + // Use `ptr::write` instead of `*... = pollable` because `ptr::write` + // doesn't call drop on the old memory. + self.pointer.add(self.index).write(pollable); self.index += 1; } } +// We create new pollable handles for each `poll_oneoff` call, so drop them all +// after the call. impl Drop for Pollables { fn drop(&mut self) { - for i in 0..self.index { - poll::drop_pollable(unsafe { *self.pointer.add(i) }) + while self.index != 0 { + self.index -= 1; + unsafe { + core::ptr::drop_in_place(self.pointer.add(self.index)); + } } } } @@ -1649,7 +1692,7 @@ pub unsafe extern "C" fn poll_oneoff( // // First, we assert that this is possible: assert!(align_of::() >= align_of::()); - assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); assert!( nsubscriptions .checked_mul(size_of::()) @@ -1659,7 +1702,7 @@ pub unsafe extern "C" fn poll_oneoff( .trapping_unwrap() .checked_add( nsubscriptions - .checked_mul(size_of::()) + .checked_mul(size_of::()) .trapping_unwrap() ) .trapping_unwrap() @@ -1667,7 +1710,7 @@ pub unsafe extern "C" fn poll_oneoff( // Store the pollable handles at the beginning, and the bool results at the // end, so that we don't clobber the bool results when writting the events. let pollables = out as *mut c_void as *mut Pollable; - let results = out.add(nsubscriptions).cast::().sub(nsubscriptions); + let results = out.add(nsubscriptions).cast::().sub(nsubscriptions); // Indefinite sleeping is not supported in preview1. if nsubscriptions == 0 { @@ -1724,175 +1767,157 @@ pub unsafe extern "C" fn poll_oneoff( monotonic_clock::subscribe(timeout, false) } - CLOCKID_MONOTONIC => monotonic_clock::subscribe(clock.timeout, absolute), + CLOCKID_MONOTONIC => { + let s = monotonic_clock::subscribe(clock.timeout, absolute); + s + } _ => return Err(ERRNO_INVAL), } } - EVENTTYPE_FD_READ => { - let stream = state - .descriptors() - .get_read_stream(subscription.u.u.fd_read.file_descriptor)?; - streams::subscribe_to_input_stream(stream) - } + EVENTTYPE_FD_READ => state.with_descriptors(|ds| { + ds.get_read_stream(subscription.u.u.fd_read.file_descriptor) + .map(|stream| stream.subscribe()) + })?, - EVENTTYPE_FD_WRITE => { - let stream = state - .descriptors() - .get_write_stream(subscription.u.u.fd_write.file_descriptor)?; - streams::subscribe_to_output_stream(stream) - } + EVENTTYPE_FD_WRITE => state.with_descriptors(|ds| { + ds.get_write_stream(subscription.u.u.fd_write.file_descriptor) + .map(|stream| stream.subscribe()) + })?, _ => return Err(ERRNO_INVAL), }); } - #[link(wasm_import_module = "wasi:poll/poll")] + #[link(wasm_import_module = "wasi:io/poll")] extern "C" { - #[link_name = "poll-oneoff"] - fn poll_oneoff_import(pollables: *const Pollable, len: usize, rval: *mut BoolList); + #[link_name = "poll-list"] + fn poll_list_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList); } - let mut ready_list = BoolList { + let mut ready_list = ReadyList { base: std::ptr::null(), len: 0, }; state.import_alloc.with_buffer( - results, + results.cast(), nsubscriptions - .checked_mul(size_of::()) + .checked_mul(size_of::()) .trapping_unwrap(), || { - poll_oneoff_import( + poll_list_import( pollables.pointer, pollables.length, &mut ready_list as *mut _, - ) + ); }, ); - assert_eq!(ready_list.len, nsubscriptions); - assert_eq!(ready_list.base, results as *const bool); + assert!(ready_list.len <= nsubscriptions); + assert_eq!(ready_list.base, results as *const u32); drop(pollables); - let ready = subscriptions - .iter() - .enumerate() - .filter_map(|(i, s)| (*ready_list.base.add(i)).then_some(s)); + let ready = std::slice::from_raw_parts(ready_list.base, ready_list.len); let mut count = 0; for subscription in ready { - let error; + let subscription = *subscriptions.as_ptr().add(*subscription as usize); + let type_; - let nbytes; - let flags; - match subscription.u.tag { + let (error, nbytes, flags) = match subscription.u.tag { EVENTTYPE_CLOCK => { - error = ERRNO_SUCCESS; type_ = wasi::EVENTTYPE_CLOCK; - nbytes = 0; - flags = 0; + (ERRNO_SUCCESS, 0, 0) } EVENTTYPE_FD_READ => { type_ = wasi::EVENTTYPE_FD_READ; - let ds = state.descriptors(); - let desc = ds - .get(subscription.u.u.fd_read.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match &streams.type_ { - StreamType::File(file) => match filesystem::stat(file.fd) { - Ok(stat) => { - error = ERRNO_SUCCESS; - nbytes = stat.size.saturating_sub(file.position.get()); - flags = if nbytes == 0 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 1; - flags = 0; + state.with_descriptors(|ds| { + let desc = ds + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::File(file) => match file.fd.stat() { + Ok(stat) => { + let nbytes = stat.size.saturating_sub(file.position.get()); + ( + ERRNO_SUCCESS, + nbytes, + if nbytes == 0 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }, + ) + } + Err(e) => (e.into(), 1, 0), + }, + StreamType::Socket(connection) => { + unreachable!() // TODO + /* + match tcp::bytes_readable(*connection) { + Ok(result) => ( + ERRNO_SUCCESS, + result.0, + if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + } + ) + Err(e) => { + (e.into(), 1, 0) + } + } + */ } + StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), }, - StreamType::Socket(connection) => { - unreachable!() // TODO - /* - match tcp::bytes_readable(*connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - } - */ - } - StreamType::Stdio(_) => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; - } - }, - _ => unreachable!(), - } + _ => unreachable!(), + } + }) } EVENTTYPE_FD_WRITE => { type_ = wasi::EVENTTYPE_FD_WRITE; - let ds = state.descriptors(); - let desc = ds - .get(subscription.u.u.fd_write.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match streams.type_ { - StreamType::File(_) | StreamType::Stdio(_) => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; - } - StreamType::Socket(connection) => { - unreachable!() // TODO - /* - match tcp::bytes_writable(connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; + state.with_descriptors(|ds| { + let desc = ds + .get(subscription.u.u.fd_write.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::File(_) | StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), + StreamType::Socket(connection) => { + unreachable!() // TODO + /* + match tcp::bytes_writable(connection) { + Ok(result) => ( + ERRNO_SUCCESS, + result.0, + if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + } + ) + Err(e) => { + (e.into(), 0, 0) + } } - } - */ - } - }, - _ => unreachable!(), - } + */ + } + }, + _ => unreachable!(), + } + }) } _ => unreachable!(), - } + }; *out.add(count) = Event { userdata: subscription.userdata, @@ -2009,8 +2034,12 @@ pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable!() } -fn datetime_to_timestamp(datetime: filesystem::Datetime) -> Timestamp { - u64::from(datetime.nanoseconds).saturating_add(datetime.seconds.saturating_mul(1_000_000_000)) +fn datetime_to_timestamp(datetime: Option) -> Timestamp { + match datetime { + Some(datetime) => u64::from(datetime.nanoseconds) + .saturating_add(datetime.seconds.saturating_mul(1_000_000_000)), + None => 0, + } } fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags { @@ -2135,15 +2164,19 @@ impl BlockingMode { // breaking our fragile linking scheme fn read( self, - input_stream: streams::InputStream, + input_stream: &streams::InputStream, read_len: u64, ) -> Result<(Vec, streams::StreamStatus), ()> { match self { - BlockingMode::NonBlocking => streams::read(input_stream, read_len), - BlockingMode::Blocking => streams::blocking_read(input_stream, read_len), + BlockingMode::NonBlocking => input_stream.read(read_len), + BlockingMode::Blocking => input_stream.blocking_read(read_len), } } - fn write(self, output_stream: streams::OutputStream, mut bytes: &[u8]) -> Result { + fn write( + self, + output_stream: &streams::OutputStream, + mut bytes: &[u8], + ) -> Result { match self { BlockingMode::Blocking => { let total = bytes.len(); @@ -2151,7 +2184,7 @@ impl BlockingMode { let len = bytes.len().min(4096); let (chunk, rest) = bytes.split_at(len); bytes = rest; - match streams::blocking_write_and_flush(output_stream, chunk) { + match output_stream.blocking_write_and_flush(chunk) { Ok(()) => {} Err(_) => return Err(ERRNO_IO), } @@ -2160,7 +2193,7 @@ impl BlockingMode { } BlockingMode::NonBlocking => { - let permit = match streams::check_write(output_stream) { + let permit = match output_stream.check_write() { Ok(n) => n, Err(streams::WriteError::Closed) => 0, Err(streams::WriteError::LastOperationFailed) => return Err(ERRNO_IO), @@ -2171,13 +2204,13 @@ impl BlockingMode { return Ok(0); } - match streams::write(output_stream, &bytes[..len]) { + match output_stream.write(&bytes[..len]) { Ok(_) => {} Err(streams::WriteError::Closed) => return Ok(0), Err(streams::WriteError::LastOperationFailed) => return Err(ERRNO_IO), } - match streams::blocking_flush(output_stream) { + match output_stream.blocking_flush() { Ok(_) => {} Err(streams::WriteError::Closed) => return Ok(0), Err(streams::WriteError::LastOperationFailed) => return Err(ERRNO_IO), @@ -2245,7 +2278,13 @@ struct State { /// /// Do not use this member directly - use State::descriptors() to ensure /// lazy initialization happens. - descriptors: RefCell>, + descriptors: UnsafeCell>, + + /// Borrow state of `descriptors`. + /// + /// If it looks like we're kind re-implementing `RefCell`, it's because we + /// basically are; `RefCell` itself pulls in static initializers. + descriptors_borrowed: UnsafeCell, /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, @@ -2288,12 +2327,6 @@ struct DirentCache { struct DirectoryEntryStream(filesystem::DirectoryEntryStream); -impl Drop for DirectoryEntryStream { - fn drop(&mut self) { - filesystem::drop_directory_entry_stream(self.0); - } -} - #[repr(C)] pub struct WasmStr { ptr: *const u8, @@ -2321,8 +2354,8 @@ pub struct StrTupleList { #[derive(Copy, Clone)] #[repr(C)] -pub struct BoolList { - base: *const bool, +pub struct ReadyList { + base: *const u32, len: usize, } @@ -2337,7 +2370,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 16 * size_of::(); + start -= 12 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2348,7 +2381,7 @@ const fn bump_arena_size() -> usize { // below. #[cfg(target_arch = "wasm32")] const _: () = { - let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; + let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; }; #[allow(unused)] @@ -2363,28 +2396,25 @@ enum AllocationState { #[allow(improper_ctypes)] extern "C" { - fn get_state_ptr() -> *const RefCell; - fn set_state_ptr(state: *const RefCell); + fn get_state_ptr() -> *const State; + fn set_state_ptr(state: *const State); fn get_allocation_state() -> AllocationState; fn set_allocation_state(state: AllocationState); - fn get_stderr_stream() -> Fd; - fn set_stderr_stream(fd: Fd); } impl State { fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno { - let ptr = State::ptr(); - let ptr = ptr.try_borrow().unwrap_or_else(|_| unreachable!()); - assert_eq!(ptr.magic1, MAGIC); - assert_eq!(ptr.magic2, MAGIC); - let ret = f(&*ptr); + let state_ref = State::ptr(); + assert_eq!(state_ref.magic1, MAGIC); + assert_eq!(state_ref.magic2, MAGIC); + let ret = f(state_ref); match ret { Ok(()) => ERRNO_SUCCESS, Err(err) => err, } } - fn ptr() -> &'static RefCell { + fn ptr() -> &'static State { unsafe { let mut ptr = get_state_ptr(); if ptr.is_null() { @@ -2396,7 +2426,7 @@ impl State { } #[cold] - fn new() -> &'static RefCell { + fn new() -> &'static State { #[link(wasm_import_module = "__main_module__")] extern "C" { fn cabi_realloc( @@ -2418,19 +2448,20 @@ impl State { cabi_realloc( ptr::null_mut(), 0, - mem::align_of::>(), - mem::size_of::>(), - ) as *mut RefCell + mem::align_of::>(), + mem::size_of::>(), + ) as *mut State }; unsafe { set_allocation_state(AllocationState::StateAllocated) }; unsafe { - ret.write(RefCell::new(State { + ret.write(State { magic1: MAGIC, magic2: MAGIC, import_alloc: ImportAlloc::new(), - descriptors: RefCell::new(None), + descriptors: UnsafeCell::new(None), + descriptors_borrowed: UnsafeCell::new(false), path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), args: Cell::new(None), @@ -2448,33 +2479,62 @@ impl State { path_data: UnsafeCell::new(MaybeUninit::uninit()), }, dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], - })); + }); &*ret } } /// Accessor for the descriptors member that ensures it is properly initialized - fn descriptors<'a>(&'a self) -> impl Deref + 'a { - let mut d = self - .descriptors - .try_borrow_mut() - .unwrap_or_else(|_| unreachable!()); - if d.is_none() { - *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + fn with_descriptors T>(&self, fn_: F) -> T { + unsafe { + if core::mem::replace(&mut *self.descriptors_borrowed.get(), true) { + unreachable!(); // Don't borrow descriptors while they're already borrowed. + } } - RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) + + let descriptors: &mut Option = unsafe { &mut *self.descriptors.get() }; + match descriptors { + None => { + *descriptors = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + } + Some(_descriptors) => {} + } + let result = match descriptors { + Some(descriptors) => fn_(descriptors), + None => unreachable!(), + }; + + unsafe { + *self.descriptors_borrowed.get() = false; + } + + result } - /// Mut accessor for the descriptors member that ensures it is properly initialized - fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref + 'a { - let mut d = self - .descriptors - .try_borrow_mut() - .unwrap_or_else(|_| unreachable!()); - if d.is_none() { - *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + fn with_descriptors_mut T>(&self, fn_: F) -> T { + unsafe { + if core::mem::replace(&mut *self.descriptors_borrowed.get(), true) { + unreachable!(); // Don't borrow descriptors while they're already borrowed. + } } - RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) + + let descriptors: &mut Option = unsafe { &mut *self.descriptors.get() }; + match descriptors { + None => { + *descriptors = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + } + Some(_descriptors) => {} + } + let result = match descriptors { + Some(descriptors) => fn_(descriptors), + None => unreachable!(), + }; + + unsafe { + *self.descriptors_borrowed.get() = false; + } + + result } fn get_environment(&self) -> &[StrTuple] { diff --git a/crates/wasi-preview1-component-adapter/src/macros.rs b/crates/wasi-preview1-component-adapter/src/macros.rs index ca46aacacf1c..c4eef95763c9 100644 --- a/crates/wasi-preview1-component-adapter/src/macros.rs +++ b/crates/wasi-preview1-component-adapter/src/macros.rs @@ -3,12 +3,13 @@ //! We're avoiding static initializers, so we can't have things like string //! literals. Replace the standard assert macros with simpler implementations. +use crate::bindings::wasi::cli::stderr::get_stderr; use crate::bindings::wasi::io::streams; #[allow(dead_code)] #[doc(hidden)] pub fn print(message: &[u8]) { - let _ = unsafe { streams::write(crate::get_stderr_stream(), message) }; + let _ = unsafe { get_stderr().blocking_write_and_flush(message) }; } /// A minimal `eprint` for debugging. diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 5bc3b63cf8b0..2ef0c8d67c30 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -33,7 +33,6 @@ cap-net-ext = { workspace = true, optional = true } cap-time-ext = { workspace = true, optional = true } io-lifetimes = { workspace = true, optional = true } fs-set-times = { workspace = true, optional = true } -is-terminal = { workspace = true, optional = true } bitflags = { workspace = true, optional = true } async-trait = { workspace = true, optional = true } system-interface = { workspace = true, optional = true} @@ -72,7 +71,6 @@ preview2 = [ 'dep:cap-time-ext', 'dep:io-lifetimes', 'dep:fs-set-times', - 'dep:is-terminal', 'dep:bitflags', 'dep:async-trait', 'dep:system-interface', diff --git a/crates/wasi/src/preview2/command.rs b/crates/wasi/src/preview2/command.rs index 1ba80b93923c..f83dce6b20cf 100644 --- a/crates/wasi/src/preview2/command.rs +++ b/crates/wasi/src/preview2/command.rs @@ -13,7 +13,7 @@ wasmtime::component::bindgen!({ "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, "wasi:sockets/tcp": crate::preview2::bindings::sockets::tcp, "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock, - "wasi:poll/poll": crate::preview2::bindings::poll::poll, + "wasi:io/poll": crate::preview2::bindings::io::poll, "wasi:io/streams": crate::preview2::bindings::io::streams, "wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone, "wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock, @@ -37,7 +37,7 @@ pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> any crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?; crate::preview2::bindings::filesystem::types::add_to_linker(l, |t| t)?; crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; - crate::preview2::bindings::poll::poll::add_to_linker(l, |t| t)?; + crate::preview2::bindings::io::poll::add_to_linker(l, |t| t)?; crate::preview2::bindings::io::streams::add_to_linker(l, |t| t)?; crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli::exit::add_to_linker(l, |t| t)?; @@ -73,7 +73,7 @@ pub mod sync { "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, "wasi:sockets/tcp": crate::preview2::bindings::sockets::tcp, "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock, - "wasi:poll/poll": crate::preview2::bindings::sync_io::poll::poll, + "wasi:io/poll": crate::preview2::bindings::sync_io::io::poll, "wasi:io/streams": crate::preview2::bindings::sync_io::io::streams, "wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone, "wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock, @@ -99,7 +99,7 @@ pub mod sync { crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?; crate::preview2::bindings::sync_io::filesystem::types::add_to_linker(l, |t| t)?; crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; - crate::preview2::bindings::sync_io::poll::poll::add_to_linker(l, |t| t)?; + crate::preview2::bindings::sync_io::io::poll::add_to_linker(l, |t| t)?; crate::preview2::bindings::sync_io::io::streams::add_to_linker(l, |t| t)?; crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?; crate::preview2::bindings::cli::exit::add_to_linker(l, |t| t)?; diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs index 785ce43be2c1..95c0f2eae41d 100644 --- a/crates/wasi/src/preview2/ctx.rs +++ b/crates/wasi/src/preview2/ctx.rs @@ -1,11 +1,10 @@ use super::clocks::host::{monotonic_clock, wall_clock}; use crate::preview2::{ clocks::{self, HostMonotonicClock, HostWallClock}, - filesystem::{Dir, TableFsExt}, + filesystem::Dir, pipe, random, stdio, - stdio::{StdioInput, StdioOutput}, - stream::{HostInputStream, HostOutputStream, TableStreamExt}, - DirPerms, FilePerms, IsATTY, Table, + stdio::{StdinStream, StdoutStream}, + DirPerms, FilePerms, Table, }; use cap_rand::{Rng, RngCore, SeedableRng}; use cap_std::ipnet::{self, IpNet}; @@ -15,9 +14,9 @@ use std::mem; use std::net::{Ipv4Addr, Ipv6Addr}; pub struct WasiCtxBuilder { - stdin: (Box, IsATTY), - stdout: (Box, IsATTY), - stderr: (Box, IsATTY), + stdin: Box, + stdout: Box, + stderr: Box, env: Vec<(String, String)>, args: Vec, preopens: Vec<(Dir, String)>, @@ -65,9 +64,9 @@ impl WasiCtxBuilder { let insecure_random_seed = cap_rand::thread_rng(cap_rand::ambient_authority()).gen::(); Self { - stdin: (Box::new(pipe::ClosedInputStream), IsATTY::No), - stdout: (Box::new(pipe::SinkOutputStream), IsATTY::No), - stderr: (Box::new(pipe::SinkOutputStream), IsATTY::No), + stdin: Box::new(pipe::ClosedInputStream), + stdout: Box::new(pipe::SinkOutputStream), + stderr: Box::new(pipe::SinkOutputStream), env: Vec::new(), args: Vec::new(), preopens: Vec::new(), @@ -81,52 +80,31 @@ impl WasiCtxBuilder { } } - pub fn stdin(&mut self, stdin: impl HostInputStream + 'static, isatty: IsATTY) -> &mut Self { - self.stdin = (Box::new(stdin), isatty); + pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self { + self.stdin = Box::new(stdin); self } - pub fn stdout(&mut self, stdout: impl HostOutputStream + 'static, isatty: IsATTY) -> &mut Self { - self.stdout = (Box::new(stdout), isatty); + pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self { + self.stdout = Box::new(stdout); self } - pub fn stderr(&mut self, stderr: impl HostOutputStream + 'static, isatty: IsATTY) -> &mut Self { - self.stderr = (Box::new(stderr), isatty); + pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self { + self.stderr = Box::new(stderr); self } pub fn inherit_stdin(&mut self) -> &mut Self { - use is_terminal::IsTerminal; - let inherited = stdio::stdin(); - let isatty = if inherited.is_terminal() { - IsATTY::Yes - } else { - IsATTY::No - }; - self.stdin(inherited, isatty) + self.stdin(stdio::stdin()) } pub fn inherit_stdout(&mut self) -> &mut Self { - use is_terminal::IsTerminal; - let inherited = stdio::stdout(); - let isatty = if inherited.is_terminal() { - IsATTY::Yes - } else { - IsATTY::No - }; - self.stdout(inherited, isatty) + self.stdout(stdio::stdout()) } pub fn inherit_stderr(&mut self) -> &mut Self { - use is_terminal::IsTerminal; - let inherited = stdio::stderr(); - let isatty = if inherited.is_terminal() { - IsATTY::Yes - } else { - IsATTY::No - }; - self.stderr(inherited, isatty) + self.stderr(stdio::stderr()) } pub fn inherit_stdio(&mut self) -> &mut Self { @@ -270,10 +248,9 @@ impl WasiCtxBuilder { /// # Panics /// /// Panics if this method is called twice. - pub fn build(&mut self, table: &mut Table) -> Result { + pub fn build(&mut self) -> WasiCtx { assert!(!self.built); - use anyhow::Context; let Self { stdin, stdout, @@ -291,33 +268,10 @@ impl WasiCtxBuilder { } = mem::replace(self, Self::new()); self.built = true; - let stdin_ix = table.push_input_stream(stdin.0).context("stdin")?; - let stdout_ix = table.push_output_stream(stdout.0).context("stdout")?; - let stderr_ix = table.push_output_stream(stderr.0).context("stderr")?; - - let preopens = preopens - .into_iter() - .map(|(dir, path)| { - let dirfd = table - .push_dir(dir) - .with_context(|| format!("preopen {path:?}"))?; - Ok((dirfd, path)) - }) - .collect::>>()?; - - Ok(WasiCtx { - stdin: StdioInput { - input_stream: stdin_ix, - isatty: stdin.1, - }, - stdout: StdioOutput { - output_stream: stdout_ix, - isatty: stdout.1, - }, - stderr: StdioOutput { - output_stream: stderr_ix, - isatty: stderr.1, - }, + WasiCtx { + stdin, + stdout, + stderr, env, args, preopens, @@ -327,7 +281,7 @@ impl WasiCtxBuilder { insecure_random_seed, wall_clock, monotonic_clock, - }) + } } } @@ -346,9 +300,9 @@ pub struct WasiCtx { pub(crate) monotonic_clock: Box, pub(crate) env: Vec<(String, String)>, pub(crate) args: Vec, - pub(crate) preopens: Vec<(u32, String)>, - pub(crate) stdin: StdioInput, - pub(crate) stdout: StdioOutput, - pub(crate) stderr: StdioOutput, + pub(crate) preopens: Vec<(Dir, String)>, + pub(crate) stdin: Box, + pub(crate) stdout: Box, + pub(crate) stderr: Box, pub(crate) pool: Pool, } diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs index f99b6ab2d9af..3f3cfb1b4329 100644 --- a/crates/wasi/src/preview2/filesystem.rs +++ b/crates/wasi/src/preview2/filesystem.rs @@ -1,3 +1,4 @@ +use crate::preview2::bindings::filesystem::types::Descriptor; use crate::preview2::{ AbortOnDropJoinHandle, HostOutputStream, OutputStreamError, StreamRuntimeError, StreamState, Table, TableError, @@ -6,6 +7,7 @@ use anyhow::anyhow; use bytes::{Bytes, BytesMut}; use futures::future::{maybe_done, MaybeDone}; use std::sync::Arc; +use wasmtime::component::Resource; bitflags::bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -44,42 +46,42 @@ impl File { } } pub(crate) trait TableFsExt { - fn push_file(&mut self, file: File) -> Result; - fn delete_file(&mut self, fd: u32) -> Result; - fn is_file(&self, fd: u32) -> bool; - fn get_file(&self, fd: u32) -> Result<&File, TableError>; + fn push_file(&mut self, file: File) -> Result, TableError>; + fn delete_file(&mut self, fd: Resource) -> Result; + fn is_file(&self, fd: &Resource) -> bool; + fn get_file(&self, fd: &Resource) -> Result<&File, TableError>; - fn push_dir(&mut self, dir: Dir) -> Result; - fn delete_dir(&mut self, fd: u32) -> Result; - fn is_dir(&self, fd: u32) -> bool; - fn get_dir(&self, fd: u32) -> Result<&Dir, TableError>; + fn push_dir(&mut self, dir: Dir) -> Result, TableError>; + fn delete_dir(&mut self, fd: Resource) -> Result; + fn is_dir(&self, fd: &Resource) -> bool; + fn get_dir(&self, fd: &Resource) -> Result<&Dir, TableError>; } impl TableFsExt for Table { - fn push_file(&mut self, file: File) -> Result { - self.push(Box::new(file)) + fn push_file(&mut self, file: File) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(file))?)) } - fn delete_file(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_file(&mut self, fd: Resource) -> Result { + self.delete(fd.rep()) } - fn is_file(&self, fd: u32) -> bool { - self.is::(fd) + fn is_file(&self, fd: &Resource) -> bool { + self.is::(fd.rep()) } - fn get_file(&self, fd: u32) -> Result<&File, TableError> { - self.get(fd) + fn get_file(&self, fd: &Resource) -> Result<&File, TableError> { + self.get(fd.rep()) } - fn push_dir(&mut self, dir: Dir) -> Result { - self.push(Box::new(dir)) + fn push_dir(&mut self, dir: Dir) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(dir))?)) } - fn delete_dir(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_dir(&mut self, fd: Resource) -> Result { + self.delete(fd.rep()) } - fn is_dir(&self, fd: u32) -> bool { - self.is::(fd) + fn is_dir(&self, fd: &Resource) -> bool { + self.is::(fd.rep()) } - fn get_dir(&self, fd: u32) -> Result<&Dir, TableError> { - self.get(fd) + fn get_dir(&self, fd: &Resource) -> Result<&Dir, TableError> { + self.get(fd.rep()) } } @@ -91,6 +93,7 @@ bitflags::bitflags! { } } +#[derive(Clone)] pub(crate) struct Dir { pub dir: Arc, pub perms: DirPerms, diff --git a/crates/wasi/src/preview2/host/clocks.rs b/crates/wasi/src/preview2/host/clocks.rs index 2c461bd804c8..3c9240af5608 100644 --- a/crates/wasi/src/preview2/host/clocks.rs +++ b/crates/wasi/src/preview2/host/clocks.rs @@ -2,12 +2,13 @@ use crate::preview2::bindings::{ clocks::monotonic_clock::{self, Instant}, - clocks::timezone::{self, Timezone, TimezoneDisplay}, + clocks::timezone::{self, TimezoneDisplay}, clocks::wall_clock::{self, Datetime}, - poll::poll::Pollable, + io::poll::Pollable, }; use crate::preview2::{HostPollable, TablePollableExt, WasiView}; use cap_std::time::SystemTime; +use wasmtime::component::Resource; impl TryFrom for Datetime { type Error = anyhow::Error; @@ -50,7 +51,7 @@ impl monotonic_clock::Host for T { Ok(self.ctx().monotonic_clock.resolution()) } - fn subscribe(&mut self, when: Instant, absolute: bool) -> anyhow::Result { + fn subscribe(&mut self, when: Instant, absolute: bool) -> anyhow::Result> { use std::time::Duration; // Calculate time relative to clock object, which may not have the same zero // point as tokio Inst::now() @@ -93,15 +94,11 @@ impl monotonic_clock::Host for T { } impl timezone::Host for T { - fn display(&mut self, timezone: Timezone, when: Datetime) -> anyhow::Result { + fn display(&mut self, when: Datetime) -> anyhow::Result { todo!("timezone display is not implemented") } - fn utc_offset(&mut self, timezone: Timezone, when: Datetime) -> anyhow::Result { + fn utc_offset(&mut self, when: Datetime) -> anyhow::Result { todo!("timezone utc_offset is not implemented") } - - fn drop_timezone(&mut self, timezone: Timezone) -> anyhow::Result<()> { - todo!("timezone drop is not implemented") - } } diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs index c9f2105fdb44..374d4a455fa5 100644 --- a/crates/wasi/src/preview2/host/filesystem.rs +++ b/crates/wasi/src/preview2/host/filesystem.rs @@ -1,8 +1,13 @@ use crate::preview2::bindings::clocks::wall_clock; +use crate::preview2::bindings::filesystem::types::{ + DirectoryEntryStream, HostDescriptor, HostDirectoryEntryStream, +}; use crate::preview2::bindings::filesystem::{preopens, types}; use crate::preview2::bindings::io::streams; use crate::preview2::filesystem::{Dir, File, TableFsExt}; use crate::preview2::{DirPerms, FilePerms, Table, TableError, WasiView}; +use anyhow::Context; +use wasmtime::component::Resource; use types::ErrorCode; @@ -15,16 +20,29 @@ impl From for types::Error { } impl preopens::Host for T { - fn get_directories(&mut self) -> Result, anyhow::Error> { - Ok(self.ctx().preopens.clone()) + fn get_directories( + &mut self, + ) -> Result, String)>, anyhow::Error> { + let mut results = Vec::new(); + for (dir, name) in self.ctx().preopens.clone() { + let fd = self + .table_mut() + .push_dir(dir) + .with_context(|| format!("failed to push preopen {name}"))?; + results.push((fd, name)); + } + Ok(results) } } #[async_trait::async_trait] -impl types::Host for T { +impl types::Host for T {} + +#[async_trait::async_trait] +impl HostDescriptor for T { async fn advise( &mut self, - fd: types::Descriptor, + fd: Resource, offset: types::Filesize, len: types::Filesize, advice: types::Advice, @@ -41,16 +59,16 @@ impl types::Host for T { Advice::NoReuse => A::NoReuse, }; - let f = self.table().get_file(fd)?; + let f = self.table().get_file(&fd)?; f.spawn_blocking(move |f| f.advise(offset, len, advice)) .await?; Ok(()) } - async fn sync_data(&mut self, fd: types::Descriptor) -> Result<(), types::Error> { + async fn sync_data(&mut self, fd: Resource) -> Result<(), types::Error> { let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; match f.spawn_blocking(|f| f.sync_data()).await { Ok(()) => Ok(()), // On windows, `sync_data` uses `FileFlushBuffers` which fails with @@ -65,8 +83,8 @@ impl types::Host for T { } Err(e) => Err(e.into()), } - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; d.spawn_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_data()?)) .await } else { @@ -76,7 +94,7 @@ impl types::Host for T { async fn get_flags( &mut self, - fd: types::Descriptor, + fd: Resource, ) -> Result { use system_interface::fs::{FdFlags, GetSetFdFlags}; use types::DescriptorFlags; @@ -96,8 +114,8 @@ impl types::Host for T { } let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; let flags = f.spawn_blocking(|f| f.get_fd_flags()).await?; let mut flags = get_from_fdflags(flags); if f.perms.contains(FilePerms::READ) { @@ -107,8 +125,8 @@ impl types::Host for T { flags |= DescriptorFlags::WRITE; } Ok(flags) - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; let flags = d.spawn_blocking(|d| d.get_fd_flags()).await?; let mut flags = get_from_fdflags(flags); if d.perms.contains(DirPerms::READ) { @@ -125,15 +143,15 @@ impl types::Host for T { async fn get_type( &mut self, - fd: types::Descriptor, + fd: Resource, ) -> Result { let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; let meta = f.spawn_blocking(|f| f.metadata()).await?; Ok(descriptortype_from(meta.file_type())) - } else if table.is_dir(fd) { + } else if table.is_dir(&fd) { Ok(types::DescriptorType::Directory) } else { Err(ErrorCode::BadDescriptor.into()) @@ -142,10 +160,10 @@ impl types::Host for T { async fn set_size( &mut self, - fd: types::Descriptor, + fd: Resource, size: types::Filesize, ) -> Result<(), types::Error> { - let f = self.table().get_file(fd)?; + let f = self.table().get_file(&fd)?; if !f.perms.contains(FilePerms::WRITE) { Err(ErrorCode::NotPermitted)?; } @@ -155,15 +173,15 @@ impl types::Host for T { async fn set_times( &mut self, - fd: types::Descriptor, + fd: Resource, atim: types::NewTimestamp, mtim: types::NewTimestamp, ) -> Result<(), types::Error> { use fs_set_times::SetTimes; let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; if !f.perms.contains(FilePerms::WRITE) { return Err(ErrorCode::NotPermitted.into()); } @@ -171,8 +189,8 @@ impl types::Host for T { let mtim = systemtimespec_from(mtim)?; f.spawn_blocking(|f| f.set_times(atim, mtim)).await?; Ok(()) - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -187,7 +205,7 @@ impl types::Host for T { async fn read( &mut self, - fd: types::Descriptor, + fd: Resource, len: types::Filesize, offset: types::Filesize, ) -> Result<(Vec, bool), types::Error> { @@ -196,7 +214,7 @@ impl types::Host for T { let table = self.table(); - let f = table.get_file(fd)?; + let f = table.get_file(&fd)?; if !f.perms.contains(FilePerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -225,7 +243,7 @@ impl types::Host for T { async fn write( &mut self, - fd: types::Descriptor, + fd: Resource, buf: Vec, offset: types::Filesize, ) -> Result { @@ -233,7 +251,7 @@ impl types::Host for T { use system_interface::fs::FileIoExt; let table = self.table(); - let f = table.get_file(fd)?; + let f = table.get_file(&fd)?; if !f.perms.contains(FilePerms::WRITE) { return Err(ErrorCode::NotPermitted.into()); } @@ -247,10 +265,10 @@ impl types::Host for T { async fn read_directory( &mut self, - fd: types::Descriptor, - ) -> Result { + fd: Resource, + ) -> Result, types::Error> { let table = self.table_mut(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -310,27 +328,10 @@ impl types::Host for T { Ok(table.push_readdir(ReaddirIterator::new(entries))?) } - async fn read_directory_entry( - &mut self, - stream: types::DirectoryEntryStream, - ) -> Result, types::Error> { + async fn sync(&mut self, fd: Resource) -> Result<(), types::Error> { let table = self.table(); - let readdir = table.get_readdir(stream)?; - readdir.next() - } - - fn drop_directory_entry_stream( - &mut self, - stream: types::DirectoryEntryStream, - ) -> anyhow::Result<()> { - self.table_mut().delete_readdir(stream)?; - Ok(()) - } - - async fn sync(&mut self, fd: types::Descriptor) -> Result<(), types::Error> { - let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; match f.spawn_blocking(|f| f.sync_all()).await { Ok(()) => Ok(()), // On windows, `sync_data` uses `FileFlushBuffers` which fails with @@ -345,8 +346,8 @@ impl types::Host for T { } Err(e) => Err(e.into()), } - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; d.spawn_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_all()?)) .await } else { @@ -356,11 +357,11 @@ impl types::Host for T { async fn create_directory_at( &mut self, - fd: types::Descriptor, + fd: Resource, path: String, ) -> Result<(), types::Error> { let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -368,15 +369,18 @@ impl types::Host for T { Ok(()) } - async fn stat(&mut self, fd: types::Descriptor) -> Result { + async fn stat( + &mut self, + fd: Resource, + ) -> Result { let table = self.table(); - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; // No permissions check on stat: if opened, allowed to stat it let meta = f.spawn_blocking(|f| f.metadata()).await?; Ok(descriptorstat_from(meta)) - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; // No permissions check on stat: if opened, allowed to stat it let meta = d.spawn_blocking(|d| d.dir_metadata()).await?; Ok(descriptorstat_from(meta)) @@ -387,12 +391,12 @@ impl types::Host for T { async fn stat_at( &mut self, - fd: types::Descriptor, + fd: Resource, path_flags: types::PathFlags, path: String, ) -> Result { let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -407,7 +411,7 @@ impl types::Host for T { async fn set_times_at( &mut self, - fd: types::Descriptor, + fd: Resource, path_flags: types::PathFlags, path: String, atim: types::NewTimestamp, @@ -416,7 +420,7 @@ impl types::Host for T { use cap_fs_ext::DirExt; let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -446,19 +450,19 @@ impl types::Host for T { async fn link_at( &mut self, - fd: types::Descriptor, + fd: Resource, // TODO delete the path flags from this function old_path_flags: types::PathFlags, old_path: String, - new_descriptor: types::Descriptor, + new_descriptor: Resource, new_path: String, ) -> Result<(), types::Error> { let table = self.table(); - let old_dir = table.get_dir(fd)?; + let old_dir = table.get_dir(&fd)?; if !old_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } - let new_dir = table.get_dir(new_descriptor)?; + let new_dir = table.get_dir(&new_descriptor)?; if !new_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -474,7 +478,7 @@ impl types::Host for T { async fn open_at( &mut self, - fd: types::Descriptor, + fd: Resource, path_flags: types::PathFlags, path: String, oflags: types::OpenFlags, @@ -482,16 +486,16 @@ impl types::Host for T { // TODO: These are the permissions to use when creating a new file. // Not implemented yet. _mode: types::Modes, - ) -> Result { + ) -> Result, types::Error> { use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; use system_interface::fs::{FdFlags, GetSetFdFlags}; use types::{DescriptorFlags, OpenFlags}; let table = self.table_mut(); - if table.is_file(fd) { + if table.is_file(&fd) { Err(ErrorCode::NotDirectory)?; } - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::READ) { Err(ErrorCode::NotPermitted)?; } @@ -590,7 +594,7 @@ impl types::Host for T { } } - fn drop_descriptor(&mut self, fd: types::Descriptor) -> anyhow::Result<()> { + fn drop(&mut self, fd: Resource) -> anyhow::Result<()> { let table = self.table_mut(); // The Drop will close the file/dir, but if the close syscall @@ -598,7 +602,7 @@ impl types::Host for T { // tokio::fs::File just uses std::fs::File's Drop impl to close, so // it doesn't appear anyone else has found this to be a problem. // (Not that they could solve it without async drop...) - if table.delete_file(fd).is_err() { + if table.delete_file(Resource::new_own(fd.rep())).is_err() { table.delete_dir(fd)?; } @@ -607,11 +611,11 @@ impl types::Host for T { async fn readlink_at( &mut self, - fd: types::Descriptor, + fd: Resource, path: String, ) -> Result { let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -624,11 +628,11 @@ impl types::Host for T { async fn remove_directory_at( &mut self, - fd: types::Descriptor, + fd: Resource, path: String, ) -> Result<(), types::Error> { let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -637,17 +641,17 @@ impl types::Host for T { async fn rename_at( &mut self, - fd: types::Descriptor, + fd: Resource, old_path: String, - new_fd: types::Descriptor, + new_fd: Resource, new_path: String, ) -> Result<(), types::Error> { let table = self.table(); - let old_dir = table.get_dir(fd)?; + let old_dir = table.get_dir(&fd)?; if !old_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } - let new_dir = table.get_dir(new_fd)?; + let new_dir = table.get_dir(&new_fd)?; if !new_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -659,7 +663,7 @@ impl types::Host for T { async fn symlink_at( &mut self, - fd: types::Descriptor, + fd: Resource, src_path: String, dest_path: String, ) -> Result<(), types::Error> { @@ -668,7 +672,7 @@ impl types::Host for T { use cap_fs_ext::DirExt; let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -678,13 +682,13 @@ impl types::Host for T { async fn unlink_file_at( &mut self, - fd: types::Descriptor, + fd: Resource, path: String, ) -> Result<(), types::Error> { use cap_fs_ext::DirExt; let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; if !d.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } @@ -694,7 +698,7 @@ impl types::Host for T { async fn access_at( &mut self, - _fd: types::Descriptor, + _fd: Resource, _path_flags: types::PathFlags, _path: String, _access: types::AccessType, @@ -704,7 +708,7 @@ impl types::Host for T { async fn change_file_permissions_at( &mut self, - _fd: types::Descriptor, + _fd: Resource, _path_flags: types::PathFlags, _path: String, _mode: types::Modes, @@ -714,7 +718,7 @@ impl types::Host for T { async fn change_directory_permissions_at( &mut self, - _fd: types::Descriptor, + _fd: Resource, _path_flags: types::PathFlags, _path: String, _mode: types::Modes, @@ -722,38 +726,47 @@ impl types::Host for T { todo!("filesystem change_directory_permissions_at is not implemented") } - async fn lock_shared(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { + async fn lock_shared(&mut self, _fd: Resource) -> Result<(), types::Error> { todo!("filesystem lock_shared is not implemented") } - async fn lock_exclusive(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { + async fn lock_exclusive( + &mut self, + _fd: Resource, + ) -> Result<(), types::Error> { todo!("filesystem lock_exclusive is not implemented") } - async fn try_lock_shared(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { + async fn try_lock_shared( + &mut self, + _fd: Resource, + ) -> Result<(), types::Error> { todo!("filesystem try_lock_shared is not implemented") } - async fn try_lock_exclusive(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { + async fn try_lock_exclusive( + &mut self, + _fd: Resource, + ) -> Result<(), types::Error> { todo!("filesystem try_lock_exclusive is not implemented") } - async fn unlock(&mut self, _fd: types::Descriptor) -> Result<(), types::Error> { + async fn unlock(&mut self, _fd: Resource) -> Result<(), types::Error> { todo!("filesystem unlock is not implemented") } fn read_via_stream( &mut self, - fd: types::Descriptor, + fd: Resource, offset: types::Filesize, - ) -> Result { + ) -> Result, types::Error> { use crate::preview2::{ filesystem::FileInputStream, stream::{InternalInputStream, InternalTableStreamExt}, }; // Trap if fd lookup fails: - let f = self.table().get_file(fd)?; + let f = self.table().get_file(&fd)?; if !f.perms.contains(FilePerms::READ) { Err(types::ErrorCode::BadDescriptor)?; @@ -774,13 +787,13 @@ impl types::Host for T { fn write_via_stream( &mut self, - fd: types::Descriptor, + fd: Resource, offset: types::Filesize, - ) -> Result { + ) -> Result, types::Error> { use crate::preview2::{filesystem::FileOutputStream, TableStreamExt}; // Trap if fd lookup fails: - let f = self.table().get_file(fd)?; + let f = self.table().get_file(&fd)?; if !f.perms.contains(FilePerms::WRITE) { Err(types::ErrorCode::BadDescriptor)?; @@ -800,12 +813,12 @@ impl types::Host for T { fn append_via_stream( &mut self, - fd: types::Descriptor, - ) -> Result { + fd: Resource, + ) -> Result, types::Error> { use crate::preview2::{filesystem::FileOutputStream, TableStreamExt}; // Trap if fd lookup fails: - let f = self.table().get_file(fd)?; + let f = self.table().get_file(&fd)?; if !f.perms.contains(FilePerms::WRITE) { Err(types::ErrorCode::BadDescriptor)?; @@ -824,8 +837,8 @@ impl types::Host for T { async fn is_same_object( &mut self, - a: types::Descriptor, - b: types::Descriptor, + a: Resource, + b: Resource, ) -> anyhow::Result { use cap_fs_ext::MetadataExt; let table = self.table(); @@ -850,7 +863,7 @@ impl types::Host for T { } async fn metadata_hash( &mut self, - fd: types::Descriptor, + fd: Resource, ) -> Result { let table = self.table(); let meta = get_descriptor_metadata(table, fd).await?; @@ -858,12 +871,12 @@ impl types::Host for T { } async fn metadata_hash_at( &mut self, - fd: types::Descriptor, + fd: Resource, path_flags: types::PathFlags, path: String, ) -> Result { let table = self.table(); - let d = table.get_dir(fd)?; + let d = table.get_dir(&fd)?; // No permissions check on metadata: if dir opened, allowed to stat it let meta = d .spawn_blocking(move |d| { @@ -878,16 +891,33 @@ impl types::Host for T { } } +#[async_trait::async_trait] +impl HostDirectoryEntryStream for T { + async fn read_directory_entry( + &mut self, + stream: Resource, + ) -> Result, types::Error> { + let table = self.table(); + let readdir = table.get_readdir(&stream)?; + readdir.next() + } + + fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { + self.table_mut().delete_readdir(stream)?; + Ok(()) + } +} + async fn get_descriptor_metadata( table: &Table, - fd: types::Descriptor, + fd: Resource, ) -> Result { - if table.is_file(fd) { - let f = table.get_file(fd)?; + if table.is_file(&fd) { + let f = table.get_file(&fd)?; // No permissions check on metadata: if opened, allowed to stat it Ok(f.spawn_blocking(|f| f.metadata()).await?) - } else if table.is_dir(fd) { - let d = table.get_dir(fd)?; + } else if table.is_dir(&fd) { + let d = table.get_dir(&fd)?; // No permissions check on metadata: if opened, allowed to stat it Ok(d.spawn_blocking(|d| d.dir_metadata()).await?) } else { @@ -1065,28 +1095,9 @@ fn descriptorstat_from(meta: cap_std::fs::Metadata) -> types::DescriptorStat { type_: descriptortype_from(meta.file_type()), link_count: meta.nlink(), size: meta.len(), - // FIXME change the wit to make these timestamps optional - data_access_timestamp: meta - .accessed() - .map(|t| datetime_from(t.into_std())) - .unwrap_or(wall_clock::Datetime { - seconds: 0, - nanoseconds: 0, - }), - data_modification_timestamp: meta - .modified() - .map(|t| datetime_from(t.into_std())) - .unwrap_or(wall_clock::Datetime { - seconds: 0, - nanoseconds: 0, - }), - status_change_timestamp: meta - .created() - .map(|t| datetime_from(t.into_std())) - .unwrap_or(wall_clock::Datetime { - seconds: 0, - nanoseconds: 0, - }), + data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(), + data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(), + status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(), } } @@ -1121,21 +1132,39 @@ impl IntoIterator for ReaddirIterator { } pub(crate) trait TableReaddirExt { - fn push_readdir(&mut self, readdir: ReaddirIterator) -> Result; - fn delete_readdir(&mut self, fd: u32) -> Result; - fn get_readdir(&self, fd: u32) -> Result<&ReaddirIterator, TableError>; + fn push_readdir( + &mut self, + readdir: ReaddirIterator, + ) -> Result, TableError>; + fn delete_readdir( + &mut self, + fd: Resource, + ) -> Result; + fn get_readdir( + &self, + fd: &Resource, + ) -> Result<&ReaddirIterator, TableError>; } impl TableReaddirExt for Table { - fn push_readdir(&mut self, readdir: ReaddirIterator) -> Result { - self.push(Box::new(readdir)) + fn push_readdir( + &mut self, + readdir: ReaddirIterator, + ) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(readdir))?)) } - fn delete_readdir(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_readdir( + &mut self, + fd: Resource, + ) -> Result { + self.delete(fd.rep()) } - fn get_readdir(&self, fd: u32) -> Result<&ReaddirIterator, TableError> { - self.get(fd) + fn get_readdir( + &self, + fd: &Resource, + ) -> Result<&ReaddirIterator, TableError> { + self.get(fd.rep()) } } @@ -1160,8 +1189,7 @@ mod test { let ix = table .push_readdir(ReaddirIterator::new(std::iter::empty())) .unwrap(); - let _ = table.get_readdir(ix).unwrap(); + let _ = table.get_readdir(&ix).unwrap(); table.delete_readdir(ix).unwrap(); - let _ = table.get_readdir(ix).err().unwrap(); } } diff --git a/crates/wasi/src/preview2/host/filesystem/sync.rs b/crates/wasi/src/preview2/host/filesystem/sync.rs index 55ad761d8cfc..36bb244b5719 100644 --- a/crates/wasi/src/preview2/host/filesystem/sync.rs +++ b/crates/wasi/src/preview2/host/filesystem/sync.rs @@ -2,154 +2,146 @@ use crate::preview2::bindings::filesystem::types as async_filesystem; use crate::preview2::bindings::sync_io::filesystem::types as sync_filesystem; use crate::preview2::bindings::sync_io::io::streams; use crate::preview2::in_tokio; +use wasmtime::component::Resource; -impl sync_filesystem::Host for T { +impl sync_filesystem::Host for T {} + +impl sync_filesystem::HostDescriptor for T { fn advise( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, offset: sync_filesystem::Filesize, len: sync_filesystem::Filesize, advice: sync_filesystem::Advice, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::advise(self, fd, offset, len, advice.into()).await + async_filesystem::HostDescriptor::advise(self, fd, offset, len, advice.into()).await })?) } - fn sync_data(&mut self, fd: sync_filesystem::Descriptor) -> Result<(), sync_filesystem::Error> { + fn sync_data( + &mut self, + fd: Resource, + ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::sync_data(self, fd).await + async_filesystem::HostDescriptor::sync_data(self, fd).await })?) } fn get_flags( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result { - Ok(in_tokio(async { async_filesystem::Host::get_flags(self, fd).await })?.into()) + Ok(in_tokio(async { async_filesystem::HostDescriptor::get_flags(self, fd).await })?.into()) } fn get_type( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result { - Ok(in_tokio(async { async_filesystem::Host::get_type(self, fd).await })?.into()) + Ok(in_tokio(async { async_filesystem::HostDescriptor::get_type(self, fd).await })?.into()) } fn set_size( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, size: sync_filesystem::Filesize, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::set_size(self, fd, size).await + async_filesystem::HostDescriptor::set_size(self, fd, size).await })?) } fn set_times( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, atim: sync_filesystem::NewTimestamp, mtim: sync_filesystem::NewTimestamp, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::set_times(self, fd, atim.into(), mtim.into()).await + async_filesystem::HostDescriptor::set_times(self, fd, atim.into(), mtim.into()).await })?) } fn read( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, len: sync_filesystem::Filesize, offset: sync_filesystem::Filesize, ) -> Result<(Vec, bool), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::read(self, fd, len, offset).await + async_filesystem::HostDescriptor::read(self, fd, len, offset).await })?) } fn write( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, buf: Vec, offset: sync_filesystem::Filesize, ) -> Result { Ok(in_tokio(async { - async_filesystem::Host::write(self, fd, buf, offset).await + async_filesystem::HostDescriptor::write(self, fd, buf, offset).await })?) } fn read_directory( &mut self, - fd: sync_filesystem::Descriptor, - ) -> Result { + fd: Resource, + ) -> Result, sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::read_directory(self, fd).await + async_filesystem::HostDescriptor::read_directory(self, fd).await })?) } - fn read_directory_entry( - &mut self, - stream: sync_filesystem::DirectoryEntryStream, - ) -> Result, sync_filesystem::Error> { - Ok( - in_tokio(async { async_filesystem::Host::read_directory_entry(self, stream).await })? - .map(|e| e.into()), - ) - } - - fn drop_directory_entry_stream( + fn sync( &mut self, - stream: sync_filesystem::DirectoryEntryStream, - ) -> anyhow::Result<()> { - async_filesystem::Host::drop_directory_entry_stream(self, stream) - } - - fn sync(&mut self, fd: sync_filesystem::Descriptor) -> Result<(), sync_filesystem::Error> { + fd: Resource, + ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::sync(self, fd).await + async_filesystem::HostDescriptor::sync(self, fd).await })?) } fn create_directory_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::create_directory_at(self, fd, path).await + async_filesystem::HostDescriptor::create_directory_at(self, fd, path).await })?) } fn stat( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result { - Ok(in_tokio(async { async_filesystem::Host::stat(self, fd).await })?.into()) + Ok(in_tokio(async { async_filesystem::HostDescriptor::stat(self, fd).await })?.into()) } fn stat_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, ) -> Result { Ok(in_tokio(async { - async_filesystem::Host::stat_at(self, fd, path_flags.into(), path).await + async_filesystem::HostDescriptor::stat_at(self, fd, path_flags.into(), path).await })? .into()) } fn set_times_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, atim: sync_filesystem::NewTimestamp, mtim: sync_filesystem::NewTimestamp, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::set_times_at( + async_filesystem::HostDescriptor::set_times_at( self, fd, path_flags.into(), @@ -163,15 +155,15 @@ impl sync_filesystem::Host for T { fn link_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, // TODO delete the path flags from this function old_path_flags: sync_filesystem::PathFlags, old_path: String, - new_descriptor: sync_filesystem::Descriptor, + new_descriptor: Resource, new_path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::link_at( + async_filesystem::HostDescriptor::link_at( self, fd, old_path_flags.into(), @@ -185,15 +177,15 @@ impl sync_filesystem::Host for T { fn open_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, oflags: sync_filesystem::OpenFlags, flags: sync_filesystem::DescriptorFlags, mode: sync_filesystem::Modes, - ) -> Result { + ) -> Result, sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::open_at( + async_filesystem::HostDescriptor::open_at( self, fd, path_flags.into(), @@ -206,85 +198,91 @@ impl sync_filesystem::Host for T { })?) } - fn drop_descriptor(&mut self, fd: sync_filesystem::Descriptor) -> anyhow::Result<()> { - async_filesystem::Host::drop_descriptor(self, fd) + fn drop(&mut self, fd: Resource) -> anyhow::Result<()> { + async_filesystem::HostDescriptor::drop(self, fd) } fn readlink_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path: String, ) -> Result { Ok(in_tokio(async { - async_filesystem::Host::readlink_at(self, fd, path).await + async_filesystem::HostDescriptor::readlink_at(self, fd, path).await })?) } fn remove_directory_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::remove_directory_at(self, fd, path).await + async_filesystem::HostDescriptor::remove_directory_at(self, fd, path).await })?) } fn rename_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, old_path: String, - new_fd: sync_filesystem::Descriptor, + new_fd: Resource, new_path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::rename_at(self, fd, old_path, new_fd, new_path).await + async_filesystem::HostDescriptor::rename_at(self, fd, old_path, new_fd, new_path).await })?) } fn symlink_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, src_path: String, dest_path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::symlink_at(self, fd, src_path, dest_path).await + async_filesystem::HostDescriptor::symlink_at(self, fd, src_path, dest_path).await })?) } fn unlink_file_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path: String, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::unlink_file_at(self, fd, path).await + async_filesystem::HostDescriptor::unlink_file_at(self, fd, path).await })?) } fn access_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, access: sync_filesystem::AccessType, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::access_at(self, fd, path_flags.into(), path, access.into()) - .await + async_filesystem::HostDescriptor::access_at( + self, + fd, + path_flags.into(), + path, + access.into(), + ) + .await })?) } fn change_file_permissions_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, mode: sync_filesystem::Modes, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::change_file_permissions_at( + async_filesystem::HostDescriptor::change_file_permissions_at( self, fd, path_flags.into(), @@ -297,13 +295,13 @@ impl sync_filesystem::Host for T { fn change_directory_permissions_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, mode: sync_filesystem::Modes, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::change_directory_permissions_at( + async_filesystem::HostDescriptor::change_directory_permissions_at( self, fd, path_flags.into(), @@ -316,97 +314,131 @@ impl sync_filesystem::Host for T { fn lock_shared( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::lock_shared(self, fd).await + async_filesystem::HostDescriptor::lock_shared(self, fd).await })?) } fn lock_exclusive( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::lock_exclusive(self, fd).await + async_filesystem::HostDescriptor::lock_exclusive(self, fd).await })?) } fn try_lock_shared( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::try_lock_shared(self, fd).await + async_filesystem::HostDescriptor::try_lock_shared(self, fd).await })?) } fn try_lock_exclusive( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::try_lock_exclusive(self, fd).await + async_filesystem::HostDescriptor::try_lock_exclusive(self, fd).await })?) } - fn unlock(&mut self, fd: sync_filesystem::Descriptor) -> Result<(), sync_filesystem::Error> { + fn unlock( + &mut self, + fd: Resource, + ) -> Result<(), sync_filesystem::Error> { Ok(in_tokio(async { - async_filesystem::Host::unlock(self, fd).await + async_filesystem::HostDescriptor::unlock(self, fd).await })?) } fn read_via_stream( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, offset: sync_filesystem::Filesize, - ) -> Result { - Ok(async_filesystem::Host::read_via_stream(self, fd, offset)?) + ) -> Result, sync_filesystem::Error> { + Ok(async_filesystem::HostDescriptor::read_via_stream( + self, fd, offset, + )?) } fn write_via_stream( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, offset: sync_filesystem::Filesize, - ) -> Result { - Ok(async_filesystem::Host::write_via_stream(self, fd, offset)?) + ) -> Result, sync_filesystem::Error> { + Ok(async_filesystem::HostDescriptor::write_via_stream( + self, fd, offset, + )?) } fn append_via_stream( &mut self, - fd: sync_filesystem::Descriptor, - ) -> Result { - Ok(async_filesystem::Host::append_via_stream(self, fd)?) + fd: Resource, + ) -> Result, sync_filesystem::Error> { + Ok(async_filesystem::HostDescriptor::append_via_stream( + self, fd, + )?) } fn is_same_object( &mut self, - a: sync_filesystem::Descriptor, - b: sync_filesystem::Descriptor, + a: Resource, + b: Resource, ) -> anyhow::Result { Ok(in_tokio(async { - async_filesystem::Host::is_same_object(self, a, b).await + async_filesystem::HostDescriptor::is_same_object(self, a, b).await })?) } fn metadata_hash( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, ) -> Result { - Ok(in_tokio(async { async_filesystem::Host::metadata_hash(self, fd).await })?.into()) + Ok( + in_tokio(async { async_filesystem::HostDescriptor::metadata_hash(self, fd).await })? + .into(), + ) } fn metadata_hash_at( &mut self, - fd: sync_filesystem::Descriptor, + fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, ) -> Result { Ok(in_tokio(async { - async_filesystem::Host::metadata_hash_at(self, fd, path_flags.into(), path).await + async_filesystem::HostDescriptor::metadata_hash_at(self, fd, path_flags.into(), path) + .await })? .into()) } } +impl sync_filesystem::HostDirectoryEntryStream + for T +{ + fn read_directory_entry( + &mut self, + stream: Resource, + ) -> Result, sync_filesystem::Error> { + Ok(in_tokio(async { + async_filesystem::HostDirectoryEntryStream::read_directory_entry(self, stream).await + })? + .map(|e| e.into())) + } + + fn drop( + &mut self, + stream: Resource, + ) -> anyhow::Result<()> { + async_filesystem::HostDirectoryEntryStream::drop(self, stream) + } +} + impl From for sync_filesystem::ErrorCode { fn from(other: async_filesystem::ErrorCode) -> Self { use async_filesystem::ErrorCode; diff --git a/crates/wasi/src/preview2/host/instance_network.rs b/crates/wasi/src/preview2/host/instance_network.rs index 8c8b56974aa1..f85e411e1e94 100644 --- a/crates/wasi/src/preview2/host/instance_network.rs +++ b/crates/wasi/src/preview2/host/instance_network.rs @@ -1,10 +1,11 @@ use crate::preview2::bindings::sockets::instance_network::{self, Network}; -use crate::preview2::network::{HostNetwork, TableNetworkExt}; +use crate::preview2::network::{HostNetworkState, TableNetworkExt}; use crate::preview2::WasiView; +use wasmtime::component::Resource; impl instance_network::Host for T { - fn instance_network(&mut self) -> Result { - let network = HostNetwork::new(self.ctx().pool.clone()); + fn instance_network(&mut self) -> Result, anyhow::Error> { + let network = HostNetworkState::new(self.ctx().pool.clone()); let network = self.table_mut().push_network(network)?; Ok(network) } diff --git a/crates/wasi/src/preview2/host/io.rs b/crates/wasi/src/preview2/host/io.rs index 128272850348..ddf309ca0517 100644 --- a/crates/wasi/src/preview2/host/io.rs +++ b/crates/wasi/src/preview2/host/io.rs @@ -1,6 +1,6 @@ use crate::preview2::{ + bindings::io::poll::Pollable, bindings::io::streams::{self, InputStream, OutputStream}, - bindings::poll::poll::Pollable, filesystem::FileInputStream, poll::PollableFuture, stream::{ @@ -13,6 +13,7 @@ use std::any::Any; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +use wasmtime::component::Resource; impl From for streams::StreamStatus { fn from(state: StreamState) -> Self { @@ -42,23 +43,221 @@ impl From for streams::Error { } #[async_trait::async_trait] -impl streams::Host for T { - fn drop_input_stream(&mut self, stream: InputStream) -> anyhow::Result<()> { - self.table_mut().delete_internal_input_stream(stream)?; +impl streams::Host for T {} + +#[async_trait::async_trait] +impl streams::HostOutputStream for T { + fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { + self.table_mut().delete_output_stream(stream)?; Ok(()) } - fn drop_output_stream(&mut self, stream: OutputStream) -> anyhow::Result<()> { - self.table_mut().delete_output_stream(stream)?; + fn check_write(&mut self, stream: Resource) -> Result { + let s = self.table_mut().get_output_stream_mut(&stream)?; + let mut ready = s.write_ready(); + let mut task = Context::from_waker(futures::task::noop_waker_ref()); + match Pin::new(&mut ready).poll(&mut task) { + Poll::Ready(Ok(permit)) => Ok(permit as u64), + Poll::Ready(Err(e)) => Err(e.into()), + Poll::Pending => Ok(0), + } + } + + fn write( + &mut self, + stream: Resource, + bytes: Vec, + ) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + HostOutputStream::write(s, bytes.into())?; + Ok(()) + } + + fn subscribe(&mut self, stream: Resource) -> anyhow::Result> { + // Ensure that table element is an output-stream: + let _ = self.table_mut().get_output_stream_mut(&stream)?; + + fn output_stream_ready<'a>(stream: &'a mut dyn Any) -> PollableFuture<'a> { + let stream = stream + .downcast_mut::>() + .expect("downcast to HostOutputStream failed"); + Box::pin(async move { + let _ = stream.write_ready().await?; + Ok(()) + }) + } + + Ok(self + .table_mut() + .push_host_pollable(HostPollable::TableEntry { + index: stream.rep(), + make_future: output_stream_ready, + })?) + } + + async fn blocking_write_and_flush( + &mut self, + stream: Resource, + bytes: Vec, + ) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + + if bytes.len() > 4096 { + return Err(streams::Error::trap(anyhow::anyhow!( + "Buffer too large for blocking-write-and-flush (expected at most 4096)" + ))); + } + + let mut bytes = bytes::Bytes::from(bytes); + while !bytes.is_empty() { + let permit = s.write_ready().await?; + let len = bytes.len().min(permit); + let chunk = bytes.split_to(len); + HostOutputStream::write(s, chunk)?; + } + + HostOutputStream::flush(s)?; + let _ = s.write_ready().await?; + + Ok(()) + } + + async fn blocking_write_zeroes_and_flush( + &mut self, + stream: Resource, + len: u64, + ) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + + if len > 4096 { + return Err(streams::Error::trap(anyhow::anyhow!( + "Buffer too large for blocking-write-zeroes-and-flush (expected at most 4096)" + ))); + } + + let mut len = len; + while len > 0 { + let permit = s.write_ready().await?; + let this_len = len.min(permit as u64); + HostOutputStream::write_zeroes(s, this_len as usize)?; + len -= this_len; + } + + HostOutputStream::flush(s)?; + let _ = s.write_ready().await?; + + Ok(()) + } + + fn write_zeroes( + &mut self, + stream: Resource, + len: u64, + ) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + HostOutputStream::write_zeroes(s, len as usize)?; + Ok(()) + } + + fn flush(&mut self, stream: Resource) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + HostOutputStream::flush(s)?; + Ok(()) + } + + async fn blocking_flush( + &mut self, + stream: Resource, + ) -> Result<(), streams::Error> { + let s = self.table_mut().get_output_stream_mut(&stream)?; + HostOutputStream::flush(s)?; + let _ = s.write_ready().await?; + Ok(()) + } + + async fn splice( + &mut self, + _dst: Resource, + _src: Resource, + _len: u64, + ) -> anyhow::Result> { + // TODO: We can't get two streams at the same time because they both + // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is + // stabilized, that could allow us to add a `get_many_stream_mut` or + // so which lets us do this. + // + // [`get_many_mut`]: https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html#method.get_many_mut + /* + let s: &mut Box = ctx + .table_mut() + .get_input_stream_mut(src) + ?; + let d: &mut Box = ctx + .table_mut() + .get_output_stream_mut(dst) + ?; + + let bytes_spliced: u64 = s.splice(&mut **d, len).await?; + + Ok(bytes_spliced) + */ + todo!("stream splice is not implemented") + } + + async fn blocking_splice( + &mut self, + _dst: Resource, + _src: Resource, + _len: u64, + ) -> anyhow::Result> { + // TODO: once splice is implemented, figure out what the blocking semantics are for waiting + // on src and dest here. + todo!("stream splice is not implemented") + } + + async fn forward( + &mut self, + _dst: Resource, + _src: Resource, + ) -> anyhow::Result> { + // TODO: We can't get two streams at the same time because they both + // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is + // stabilized, that could allow us to add a `get_many_stream_mut` or + // so which lets us do this. + // + // [`get_many_mut`]: https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html#method.get_many_mut + /* + let s: &mut Box = ctx + .table_mut() + .get_input_stream_mut(src) + ?; + let d: &mut Box = ctx + .table_mut() + .get_output_stream_mut(dst) + ?; + + let bytes_spliced: u64 = s.splice(&mut **d, len).await?; + + Ok(bytes_spliced) + */ + + todo!("stream forward is not implemented") + } +} + +#[async_trait::async_trait] +impl streams::HostInputStream for T { + fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { + self.table_mut().delete_internal_input_stream(stream)?; Ok(()) } async fn read( &mut self, - stream: InputStream, + stream: Resource, len: u64, ) -> anyhow::Result, streams::StreamStatus), ()>> { - match self.table_mut().get_internal_input_stream_mut(stream)? { + match self.table_mut().get_internal_input_stream_mut(&stream)? { InternalInputStream::Host(s) => { let (bytes, state) = match HostInputStream::read(s.as_mut(), len as usize) { Ok(a) => a, @@ -94,10 +293,10 @@ impl streams::Host for T { async fn blocking_read( &mut self, - stream: InputStream, + stream: Resource, len: u64, ) -> anyhow::Result, streams::StreamStatus), ()>> { - match self.table_mut().get_internal_input_stream_mut(stream)? { + match self.table_mut().get_internal_input_stream_mut(&stream)? { InternalInputStream::Host(s) => { s.ready().await?; let (bytes, state) = match HostInputStream::read(s.as_mut(), len as usize) { @@ -133,10 +332,10 @@ impl streams::Host for T { async fn skip( &mut self, - stream: InputStream, + stream: Resource, len: u64, ) -> anyhow::Result> { - match self.table_mut().get_internal_input_stream_mut(stream)? { + match self.table_mut().get_internal_input_stream_mut(&stream)? { InternalInputStream::Host(s) => { // TODO: the cast to usize should be fallible, use `.try_into()?` let (bytes_skipped, state) = match HostInputStream::skip(s.as_mut(), len as usize) { @@ -172,10 +371,10 @@ impl streams::Host for T { async fn blocking_skip( &mut self, - stream: InputStream, + stream: Resource, len: u64, ) -> anyhow::Result> { - match self.table_mut().get_internal_input_stream_mut(stream)? { + match self.table_mut().get_internal_input_stream_mut(&stream)? { InternalInputStream::Host(s) => { s.ready().await?; // TODO: the cast to usize should be fallible, use `.try_into()?` @@ -210,9 +409,9 @@ impl streams::Host for T { } } - fn subscribe_to_input_stream(&mut self, stream: InputStream) -> anyhow::Result { + fn subscribe(&mut self, stream: Resource) -> anyhow::Result> { // Ensure that table element is an input-stream: - let pollable = match self.table_mut().get_internal_input_stream_mut(stream)? { + let pollable = match self.table_mut().get_internal_input_stream_mut(&stream)? { InternalInputStream::Host(_) => { fn input_stream_ready<'a>(stream: &'a mut dyn Any) -> PollableFuture<'a> { let stream = stream @@ -225,7 +424,7 @@ impl streams::Host for T { } HostPollable::TableEntry { - index: stream, + index: stream.rep(), make_future: input_stream_ready, } } @@ -237,179 +436,19 @@ impl streams::Host for T { }; Ok(self.table_mut().push_host_pollable(pollable)?) } - - /* -------------------------------------------------------------- - * - * OutputStream methods - * - * -------------------------------------------------------------- */ - - fn check_write(&mut self, stream: OutputStream) -> Result { - let s = self.table_mut().get_output_stream_mut(stream)?; - let mut ready = s.write_ready(); - let mut task = Context::from_waker(futures::task::noop_waker_ref()); - match Pin::new(&mut ready).poll(&mut task) { - Poll::Ready(Ok(permit)) => Ok(permit as u64), - Poll::Ready(Err(e)) => Err(e.into()), - Poll::Pending => Ok(0), - } - } - - async fn write(&mut self, stream: OutputStream, bytes: Vec) -> Result<(), streams::Error> { - let s = self.table_mut().get_output_stream_mut(stream)?; - HostOutputStream::write(s, bytes.into())?; - Ok(()) - } - - fn subscribe_to_output_stream(&mut self, stream: OutputStream) -> anyhow::Result { - // Ensure that table element is an output-stream: - let _ = self.table_mut().get_output_stream_mut(stream)?; - - fn output_stream_ready<'a>(stream: &'a mut dyn Any) -> PollableFuture<'a> { - let stream = stream - .downcast_mut::>() - .expect("downcast to HostOutputStream failed"); - Box::pin(async move { - let _ = stream.write_ready().await?; - Ok(()) - }) - } - - Ok(self - .table_mut() - .push_host_pollable(HostPollable::TableEntry { - index: stream, - make_future: output_stream_ready, - })?) - } - - async fn blocking_write_and_flush( - &mut self, - stream: OutputStream, - bytes: Vec, - ) -> Result<(), streams::Error> { - let s = self.table_mut().get_output_stream_mut(stream)?; - - if bytes.len() > 4096 { - return Err(streams::Error::trap(anyhow::anyhow!( - "Buffer too large for blocking-write-and-flush (expected at most 4096)" - ))); - } - - let mut bytes = bytes::Bytes::from(bytes); - while !bytes.is_empty() { - let permit = s.write_ready().await?; - let len = bytes.len().min(permit); - let chunk = bytes.split_to(len); - HostOutputStream::write(s, chunk)?; - } - - HostOutputStream::flush(s)?; - let _ = s.write_ready().await?; - - Ok(()) - } - - fn write_zeroes(&mut self, stream: OutputStream, len: u64) -> Result<(), streams::Error> { - let s = self.table_mut().get_output_stream_mut(stream)?; - HostOutputStream::write_zeroes(s, len as usize)?; - Ok(()) - } - - fn flush(&mut self, stream: OutputStream) -> Result<(), streams::Error> { - let s = self.table_mut().get_output_stream_mut(stream)?; - HostOutputStream::flush(s)?; - Ok(()) - } - async fn blocking_flush(&mut self, stream: OutputStream) -> Result<(), streams::Error> { - let s = self.table_mut().get_output_stream_mut(stream)?; - HostOutputStream::flush(s)?; - let _ = s.write_ready().await?; - Ok(()) - } - - /* -------------------------------------------------------------- - * - * Aspirational methods - * - * -------------------------------------------------------------- */ - async fn splice( - &mut self, - _src: InputStream, - _dst: OutputStream, - _len: u64, - ) -> anyhow::Result> { - // TODO: We can't get two streams at the same time because they both - // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is - // stabilized, that could allow us to add a `get_many_stream_mut` or - // so which lets us do this. - // - // [`get_many_mut`]: https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html#method.get_many_mut - /* - let s: &mut Box = ctx - .table_mut() - .get_input_stream_mut(src) - ?; - let d: &mut Box = ctx - .table_mut() - .get_output_stream_mut(dst) - ?; - - let bytes_spliced: u64 = s.splice(&mut **d, len).await?; - - Ok(bytes_spliced) - */ - todo!("stream splice is not implemented") - } - - async fn blocking_splice( - &mut self, - _src: InputStream, - _dst: OutputStream, - _len: u64, - ) -> anyhow::Result> { - // TODO: once splice is implemented, figure out what the blocking semantics are for waiting - // on src and dest here. - todo!("stream splice is not implemented") - } - - async fn forward( - &mut self, - _src: InputStream, - _dst: OutputStream, - ) -> anyhow::Result> { - // TODO: We can't get two streams at the same time because they both - // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is - // stabilized, that could allow us to add a `get_many_stream_mut` or - // so which lets us do this. - // - // [`get_many_mut`]: https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html#method.get_many_mut - /* - let s: &mut Box = ctx - .table_mut() - .get_input_stream_mut(src) - ?; - let d: &mut Box = ctx - .table_mut() - .get_output_stream_mut(dst) - ?; - - let bytes_spliced: u64 = s.splice(&mut **d, len).await?; - - Ok(bytes_spliced) - */ - - todo!("stream forward is not implemented") - } } pub mod sync { use crate::preview2::{ - bindings::io::streams::{self as async_streams, Host as AsyncHost}, + bindings::io::streams::{ + self as async_streams, HostInputStream as AsyncHostInputStream, + HostOutputStream as AsyncHostOutputStream, + }, + bindings::sync_io::io::poll::Pollable, bindings::sync_io::io::streams::{self, InputStream, OutputStream}, - bindings::sync_io::poll::poll::Pollable, in_tokio, WasiView, }; + use wasmtime::component::Resource; // same boilerplate everywhere, converting between two identical types with different // definition sites. one day wasmtime-wit-bindgen will make all this unnecessary @@ -445,108 +484,146 @@ pub mod sync { } } - impl streams::Host for T { - fn drop_input_stream(&mut self, stream: InputStream) -> anyhow::Result<()> { - AsyncHost::drop_input_stream(self, stream) - } + impl streams::Host for T {} - fn drop_output_stream(&mut self, stream: OutputStream) -> anyhow::Result<()> { - AsyncHost::drop_output_stream(self, stream) + impl streams::HostOutputStream for T { + fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { + AsyncHostOutputStream::drop(self, stream) } - fn read( - &mut self, - stream: InputStream, - len: u64, - ) -> anyhow::Result, streams::StreamStatus), ()>> { - in_tokio(async { AsyncHost::read(self, stream, len).await }).map(xform) + fn check_write(&mut self, stream: Resource) -> Result { + Ok(AsyncHostOutputStream::check_write(self, stream)?) } - fn blocking_read( + fn write( &mut self, - stream: InputStream, - len: u64, - ) -> anyhow::Result, streams::StreamStatus), ()>> { - in_tokio(async { AsyncHost::blocking_read(self, stream, len).await }).map(xform) + stream: Resource, + bytes: Vec, + ) -> Result<(), streams::Error> { + Ok(AsyncHostOutputStream::write(self, stream, bytes)?) } - fn check_write(&mut self, stream: OutputStream) -> Result { - Ok(AsyncHost::check_write(self, stream)?) - } - fn write(&mut self, stream: OutputStream, bytes: Vec) -> Result<(), streams::Error> { + fn blocking_write_and_flush( + &mut self, + stream: Resource, + bytes: Vec, + ) -> Result<(), streams::Error> { Ok(in_tokio(async { - AsyncHost::write(self, stream, bytes).await + AsyncHostOutputStream::blocking_write_and_flush(self, stream, bytes).await })?) } - fn blocking_write_and_flush( + + fn blocking_write_zeroes_and_flush( &mut self, - stream: OutputStream, - bytes: Vec, + stream: Resource, + len: u64, ) -> Result<(), streams::Error> { Ok(in_tokio(async { - AsyncHost::blocking_write_and_flush(self, stream, bytes).await + AsyncHostOutputStream::blocking_write_zeroes_and_flush(self, stream, len).await })?) } - fn subscribe_to_output_stream(&mut self, stream: OutputStream) -> anyhow::Result { - AsyncHost::subscribe_to_output_stream(self, stream) + + fn subscribe( + &mut self, + stream: Resource, + ) -> anyhow::Result> { + Ok(AsyncHostOutputStream::subscribe(self, stream)?) } - fn write_zeroes(&mut self, stream: OutputStream, len: u64) -> Result<(), streams::Error> { - Ok(AsyncHost::write_zeroes(self, stream, len)?) + + fn write_zeroes( + &mut self, + stream: Resource, + len: u64, + ) -> Result<(), streams::Error> { + Ok(AsyncHostOutputStream::write_zeroes(self, stream, len)?) } - fn flush(&mut self, stream: OutputStream) -> Result<(), streams::Error> { - Ok(AsyncHost::flush(self, stream)?) + fn flush(&mut self, stream: Resource) -> Result<(), streams::Error> { + Ok(AsyncHostOutputStream::flush( + self, + Resource::new_borrow(stream.rep()), + )?) } - fn blocking_flush(&mut self, stream: OutputStream) -> Result<(), streams::Error> { + + fn blocking_flush(&mut self, stream: Resource) -> Result<(), streams::Error> { Ok(in_tokio(async { - AsyncHost::blocking_flush(self, stream).await + AsyncHostOutputStream::blocking_flush(self, Resource::new_borrow(stream.rep())) + .await })?) } - fn skip( + fn splice( &mut self, - stream: InputStream, + dst: Resource, + src: Resource, len: u64, ) -> anyhow::Result> { - in_tokio(async { AsyncHost::skip(self, stream, len).await }).map(xform) + in_tokio(async { AsyncHostOutputStream::splice(self, dst, src, len).await }).map(xform) } - fn blocking_skip( + fn blocking_splice( &mut self, - stream: InputStream, + dst: Resource, + src: Resource, len: u64, ) -> anyhow::Result> { - in_tokio(async { AsyncHost::blocking_skip(self, stream, len).await }).map(xform) + in_tokio(async { AsyncHostOutputStream::blocking_splice(self, dst, src, len).await }) + .map(xform) } - fn splice( + fn forward( &mut self, - src: InputStream, - dst: OutputStream, - len: u64, + dst: Resource, + src: Resource, ) -> anyhow::Result> { - in_tokio(async { AsyncHost::splice(self, src, dst, len).await }).map(xform) + in_tokio(async { AsyncHostOutputStream::forward(self, dst, src).await }).map(xform) } + } - fn blocking_splice( + impl streams::HostInputStream for T { + fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { + AsyncHostInputStream::drop(self, stream) + } + + fn read( + &mut self, + stream: Resource, + len: u64, + ) -> anyhow::Result, streams::StreamStatus), ()>> { + in_tokio(async { AsyncHostInputStream::read(self, stream, len).await }).map(xform) + } + + fn blocking_read( &mut self, - src: InputStream, - dst: OutputStream, + stream: Resource, + len: u64, + ) -> anyhow::Result, streams::StreamStatus), ()>> { + in_tokio(async { AsyncHostInputStream::blocking_read(self, stream, len).await }) + .map(xform) + } + + fn skip( + &mut self, + stream: Resource, len: u64, ) -> anyhow::Result> { - in_tokio(async { AsyncHost::blocking_splice(self, src, dst, len).await }).map(xform) + in_tokio(async { AsyncHostInputStream::skip(self, stream, len).await }).map(xform) } - fn forward( + fn blocking_skip( &mut self, - src: InputStream, - dst: OutputStream, + stream: Resource, + len: u64, ) -> anyhow::Result> { - in_tokio(async { AsyncHost::forward(self, src, dst).await }).map(xform) + in_tokio(async { AsyncHostInputStream::blocking_skip(self, stream, len).await }) + .map(xform) } - fn subscribe_to_input_stream(&mut self, stream: InputStream) -> anyhow::Result { - AsyncHost::subscribe_to_input_stream(self, stream) + fn subscribe( + &mut self, + stream: Resource, + ) -> anyhow::Result> { + AsyncHostInputStream::subscribe(self, stream) } } } diff --git a/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs index 023a5d9f3026..03d588e5320c 100644 --- a/crates/wasi/src/preview2/host/network.rs +++ b/crates/wasi/src/preview2/host/network.rs @@ -5,9 +5,12 @@ use crate::preview2::bindings::sockets::network::{ use crate::preview2::network::TableNetworkExt; use crate::preview2::{TableError, WasiView}; use std::io; +use wasmtime::component::Resource; -impl network::Host for T { - fn drop_network(&mut self, this: network::Network) -> Result<(), anyhow::Error> { +impl network::Host for T {} + +impl crate::preview2::bindings::sockets::network::HostNetwork for T { + fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { let table = self.table_mut(); table.delete_network(this)?; diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs index 89f9bc240207..370da1adb5a6 100644 --- a/crates/wasi/src/preview2/host/tcp.rs +++ b/crates/wasi/src/preview2/host/tcp.rs @@ -1,13 +1,13 @@ use crate::preview2::bindings::{ + io::poll::Pollable, io::streams::{InputStream, OutputStream}, - poll::poll::Pollable, sockets::network::{self, ErrorCode, IpAddressFamily, IpSocketAddress, Network}, sockets::tcp::{self, ShutdownType}, }; use crate::preview2::network::TableNetworkExt; use crate::preview2::poll::TablePollableExt; use crate::preview2::stream::TableStreamExt; -use crate::preview2::tcp::{HostTcpSocket, HostTcpState, TableTcpSocketExt}; +use crate::preview2::tcp::{HostTcpSocketState, HostTcpState, TableTcpSocketExt}; use crate::preview2::{HostPollable, PollableFuture, WasiView}; use cap_net_ext::{Blocking, PoolExt, TcpListenerExt}; use cap_std::net::TcpListener; @@ -16,23 +16,26 @@ use rustix::io::Errno; use rustix::net::sockopt; use std::any::Any; use tokio::io::Interest; +use wasmtime::component::Resource; -impl tcp::Host for T { +impl tcp::Host for T {} + +impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { fn start_bind( &mut self, - this: tcp::TcpSocket, - network: Network, + this: Resource, + network: Resource, local_address: IpSocketAddress, ) -> Result<(), network::Error> { let table = self.table_mut(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; match socket.tcp_state { HostTcpState::Default => {} _ => return Err(ErrorCode::NotInProgress.into()), } - let network = table.get_network(network)?; + let network = table.get_network(&network)?; let binder = network.0.tcp_binder(local_address)?; // Perform the OS bind call. @@ -40,15 +43,15 @@ impl tcp::Host for T { &*socket.tcp_socket().as_socketlike_view::(), )?; - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; socket.tcp_state = HostTcpState::BindStarted; Ok(()) } - fn finish_bind(&mut self, this: tcp::TcpSocket) -> Result<(), network::Error> { + fn finish_bind(&mut self, this: Resource) -> Result<(), network::Error> { let table = self.table_mut(); - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; match socket.tcp_state { HostTcpState::BindStarted => {} @@ -62,13 +65,13 @@ impl tcp::Host for T { fn start_connect( &mut self, - this: tcp::TcpSocket, - network: Network, + this: Resource, + network: Resource, remote_address: IpSocketAddress, ) -> Result<(), network::Error> { let table = self.table_mut(); let r = { - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; match socket.tcp_state { HostTcpState::Default => {} @@ -76,7 +79,7 @@ impl tcp::Host for T { _ => return Err(ErrorCode::NotInProgress.into()), } - let network = table.get_network(network)?; + let network = table.get_network(&network)?; let connecter = network.0.tcp_connecter(remote_address)?; // Do an OS `connect`. Our socket is non-blocking, so it'll either... @@ -90,7 +93,7 @@ impl tcp::Host for T { match r { // succeed immediately, Ok(()) => { - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; socket.tcp_state = HostTcpState::ConnectReady; return Ok(()); } @@ -100,7 +103,7 @@ impl tcp::Host for T { Err(err) => return Err(err.into()), } - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; socket.tcp_state = HostTcpState::Connecting; Ok(()) @@ -108,10 +111,10 @@ impl tcp::Host for T { fn finish_connect( &mut self, - this: tcp::TcpSocket, - ) -> Result<(InputStream, OutputStream), network::Error> { + this: Resource, + ) -> Result<(Resource, Resource), network::Error> { let table = self.table_mut(); - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; match socket.tcp_state { HostTcpState::ConnectReady => {} @@ -141,15 +144,19 @@ impl tcp::Host for T { socket.tcp_state = HostTcpState::Connected; let (input, output) = socket.as_split(); - let input_stream = self.table_mut().push_input_stream_child(input, this)?; - let output_stream = self.table_mut().push_output_stream_child(output, this)?; + let input_stream = self + .table_mut() + .push_input_stream_child(input, Resource::::new_borrow(this.rep()))?; + let output_stream = self + .table_mut() + .push_output_stream_child(output, Resource::::new_borrow(this.rep()))?; Ok((input_stream, output_stream)) } - fn start_listen(&mut self, this: tcp::TcpSocket) -> Result<(), network::Error> { + fn start_listen(&mut self, this: Resource) -> Result<(), network::Error> { let table = self.table_mut(); - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; match socket.tcp_state { HostTcpState::Bound => {} @@ -168,9 +175,9 @@ impl tcp::Host for T { Ok(()) } - fn finish_listen(&mut self, this: tcp::TcpSocket) -> Result<(), network::Error> { + fn finish_listen(&mut self, this: Resource) -> Result<(), network::Error> { let table = self.table_mut(); - let socket = table.get_tcp_socket_mut(this)?; + let socket = table.get_tcp_socket_mut(&this)?; match socket.tcp_state { HostTcpState::ListenStarted => {} @@ -184,10 +191,17 @@ impl tcp::Host for T { fn accept( &mut self, - this: tcp::TcpSocket, - ) -> Result<(tcp::TcpSocket, InputStream, OutputStream), network::Error> { + this: Resource, + ) -> Result< + ( + Resource, + Resource, + Resource, + ), + network::Error, + > { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; match socket.tcp_state { HostTcpState::Listening => {} @@ -202,7 +216,7 @@ impl tcp::Host for T { .as_socketlike_view::() .accept_with(Blocking::No) })?; - let mut tcp_socket = HostTcpSocket::from_tcp_stream(connection)?; + let mut tcp_socket = HostTcpSocketState::from_tcp_stream(connection)?; // Mark the socket as connected so that we can exit early from methods like `start-bind`. tcp_socket.tcp_state = HostTcpState::Connected; @@ -210,19 +224,24 @@ impl tcp::Host for T { let (input, output) = tcp_socket.as_split(); let tcp_socket = self.table_mut().push_tcp_socket(tcp_socket)?; - let input_stream = self - .table_mut() - .push_input_stream_child(input, tcp_socket)?; - let output_stream = self - .table_mut() - .push_output_stream_child(output, tcp_socket)?; + let input_stream = self.table_mut().push_input_stream_child( + input, + Resource::::new_borrow(tcp_socket.rep()), + )?; + let output_stream = self.table_mut().push_output_stream_child( + output, + Resource::::new_borrow(tcp_socket.rep()), + )?; Ok((tcp_socket, input_stream, output_stream)) } - fn local_address(&mut self, this: tcp::TcpSocket) -> Result { + fn local_address( + &mut self, + this: Resource, + ) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; let addr = socket .tcp_socket() .as_socketlike_view::() @@ -230,9 +249,12 @@ impl tcp::Host for T { Ok(addr.into()) } - fn remote_address(&mut self, this: tcp::TcpSocket) -> Result { + fn remote_address( + &mut self, + this: Resource, + ) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; let addr = socket .tcp_socket() .as_socketlike_view::() @@ -240,9 +262,12 @@ impl tcp::Host for T { Ok(addr.into()) } - fn address_family(&mut self, this: tcp::TcpSocket) -> Result { + fn address_family( + &mut self, + this: Resource, + ) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; // If `SO_DOMAIN` is available, use it. // @@ -285,25 +310,29 @@ impl tcp::Host for T { } } - fn ipv6_only(&mut self, this: tcp::TcpSocket) -> Result { + fn ipv6_only(&mut self, this: Resource) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::get_ipv6_v6only(socket.tcp_socket())?) } - fn set_ipv6_only(&mut self, this: tcp::TcpSocket, value: bool) -> Result<(), network::Error> { + fn set_ipv6_only( + &mut self, + this: Resource, + value: bool, + ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::set_ipv6_v6only(socket.tcp_socket(), value)?) } fn set_listen_backlog_size( &mut self, - this: tcp::TcpSocket, + this: Resource, value: u64, ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; match socket.tcp_state { HostTcpState::Listening => {} @@ -314,33 +343,41 @@ impl tcp::Host for T { Ok(rustix::net::listen(socket.tcp_socket(), value)?) } - fn keep_alive(&mut self, this: tcp::TcpSocket) -> Result { + fn keep_alive(&mut self, this: Resource) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::get_socket_keepalive(socket.tcp_socket())?) } - fn set_keep_alive(&mut self, this: tcp::TcpSocket, value: bool) -> Result<(), network::Error> { + fn set_keep_alive( + &mut self, + this: Resource, + value: bool, + ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::set_socket_keepalive(socket.tcp_socket(), value)?) } - fn no_delay(&mut self, this: tcp::TcpSocket) -> Result { + fn no_delay(&mut self, this: Resource) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::get_tcp_nodelay(socket.tcp_socket())?) } - fn set_no_delay(&mut self, this: tcp::TcpSocket, value: bool) -> Result<(), network::Error> { + fn set_no_delay( + &mut self, + this: Resource, + value: bool, + ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::set_tcp_nodelay(socket.tcp_socket(), value)?) } - fn unicast_hop_limit(&mut self, this: tcp::TcpSocket) -> Result { + fn unicast_hop_limit(&mut self, this: Resource) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; // We don't track whether the socket is IPv4 or IPv6 so try one and // fall back to the other. @@ -357,11 +394,11 @@ impl tcp::Host for T { fn set_unicast_hop_limit( &mut self, - this: tcp::TcpSocket, + this: Resource, value: u8, ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; // We don't track whether the socket is IPv4 or IPv6 so try one and // fall back to the other. @@ -372,19 +409,22 @@ impl tcp::Host for T { } } - fn receive_buffer_size(&mut self, this: tcp::TcpSocket) -> Result { + fn receive_buffer_size( + &mut self, + this: Resource, + ) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::get_socket_recv_buffer_size(socket.tcp_socket())? as u64) } fn set_receive_buffer_size( &mut self, - this: tcp::TcpSocket, + this: Resource, value: u64, ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; let value = value.try_into().map_err(|_| ErrorCode::OutOfMemory)?; Ok(sockopt::set_socket_recv_buffer_size( socket.tcp_socket(), @@ -392,19 +432,19 @@ impl tcp::Host for T { )?) } - fn send_buffer_size(&mut self, this: tcp::TcpSocket) -> Result { + fn send_buffer_size(&mut self, this: Resource) -> Result { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; Ok(sockopt::get_socket_send_buffer_size(socket.tcp_socket())? as u64) } fn set_send_buffer_size( &mut self, - this: tcp::TcpSocket, + this: Resource, value: u64, ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; let value = value.try_into().map_err(|_| ErrorCode::OutOfMemory)?; Ok(sockopt::set_socket_send_buffer_size( socket.tcp_socket(), @@ -412,11 +452,11 @@ impl tcp::Host for T { )?) } - fn subscribe(&mut self, this: tcp::TcpSocket) -> anyhow::Result { + fn subscribe(&mut self, this: Resource) -> anyhow::Result> { fn make_tcp_socket_future<'a>(stream: &'a mut dyn Any) -> PollableFuture<'a> { let socket = stream - .downcast_mut::() - .expect("downcast to HostTcpSocket failed"); + .downcast_mut::() + .expect("downcast to HostTcpSocketState failed"); // Some states are ready immediately. match socket.tcp_state { @@ -440,7 +480,7 @@ impl tcp::Host for T { } let pollable = HostPollable::TableEntry { - index: this, + index: this.rep(), make_future: make_tcp_socket_future, }; @@ -449,11 +489,11 @@ impl tcp::Host for T { fn shutdown( &mut self, - this: tcp::TcpSocket, + this: Resource, shutdown_type: ShutdownType, ) -> Result<(), network::Error> { let table = self.table(); - let socket = table.get_tcp_socket(this)?; + let socket = table.get_tcp_socket(&this)?; let how = match shutdown_type { ShutdownType::Receive => std::net::Shutdown::Read, @@ -468,7 +508,7 @@ impl tcp::Host for T { Ok(()) } - fn drop_tcp_socket(&mut self, this: tcp::TcpSocket) -> Result<(), anyhow::Error> { + fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { let table = self.table_mut(); // As in the filesystem implementation, we assume closing a socket diff --git a/crates/wasi/src/preview2/host/tcp_create_socket.rs b/crates/wasi/src/preview2/host/tcp_create_socket.rs index d2559e7df7d5..c3f349177a8e 100644 --- a/crates/wasi/src/preview2/host/tcp_create_socket.rs +++ b/crates/wasi/src/preview2/host/tcp_create_socket.rs @@ -3,15 +3,16 @@ use crate::preview2::bindings::{ sockets::tcp::TcpSocket, sockets::tcp_create_socket, }; -use crate::preview2::tcp::{HostTcpSocket, TableTcpSocketExt}; +use crate::preview2::tcp::{HostTcpSocketState, TableTcpSocketExt}; use crate::preview2::WasiView; +use wasmtime::component::Resource; impl tcp_create_socket::Host for T { fn create_tcp_socket( &mut self, address_family: IpAddressFamily, - ) -> Result { - let socket = HostTcpSocket::new(address_family.into())?; + ) -> Result, network::Error> { + let socket = HostTcpSocketState::new(address_family.into())?; let socket = self.table_mut().push_tcp_socket(socket)?; Ok(socket) } diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs index 6d9ad182dcec..5d1f02e55487 100644 --- a/crates/wasi/src/preview2/mod.rs +++ b/crates/wasi/src/preview2/mod.rs @@ -58,7 +58,7 @@ pub mod bindings { wasmtime::component::bindgen!({ path: "wit", interfaces: " - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:filesystem/types ", @@ -69,10 +69,15 @@ pub mod bindings { }, with: { "wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock, + "wasi:filesystem/types/descriptor": super::super::filesystem::types::Descriptor, + "wasi:filesystem/types/directory-entry-stream": super::super::filesystem::types::DirectoryEntryStream, + "wasi:io/poll/pollable": super::super::io::poll::Pollable, + "wasi:io/streams/input-stream": super::super::io::streams::InputStream, + "wasi:io/streams/output-stream": super::super::io::streams::OutputStream, } }); } - pub use self::_internal::wasi::{filesystem, io, poll}; + pub use self::_internal::wasi::{filesystem, io}; } wasmtime::component::bindgen!({ @@ -88,49 +93,52 @@ pub mod bindings { // which in theory can be shared across interfaces, so this may // need fancier syntax in the future. only_imports: [ - "access-at", - "advise", - "blocking-flush", - "blocking-read", - "blocking-skip", - "blocking-splice", - "blocking-write", - "blocking-write-and-flush", - "change-directory-permissions-at", - "change-file-permissions-at", - "create-directory-at", - "forward", - "get-flags", - "get-type", - "is-same-object", - "link-at", - "lock-exclusive", - "lock-shared", - "metadata-hash", - "metadata-hash-at", - "open-at", - "poll-oneoff", - "read", - "read-directory", - "read-directory-entry", - "readlink-at", - "remove-directory-at", - "rename-at", - "set-size", - "set-times", - "set-times-at", - "skip", - "splice", - "stat", - "stat-at", - "symlink-at", - "sync", - "sync-data", - "try-lock-exclusive", - "try-lock-shared", - "unlink-file-at", - "unlock", - "write", + "[method]descriptor.access-at", + "[method]descriptor.advise", + "[method]descriptor.change-directory-permissions-at", + "[method]descriptor.change-file-permissions-at", + "[method]descriptor.create-directory-at", + "[method]descriptor.get-flags", + "[method]descriptor.get-type", + "[method]descriptor.is-same-object", + "[method]descriptor.link-at", + "[method]descriptor.lock-exclusive", + "[method]descriptor.lock-shared", + "[method]descriptor.metadata-hash", + "[method]descriptor.metadata-hash-at", + "[method]descriptor.open-at", + "[method]descriptor.read", + "[method]descriptor.read-directory", + "[method]descriptor.readlink-at", + "[method]descriptor.remove-directory-at", + "[method]descriptor.rename-at", + "[method]descriptor.set-size", + "[method]descriptor.set-times", + "[method]descriptor.set-times-at", + "[method]descriptor.stat", + "[method]descriptor.stat-at", + "[method]descriptor.symlink-at", + "[method]descriptor.sync", + "[method]descriptor.sync-data", + "[method]descriptor.try-lock-exclusive", + "[method]descriptor.try-lock-shared", + "[method]descriptor.unlink-file-at", + "[method]descriptor.unlock", + "[method]descriptor.write", + "[method]input-stream.read", + "[method]input-stream.blocking-read", + "[method]input-stream.blocking-skip", + "[method]input-stream.skip", + "[method]output-stream.forward", + "[method]output-stream.splice", + "[method]output-stream.blocking-splice", + "[method]output-stream.blocking-flush", + "[method]output-stream.blocking-write", + "[method]output-stream.blocking-write-and-flush", + "[method]output-stream.blocking-write-zeroes-and-flush", + "[method]directory-entry-stream.read-directory-entry", + "poll-list", + "poll-one", ], }, trappable_error_type: { diff --git a/crates/wasi/src/preview2/network.rs b/crates/wasi/src/preview2/network.rs index 4d462fcbd275..64c46f461044 100644 --- a/crates/wasi/src/preview2/network.rs +++ b/crates/wasi/src/preview2/network.rs @@ -1,32 +1,34 @@ +use crate::preview2::bindings::sockets::network::Network; use crate::preview2::{Table, TableError}; use cap_std::net::Pool; +use wasmtime::component::Resource; -pub(crate) struct HostNetwork(pub(crate) Pool); +pub(crate) struct HostNetworkState(pub(crate) Pool); -impl HostNetwork { +impl HostNetworkState { pub fn new(pool: Pool) -> Self { Self(pool) } } pub(crate) trait TableNetworkExt { - fn push_network(&mut self, network: HostNetwork) -> Result; - fn delete_network(&mut self, fd: u32) -> Result; - fn is_network(&self, fd: u32) -> bool; - fn get_network(&self, fd: u32) -> Result<&HostNetwork, TableError>; + fn push_network(&mut self, network: HostNetworkState) -> Result, TableError>; + fn delete_network(&mut self, fd: Resource) -> Result; + fn is_network(&self, fd: &Resource) -> bool; + fn get_network(&self, fd: &Resource) -> Result<&HostNetworkState, TableError>; } impl TableNetworkExt for Table { - fn push_network(&mut self, network: HostNetwork) -> Result { - self.push(Box::new(network)) + fn push_network(&mut self, network: HostNetworkState) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(network))?)) } - fn delete_network(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_network(&mut self, fd: Resource) -> Result { + self.delete(fd.rep()) } - fn is_network(&self, fd: u32) -> bool { - self.is::(fd) + fn is_network(&self, fd: &Resource) -> bool { + self.is::(fd.rep()) } - fn get_network(&self, fd: u32) -> Result<&HostNetwork, TableError> { - self.get(fd) + fn get_network(&self, fd: &Resource) -> Result<&HostNetworkState, TableError> { + self.get(fd.rep()) } } diff --git a/crates/wasi/src/preview2/pipe.rs b/crates/wasi/src/preview2/pipe.rs index cfde46d8c127..c261760d7d93 100644 --- a/crates/wasi/src/preview2/pipe.rs +++ b/crates/wasi/src/preview2/pipe.rs @@ -10,45 +10,46 @@ use crate::preview2::{HostInputStream, HostOutputStream, OutputStreamError, StreamState}; use anyhow::{anyhow, Error}; use bytes::Bytes; +use std::sync::{Arc, Mutex}; use tokio::sync::mpsc; pub use crate::preview2::write_stream::AsyncWriteStream; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MemoryInputPipe { - buffer: std::io::Cursor, + buffer: Arc>, } impl MemoryInputPipe { pub fn new(bytes: Bytes) -> Self { Self { - buffer: std::io::Cursor::new(bytes), + buffer: Arc::new(Mutex::new(bytes)), } } pub fn is_empty(&self) -> bool { - self.buffer.get_ref().len() as u64 == self.buffer.position() + self.buffer.lock().unwrap().is_empty() } } #[async_trait::async_trait] impl HostInputStream for MemoryInputPipe { fn read(&mut self, size: usize) -> Result<(Bytes, StreamState), Error> { - if self.is_empty() { + let mut buffer = self.buffer.lock().unwrap(); + if buffer.is_empty() { return Ok((Bytes::new(), StreamState::Closed)); } - let mut dest = bytes::BytesMut::zeroed(size); - let nbytes = std::io::Read::read(&mut self.buffer, dest.as_mut())?; - dest.truncate(nbytes); - - let state = if self.is_empty() { + let size = size.min(buffer.len()); + let read = buffer.split_to(size); + let state = if buffer.is_empty() { StreamState::Closed } else { StreamState::Open }; - Ok((dest.freeze(), state)) + Ok((read, state)) } + async fn ready(&mut self) -> Result<(), Error> { Ok(()) } @@ -57,7 +58,7 @@ impl HostInputStream for MemoryInputPipe { #[derive(Debug, Clone)] pub struct MemoryOutputPipe { capacity: usize, - buffer: std::sync::Arc>, + buffer: Arc>, } impl MemoryOutputPipe { @@ -212,6 +213,7 @@ impl HostInputStream for AsyncReadStream { } /// An output stream that consumes all input written to it, and is always ready. +#[derive(Copy, Clone)] pub struct SinkOutputStream; #[async_trait::async_trait] @@ -231,6 +233,7 @@ impl HostOutputStream for SinkOutputStream { } /// A stream that is ready immediately, but will always report that it's closed. +#[derive(Copy, Clone)] pub struct ClosedInputStream; #[async_trait::async_trait] @@ -245,6 +248,7 @@ impl HostInputStream for ClosedInputStream { } /// An output stream that is always closed. +#[derive(Copy, Clone)] pub struct ClosedOutputStream; #[async_trait::async_trait] diff --git a/crates/wasi/src/preview2/poll.rs b/crates/wasi/src/preview2/poll.rs index 0a947fef3dec..8bce303639f1 100644 --- a/crates/wasi/src/preview2/poll.rs +++ b/crates/wasi/src/preview2/poll.rs @@ -1,5 +1,5 @@ use crate::preview2::{ - bindings::poll::poll::{self, Pollable}, + bindings::io::poll::{self, Pollable}, Table, TableError, WasiView, }; use anyhow::Result; @@ -8,17 +8,18 @@ use std::collections::{hash_map::Entry, HashMap}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +use wasmtime::component::Resource; pub type PollableFuture<'a> = Pin> + Send + 'a>>; pub type MakeFuture = for<'a> fn(&'a mut dyn Any) -> PollableFuture<'a>; pub type ClosureFuture = Box PollableFuture<'static> + Send + Sync + 'static>; -/// A host representation of the `wasi:poll/poll.pollable` resource. +/// A host representation of the `wasi:io/poll.pollable` resource. /// /// A pollable is not the same thing as a Rust Future: the same pollable may be used to /// repeatedly check for readiness of a given condition, e.g. if a stream is readable /// or writable. So, rather than containing a Future, which can only become Ready once, a -/// HostPollable contains a way to create a Future in each call to poll_oneoff. +/// HostPollable contains a way to create a Future in each call to `poll_list`. pub enum HostPollable { /// Create a Future by calling a fn on another resource in the table. This /// indirection means the created Future can use a mut borrow of another @@ -31,35 +32,36 @@ pub enum HostPollable { } pub trait TablePollableExt { - fn push_host_pollable(&mut self, p: HostPollable) -> Result; - fn get_host_pollable_mut(&mut self, fd: u32) -> Result<&mut HostPollable, TableError>; - fn delete_host_pollable(&mut self, fd: u32) -> Result; + fn push_host_pollable(&mut self, p: HostPollable) -> Result, TableError>; + fn get_host_pollable_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut HostPollable, TableError>; + fn delete_host_pollable(&mut self, fd: Resource) -> Result; } impl TablePollableExt for Table { - fn push_host_pollable(&mut self, p: HostPollable) -> Result { - match p { - HostPollable::TableEntry { index, .. } => self.push_child(Box::new(p), index), - HostPollable::Closure { .. } => self.push(Box::new(p)), - } + fn push_host_pollable(&mut self, p: HostPollable) -> Result, TableError> { + Ok(Resource::new_own(match p { + HostPollable::TableEntry { index, .. } => self.push_child(Box::new(p), index)?, + HostPollable::Closure { .. } => self.push(Box::new(p))?, + })) } - fn get_host_pollable_mut(&mut self, fd: u32) -> Result<&mut HostPollable, TableError> { - self.get_mut::(fd) + fn get_host_pollable_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut HostPollable, TableError> { + self.get_mut::(fd.rep()) } - fn delete_host_pollable(&mut self, fd: u32) -> Result { - self.delete::(fd) + fn delete_host_pollable(&mut self, fd: Resource) -> Result { + self.delete::(fd.rep()) } } #[async_trait::async_trait] impl poll::Host for T { - fn drop_pollable(&mut self, pollable: Pollable) -> Result<()> { - self.table_mut().delete_host_pollable(pollable)?; - Ok(()) - } - - async fn poll_oneoff(&mut self, pollables: Vec) -> Result> { - type ReadylistIndex = usize; + async fn poll_list(&mut self, pollables: Vec>) -> Result> { + type ReadylistIndex = u32; let table = self.table_mut(); @@ -67,7 +69,8 @@ impl poll::Host for T { let mut closure_futures: Vec<(PollableFuture<'_>, Vec)> = Vec::new(); for (ix, p) in pollables.iter().enumerate() { - match table.get_host_pollable_mut(*p)? { + let ix: u32 = ix.try_into()?; + match table.get_host_pollable_mut(&p)? { HostPollable::Closure(f) => closure_futures.push((f(), vec![ix])), HostPollable::TableEntry { index, make_future } => { match table_futures.entry(*index) { @@ -88,26 +91,24 @@ impl poll::Host for T { closure_futures.push((make_future(entry), readylist_indices)); } - struct PollOneoff<'a> { + struct PollList<'a> { elems: Vec<(PollableFuture<'a>, Vec)>, } - impl<'a> Future for PollOneoff<'a> { - type Output = Result>; + impl<'a> Future for PollList<'a> { + type Output = Result>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut any_ready = false; - let mut results = vec![false; self.elems.len()]; + let mut results = Vec::new(); for (fut, readylist_indicies) in self.elems.iter_mut() { match fut.as_mut().poll(cx) { Poll::Ready(Ok(())) => { - for r in readylist_indicies { - results[*r] = true; - } + results.extend_from_slice(readylist_indicies); any_ready = true; } Poll::Ready(Err(e)) => { return Poll::Ready(Err( - e.context(format!("poll_oneoff {readylist_indicies:?}")) + e.context(format!("poll_list {readylist_indicies:?}")) )); } Poll::Pending => {} @@ -121,28 +122,60 @@ impl poll::Host for T { } } - Ok(PollOneoff { + Ok(PollList { elems: closure_futures, } .await?) } + + async fn poll_one(&mut self, pollable: Resource) -> Result<()> { + use anyhow::Context; + + let table = self.table_mut(); + + let closure_future = match table.get_host_pollable_mut(&pollable)? { + HostPollable::Closure(f) => f(), + HostPollable::TableEntry { index, make_future } => { + let index = *index; + let make_future = *make_future; + make_future(table.get_as_any_mut(index)?) + } + }; + + closure_future.await.context("poll_one") + } +} + +#[async_trait::async_trait] +impl crate::preview2::bindings::io::poll::HostPollable for T { + fn drop(&mut self, pollable: Resource) -> Result<()> { + self.table_mut().delete_host_pollable(pollable)?; + Ok(()) + } } pub mod sync { use crate::preview2::{ - bindings::poll::poll::Host as AsyncHost, - bindings::sync_io::poll::poll::{self, Pollable}, + bindings::io::poll::{Host as AsyncHost, HostPollable as AsyncHostPollable}, + bindings::sync_io::io::poll::{self, Pollable}, in_tokio, WasiView, }; use anyhow::Result; + use wasmtime::component::Resource; impl poll::Host for T { - fn drop_pollable(&mut self, pollable: Pollable) -> Result<()> { - AsyncHost::drop_pollable(self, pollable) + fn poll_list(&mut self, pollables: Vec>) -> Result> { + in_tokio(async { AsyncHost::poll_list(self, pollables).await }) } - fn poll_oneoff(&mut self, pollables: Vec) -> Result> { - in_tokio(async { AsyncHost::poll_oneoff(self, pollables).await }) + fn poll_one(&mut self, pollable: Resource) -> Result<()> { + in_tokio(async { AsyncHost::poll_one(self, pollable).await }) + } + } + + impl crate::preview2::bindings::sync_io::io::poll::HostPollable for T { + fn drop(&mut self, pollable: Resource) -> Result<()> { + AsyncHostPollable::drop(self, pollable) } } } diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs index a44330b25b11..4a432704de0e 100644 --- a/crates/wasi/src/preview2/preview1.rs +++ b/crates/wasi/src/preview2/preview1.rs @@ -4,8 +4,8 @@ use crate::preview2::bindings::cli::{ }; use crate::preview2::bindings::clocks::{monotonic_clock, wall_clock}; use crate::preview2::bindings::filesystem::{preopens, types as filesystem}; +use crate::preview2::bindings::io::poll; use crate::preview2::bindings::io::streams; -use crate::preview2::bindings::poll; use crate::preview2::filesystem::TableFsExt; use crate::preview2::host::filesystem::TableReaddirExt; use crate::preview2::{bindings, IsATTY, TableError, WasiView}; @@ -18,13 +18,14 @@ use std::ops::{Deref, DerefMut}; use std::slice; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use wasmtime::component::Resource; use wiggle::tracing::instrument; use wiggle::{GuestError, GuestPtr, GuestSlice, GuestSliceMut, GuestStrCow, GuestType}; #[derive(Clone, Debug)] struct File { /// The handle to the preview2 descriptor that this file is referencing. - fd: filesystem::Descriptor, + fd: u32, /// The current-position pointer. position: Arc, @@ -54,26 +55,26 @@ impl BlockingMode { async fn read( &self, host: &mut impl streams::Host, - input_stream: streams::InputStream, + input_stream: Resource, max_size: usize, ) -> Result<(Vec, streams::StreamStatus), types::Error> { let max_size = max_size.try_into().unwrap_or(u64::MAX); match self { - BlockingMode::Blocking => { - stream_res(streams::Host::blocking_read(host, input_stream, max_size).await) - } + BlockingMode::Blocking => stream_res( + streams::HostInputStream::blocking_read(host, input_stream, max_size).await, + ), BlockingMode::NonBlocking => { - stream_res(streams::Host::read(host, input_stream, max_size).await) + stream_res(streams::HostInputStream::read(host, input_stream, max_size).await) } } } async fn write( &self, - host: &mut (impl streams::Host + poll::poll::Host), - output_stream: streams::OutputStream, + host: &mut (impl streams::Host + poll::Host), + output_stream: Resource, mut bytes: &[u8], ) -> Result { - use streams::Host as Streams; + use streams::HostOutputStream as Streams; match self { BlockingMode::Blocking => { @@ -84,13 +85,15 @@ impl BlockingMode { let (chunk, rest) = bytes.split_at(len); bytes = rest; - Streams::blocking_write_and_flush(host, output_stream, Vec::from(chunk)).await? + let borrow = Resource::new_borrow(output_stream.rep()); + Streams::blocking_write_and_flush(host, borrow, Vec::from(chunk)).await? } Ok(total) } BlockingMode::NonBlocking => { - let n = match Streams::check_write(host, output_stream) { + let borrow = Resource::new_borrow(output_stream.rep()); + let n = match Streams::check_write(host, borrow) { Ok(n) => n, Err(e) if matches!(e.downcast_ref(), Some(streams::WriteError::Closed)) => 0, Err(e) => Err(e)?, @@ -101,7 +104,8 @@ impl BlockingMode { return Ok(0); } - match Streams::write(host, output_stream, bytes[..len].to_vec()).await { + let borrow = Resource::new_borrow(output_stream.rep()); + match Streams::write(host, borrow, bytes[..len].to_vec()) { Ok(()) => {} Err(e) if matches!(e.downcast_ref(), Some(streams::WriteError::Closed)) => { return Ok(0) @@ -109,7 +113,8 @@ impl BlockingMode { Err(e) => Err(e)?, } - match Streams::blocking_flush(host, output_stream).await { + let borrow = Resource::new_borrow(output_stream.rep()); + match Streams::blocking_flush(host, borrow).await { Ok(()) => {} Err(e) if matches!(e.downcast_ref(), Some(streams::WriteError::Closed)) => { return Ok(0) @@ -125,19 +130,10 @@ impl BlockingMode { #[derive(Clone, Debug)] enum Descriptor { - Stdin { - input_stream: streams::InputStream, - isatty: IsATTY, - }, - Stdout { - output_stream: streams::OutputStream, - isatty: IsATTY, - }, - Stderr { - output_stream: streams::OutputStream, - isatty: IsATTY, - }, - PreopenDirectory((filesystem::Descriptor, String)), + Stdin { input_stream: u32, isatty: IsATTY }, + Stdout { output_stream: u32, isatty: IsATTY }, + Stderr { output_stream: u32, isatty: IsATTY }, + PreopenDirectory((u32, String)), File(File), } @@ -185,13 +181,14 @@ impl Descriptors { input_stream: host .get_stdin() .context("failed to call `get-stdin`") - .map_err(types::Error::trap)?, + .map_err(types::Error::trap)? + .rep(), isatty: if let Some(term_in) = host .get_terminal_stdin() .context("failed to call `get-terminal-stdin`") .map_err(types::Error::trap)? { - host.drop_terminal_input(term_in) + terminal_input::HostTerminalInput::drop(host, term_in) .context("failed to call `drop-terminal-input`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -203,13 +200,14 @@ impl Descriptors { output_stream: host .get_stdout() .context("failed to call `get-stdout`") - .map_err(types::Error::trap)?, + .map_err(types::Error::trap)? + .rep(), isatty: if let Some(term_out) = host .get_terminal_stdout() .context("failed to call `get-terminal-stdout`") .map_err(types::Error::trap)? { - host.drop_terminal_output(term_out) + terminal_output::HostTerminalOutput::drop(host, term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -221,13 +219,14 @@ impl Descriptors { output_stream: host .get_stderr() .context("failed to call `get-stderr`") - .map_err(types::Error::trap)?, + .map_err(types::Error::trap)? + .rep(), isatty: if let Some(term_out) = host .get_terminal_stderr() .context("failed to call `get-terminal-stderr`") .map_err(types::Error::trap)? { - host.drop_terminal_output(term_out) + terminal_output::HostTerminalOutput::drop(host, term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -241,7 +240,7 @@ impl Descriptors { .context("failed to call `get-directories`") .map_err(types::Error::trap)? { - descriptors.push(Descriptor::PreopenDirectory(dir))?; + descriptors.push(Descriptor::PreopenDirectory((dir.0.rep(), dir.1)))?; } Ok(descriptors) } @@ -349,7 +348,9 @@ impl Transaction<'_, T> { fn get_file(&mut self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { - Some(Descriptor::File(file @ File { fd, .. })) if self.view.table().is_file(*fd) => { + Some(Descriptor::File(file @ File { fd, .. })) + if self.view.table().is_file(&Resource::new_borrow(*fd)) => + { Ok(file) } _ => Err(types::Errno::Badf.into()), @@ -361,7 +362,11 @@ impl Transaction<'_, T> { fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> { let fd = fd.into(); match self.descriptors.get_mut().get_mut(&fd) { - Some(Descriptor::File(file)) if self.view.table().is_file(file.fd) => Ok(file), + Some(Descriptor::File(file)) + if self.view.table().is_file(&Resource::new_borrow(file.fd)) => + { + Ok(file) + } _ => Err(types::Errno::Badf.into()), } } @@ -375,7 +380,9 @@ impl Transaction<'_, T> { fn get_seekable(&mut self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { - Some(Descriptor::File(file @ File { fd, .. })) if self.view.table().is_file(*fd) => { + Some(Descriptor::File(file @ File { fd, .. })) + if self.view.table().is_file(&Resource::new_borrow(*fd)) => + { Ok(file) } Some( @@ -389,31 +396,35 @@ impl Transaction<'_, T> { } /// Returns [`filesystem::Descriptor`] corresponding to `fd` - fn get_fd(&mut self, fd: types::Fd) -> Result { + fn get_fd(&mut self, fd: types::Fd) -> Result> { match self.get_descriptor(fd)? { - Descriptor::File(File { fd, .. }) => Ok(*fd), - Descriptor::PreopenDirectory((fd, _)) => Ok(*fd), - Descriptor::Stdin { input_stream, .. } => Ok(*input_stream), - Descriptor::Stdout { output_stream, .. } | Descriptor::Stderr { output_stream, .. } => { - Ok(*output_stream) + Descriptor::File(File { fd, .. }) => Ok(Resource::new_borrow(*fd)), + Descriptor::PreopenDirectory((fd, _)) => Ok(Resource::new_borrow(*fd)), + Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => { + Err(types::Errno::Badf.into()) } } } /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type - fn get_file_fd(&mut self, fd: types::Fd) -> Result { - self.get_file(fd).map(|File { fd, .. }| *fd) + fn get_file_fd(&mut self, fd: types::Fd) -> Result> { + self.get_file(fd) + .map(|File { fd, .. }| Resource::new_borrow(*fd)) } /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`] /// of [`crate::preview2::filesystem::Dir`] type - fn get_dir_fd(&mut self, fd: types::Fd) -> Result { + fn get_dir_fd(&mut self, fd: types::Fd) -> Result> { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { - Some(Descriptor::File(File { fd, .. })) if self.view.table().is_dir(*fd) => Ok(*fd), - Some(Descriptor::PreopenDirectory((fd, _))) => Ok(*fd), + Some(Descriptor::File(File { fd, .. })) + if self.view.table().is_dir(&Resource::new_borrow(*fd)) => + { + Ok(Resource::new_borrow(*fd)) + } + Some(Descriptor::PreopenDirectory((fd, _))) => Ok(Resource::new_borrow(*fd)), _ => Err(types::Errno::Badf.into()), } } @@ -448,7 +459,7 @@ trait WasiPreview1ViewExt: /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` - fn get_fd(&mut self, fd: types::Fd) -> Result { + fn get_fd(&mut self, fd: types::Fd) -> Result, types::Error> { let mut st = self.transact()?; let fd = st.get_fd(fd)?; Ok(fd) @@ -457,7 +468,10 @@ trait WasiPreview1ViewExt: /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type - fn get_file_fd(&mut self, fd: types::Fd) -> Result { + fn get_file_fd( + &mut self, + fd: types::Fd, + ) -> Result, types::Error> { let mut st = self.transact()?; let fd = st.get_file_fd(fd)?; Ok(fd) @@ -467,7 +481,10 @@ trait WasiPreview1ViewExt: /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`] /// of [`crate::preview2::filesystem::Dir`] type - fn get_dir_fd(&mut self, fd: types::Fd) -> Result { + fn get_dir_fd( + &mut self, + fd: types::Fd, + ) -> Result, types::Error> { let mut st = self.transact()?; let fd = st.get_dir_fd(fd)?; Ok(fd) @@ -476,13 +493,13 @@ trait WasiPreview1ViewExt: impl WasiPreview1ViewExt for T {} -pub fn add_to_linker_async( +pub fn add_to_linker_async( linker: &mut wasmtime::Linker, ) -> anyhow::Result<()> { wasi_snapshot_preview1::add_to_linker(linker, |t| t) } -pub fn add_to_linker_sync( +pub fn add_to_linker_sync( linker: &mut wasmtime::Linker, ) -> anyhow::Result<()> { sync::add_wasi_snapshot_preview1_to_linker(linker, |t| t) @@ -849,7 +866,7 @@ impl< + bindings::cli::exit::Host + bindings::filesystem::preopens::Host + bindings::filesystem::types::Host - + bindings::poll::poll::Host + + bindings::io::poll::Host + bindings::random::random::Host + bindings::io::streams::Host + bindings::clocks::monotonic_clock::Host @@ -1018,16 +1035,17 @@ impl< .clone(); match desc { Descriptor::Stdin { input_stream, .. } => { - streams::Host::drop_input_stream(self, input_stream) + streams::HostInputStream::drop(self, Resource::new_own(input_stream)) .context("failed to call `drop-input-stream`") } Descriptor::Stdout { output_stream, .. } | Descriptor::Stderr { output_stream, .. } => { - streams::Host::drop_output_stream(self, output_stream) + streams::HostOutputStream::drop(self, Resource::new_own(output_stream)) .context("failed to call `drop-output-stream`") } - Descriptor::File(File { fd, .. }) | Descriptor::PreopenDirectory((fd, _)) => self - .drop_descriptor(fd) - .context("failed to call `drop-descriptor`"), + Descriptor::File(File { fd, .. }) | Descriptor::PreopenDirectory((fd, _)) => { + filesystem::HostDescriptor::drop(self, Resource::new_own(fd)) + .context("failed to call `drop-descriptor`") + } } .map_err(types::Error::trap) } @@ -1115,13 +1133,16 @@ impl< .. }) => (*fd, *blocking_mode, *append), }; - let flags = self.get_flags(fd).await.map_err(|e| { - e.try_into() - .context("failed to call `get-flags`") - .unwrap_or_else(types::Error::trap) - })?; + let flags = self + .get_flags(Resource::new_borrow(fd)) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `get-flags`") + .unwrap_or_else(types::Error::trap) + })?; let fs_filetype = self - .get_type(fd) + .get_type(Resource::new_borrow(fd)) .await .map_err(|e| { e.try_into() @@ -1225,20 +1246,27 @@ impl< data_access_timestamp, data_modification_timestamp, status_change_timestamp, - } = self.stat(fd).await.map_err(|e| { + } = self.stat(Resource::new_borrow(fd)).await.map_err(|e| { e.try_into() .context("failed to call `stat`") .unwrap_or_else(types::Error::trap) })?; - let metadata_hash = self.metadata_hash(fd).await.map_err(|e| { - e.try_into() - .context("failed to call `metadata_hash`") - .unwrap_or_else(types::Error::trap) - })?; + let metadata_hash = + self.metadata_hash(Resource::new_borrow(fd)) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `metadata_hash`") + .unwrap_or_else(types::Error::trap) + })?; let filetype = type_.try_into().map_err(types::Error::trap)?; - let atim = data_access_timestamp.try_into()?; - let mtim = data_modification_timestamp.try_into()?; - let ctim = status_change_timestamp.try_into()?; + let zero = wall_clock::Datetime { + seconds: 0, + nanoseconds: 0, + }; + let atim = data_access_timestamp.unwrap_or(zero).try_into()?; + let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?; + let ctim = status_change_timestamp.unwrap_or(zero).try_into()?; Ok(types::Filestat { dev: 1, ino: metadata_hash.lower, @@ -1313,17 +1341,19 @@ impl< blocking_mode, position, .. - }) if self.table().is_file(fd) => { + }) if self.table().is_file(&Resource::new_borrow(fd)) => { let Some(buf) = first_non_empty_iovec(iovs)? else { return Ok(0); }; let pos = position.load(Ordering::Relaxed); - let stream = self.read_via_stream(fd, pos).map_err(|e| { - e.try_into() - .context("failed to call `read-via-stream`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .read_via_stream(Resource::new_borrow(fd), pos) + .map_err(|e| { + e.try_into() + .context("failed to call `read-via-stream`") + .unwrap_or_else(types::Error::trap) + })?; let (read, state) = blocking_mode.read(self, stream, buf.len()).await?; let n = read.len().try_into()?; let pos = pos.checked_add(n).ok_or(types::Errno::Overflow)?; @@ -1336,9 +1366,9 @@ impl< return Ok(0); }; let (read, state) = stream_res( - streams::Host::blocking_read( + streams::HostInputStream::blocking_read( self, - input_stream, + Resource::new_borrow(input_stream), buf.len().try_into().unwrap_or(u64::MAX), ) .await, @@ -1372,16 +1402,18 @@ impl< let (mut buf, read, state) = match desc { Descriptor::File(File { fd, blocking_mode, .. - }) if self.table().is_file(fd) => { + }) if self.table().is_file(&Resource::new_borrow(fd)) => { let Some(buf) = first_non_empty_iovec(iovs)? else { return Ok(0); }; - let stream = self.read_via_stream(fd, offset).map_err(|e| { - e.try_into() - .context("failed to call `read-via-stream`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .read_via_stream(Resource::new_borrow(fd), offset) + .map_err(|e| { + e.try_into() + .context("failed to call `read-via-stream`") + .unwrap_or_else(types::Error::trap) + })?; let (read, state) = blocking_mode.read(self, stream, buf.len()).await?; (buf, read, state) } @@ -1418,24 +1450,28 @@ impl< blocking_mode, append, position, - }) if self.table().is_file(fd) => { + }) if self.table().is_file(&Resource::new_borrow(fd)) => { let Some(buf) = first_non_empty_ciovec(ciovs)? else { return Ok(0); }; let (stream, pos) = if append { - let stream = self.append_via_stream(fd).map_err(|e| { - e.try_into() - .context("failed to call `append-via-stream`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .append_via_stream(Resource::new_borrow(fd)) + .map_err(|e| { + e.try_into() + .context("failed to call `append-via-stream`") + .unwrap_or_else(types::Error::trap) + })?; (stream, 0) } else { let position = position.load(Ordering::Relaxed); - let stream = self.write_via_stream(fd, position).map_err(|e| { - e.try_into() - .context("failed to call `write-via-stream`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .write_via_stream(Resource::new_borrow(fd), position) + .map_err(|e| { + e.try_into() + .context("failed to call `write-via-stream`") + .unwrap_or_else(types::Error::trap) + })?; (stream, position) }; let n = blocking_mode.write(self, stream, &buf).await?; @@ -1450,7 +1486,7 @@ impl< return Ok(0); }; Ok(BlockingMode::Blocking - .write(self, output_stream, &buf) + .write(self, Resource::new_borrow(output_stream), &buf) .await? .try_into()?) } @@ -1471,15 +1507,17 @@ impl< let n = match desc { Descriptor::File(File { fd, blocking_mode, .. - }) if self.table().is_file(fd) => { + }) if self.table().is_file(&Resource::new_borrow(fd)) => { let Some(buf) = first_non_empty_ciovec(ciovs)? else { return Ok(0); }; - let stream = self.write_via_stream(fd, offset).map_err(|e| { - e.try_into() - .context("failed to call `write-via-stream`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .write_via_stream(Resource::new_borrow(fd), offset) + .map_err(|e| { + e.try_into() + .context("failed to call `write-via-stream`") + .unwrap_or_else(types::Error::trap) + })?; blocking_mode.write(self, stream, &buf).await? } Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => { @@ -1551,11 +1589,12 @@ impl< .checked_add_signed(offset) .ok_or(types::Errno::Inval)?, types::Whence::End => { - let filesystem::DescriptorStat { size, .. } = self.stat(fd).await.map_err(|e| { - e.try_into() - .context("failed to call `stat`") - .unwrap_or_else(types::Error::trap) - })?; + let filesystem::DescriptorStat { size, .. } = + self.stat(Resource::new_borrow(fd)).await.map_err(|e| { + e.try_into() + .context("failed to call `stat`") + .unwrap_or_else(types::Error::trap) + })?; size.checked_add_signed(offset).ok_or(types::Errno::Inval)? } _ => return Err(types::Errno::Inval.into()), @@ -1596,16 +1635,22 @@ impl< cookie: types::Dircookie, ) -> Result { let fd = self.get_dir_fd(fd)?; - let stream = self.read_directory(fd).await.map_err(|e| { - e.try_into() - .context("failed to call `read-directory`") - .unwrap_or_else(types::Error::trap) - })?; - let dir_metadata_hash = self.metadata_hash(fd).await.map_err(|e| { - e.try_into() - .context("failed to call `metadata-hash`") - .unwrap_or_else(types::Error::trap) - })?; + let stream = self + .read_directory(Resource::new_borrow(fd.rep())) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `read-directory`") + .unwrap_or_else(types::Error::trap) + })?; + let dir_metadata_hash = self + .metadata_hash(Resource::new_borrow(fd.rep())) + .await + .map_err(|e| { + e.try_into() + .context("failed to call `metadata-hash`") + .unwrap_or_else(types::Error::trap) + })?; let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?; let head = [ @@ -1643,7 +1688,11 @@ impl< .unwrap_or_else(types::Error::trap) })?; let metadata_hash = self - .metadata_hash_at(fd, filesystem::PathFlags::empty(), name.clone()) + .metadata_hash_at( + Resource::new_borrow(fd.rep()), + filesystem::PathFlags::empty(), + name.clone(), + ) .await .map_err(|e| { e.try_into() @@ -1709,7 +1758,8 @@ impl< ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(path)?; - self.create_directory_at(dirfd, path).await.map_err(|e| { + let borrow = Resource::new_borrow(dirfd.rep()); + self.create_directory_at(borrow, path).await.map_err(|e| { e.try_into() .context("failed to call `create-directory-at`") .unwrap_or_else(types::Error::trap) @@ -1735,7 +1785,11 @@ impl< data_modification_timestamp, status_change_timestamp, } = self - .stat_at(dirfd, flags.into(), path.clone()) + .stat_at( + Resource::new_borrow(dirfd.rep()), + flags.into(), + path.clone(), + ) .await .map_err(|e| { e.try_into() @@ -1751,9 +1805,13 @@ impl< .unwrap_or_else(types::Error::trap) })?; let filetype = type_.try_into().map_err(types::Error::trap)?; - let atim = data_access_timestamp.try_into()?; - let mtim = data_modification_timestamp.try_into()?; - let ctim = status_change_timestamp.try_into()?; + let zero = wall_clock::Datetime { + seconds: 0, + nanoseconds: 0, + }; + let atim = data_access_timestamp.unwrap_or(zero).try_into()?; + let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?; + let ctim = status_change_timestamp.unwrap_or(zero).try_into()?; Ok(types::Filestat { dev: 1, ino: metadata_hash.lower, @@ -1859,8 +1917,10 @@ impl< let desc = self.transact()?.get_descriptor(dirfd)?.clone(); let dirfd = match desc { Descriptor::PreopenDirectory((fd, _)) => fd, - Descriptor::File(File { fd, .. }) if self.table().is_dir(fd) => fd, - Descriptor::File(File { fd, .. }) if !self.table().is_dir(fd) => { + Descriptor::File(File { fd, .. }) if self.table().is_dir(&Resource::new_borrow(fd)) => { + fd + } + Descriptor::File(File { fd: _, .. }) => { // NOTE: Unlike most other methods, legacy implementation returns `NOTDIR` here return Err(types::Errno::Notdir.into()); } @@ -1868,7 +1928,7 @@ impl< }; let fd = self .open_at( - dirfd, + Resource::new_borrow(dirfd), dirflags.into(), path, oflags.into(), @@ -1882,7 +1942,7 @@ impl< .unwrap_or_else(types::Error::trap) })?; let fd = self.transact()?.descriptors.get_mut().push_file(File { - fd, + fd: fd.rep(), position: Default::default(), append: fdflags.contains(types::Fdflags::APPEND), blocking_mode: BlockingMode::from_fdflags(&fdflags), @@ -1968,7 +2028,8 @@ impl< let dirfd = self.get_dir_fd(dirfd)?; let src_path = read_string(src_path)?; let dest_path = read_string(dest_path)?; - self.symlink_at(dirfd, src_path, dest_path) + let borrow = Resource::new_borrow(dirfd.rep()); + self.symlink_at(borrow, src_path, dest_path) .await .map_err(|e| { e.try_into() @@ -1985,7 +2046,8 @@ impl< ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = path.as_cow()?.to_string(); - self.unlink_file_at(dirfd, path).await.map_err(|e| { + let borrow = Resource::new_borrow(dirfd.rep()); + self.unlink_file_at(borrow, path).await.map_err(|e| { e.try_into() .context("failed to call `unlink-file-at`") .unwrap_or_else(types::Error::trap) diff --git a/crates/wasi/src/preview2/stdio.rs b/crates/wasi/src/preview2/stdio.rs index 08f4350462cc..e7a6d667ae81 100644 --- a/crates/wasi/src/preview2/stdio.rs +++ b/crates/wasi/src/preview2/stdio.rs @@ -3,10 +3,56 @@ use crate::preview2::bindings::cli::{ terminal_stdout, }; use crate::preview2::bindings::io::streams; -use crate::preview2::pipe::AsyncWriteStream; -use crate::preview2::{HostOutputStream, OutputStreamError, WasiView}; -use bytes::Bytes; -use is_terminal::IsTerminal; +use crate::preview2::pipe::{self, AsyncWriteStream}; +use crate::preview2::stream::TableStreamExt; +use crate::preview2::{HostInputStream, HostOutputStream, WasiView}; +use std::io::IsTerminal; +use wasmtime::component::Resource; + +/// A trait used to represent the standard input to a guest program. +/// +/// This is used to implement various WASI APIs via the method implementations +/// below. +/// +/// Built-in implementations are provided for [`Stdin`], +/// [`pipe::MemoryInputPipe`], and [`pipe::ClosedInputStream`]. +pub trait StdinStream: Send + Sync { + /// Creates a fresh stream which is reading stdin. + /// + /// Note that the returned stream must share state with all other streams + /// previously created. Guests may create multiple handles to the same stdin + /// and they should all be synchronized in their progress through the + /// program's input. + /// + /// Note that this means that if one handle becomes ready for reading they + /// all become ready for reading. Subsequently if one is read from it may + /// mean that all the others are no longer ready for reading. This is + /// basically a consequence of the way the WIT APIs are designed today. + fn stream(&self) -> Box; + + /// Returns whether this stream is backed by a TTY. + fn isatty(&self) -> bool; +} + +impl StdinStream for pipe::MemoryInputPipe { + fn stream(&self) -> Box { + Box::new(self.clone()) + } + + fn isatty(&self) -> bool { + false + } +} + +impl StdinStream for pipe::ClosedInputStream { + fn stream(&self) -> Box { + Box::new(self.clone()) + } + + fn isatty(&self) -> bool { + false + } +} mod worker_thread_stdin; pub use self::worker_thread_stdin::{stdin, Stdin}; @@ -17,55 +63,91 @@ pub use self::worker_thread_stdin::{stdin, Stdin}; // and tokio's stdout/err. const STDIO_BUFFER_SIZE: usize = 4096; -pub struct Stdout(AsyncWriteStream); - -pub fn stdout() -> Stdout { - Stdout(AsyncWriteStream::new( - STDIO_BUFFER_SIZE, - tokio::io::stdout(), - )) +/// Similar to [`StdinStream`], except for output. +pub trait StdoutStream: Send + Sync { + /// Returns a fresh new stream which can write to this output stream. + /// + /// Note that all output streams should output to the same logical source. + /// This means that it's possible for each independent stream to acquire a + /// separate "permit" to write and then act on that permit. Note that + /// additionally at this time once a permit is "acquired" there's no way to + /// release it, for example you can wait for readiness and then never + /// actually write in WASI. This means that acquisition of a permit for one + /// stream cannot discount the size of a permit another stream could + /// obtain. + /// + /// Implementations must be able to handle this + fn stream(&self) -> Box; + + /// Returns whether this stream is backed by a TTY. + fn isatty(&self) -> bool; } -impl IsTerminal for Stdout { - fn is_terminal(&self) -> bool { - std::io::stdout().is_terminal() + +impl StdoutStream for pipe::MemoryOutputPipe { + fn stream(&self) -> Box { + Box::new(self.clone()) + } + + fn isatty(&self) -> bool { + false } } -#[async_trait::async_trait] -impl HostOutputStream for Stdout { - fn write(&mut self, bytes: Bytes) -> Result<(), OutputStreamError> { - self.0.write(bytes) + +impl StdoutStream for pipe::SinkOutputStream { + fn stream(&self) -> Box { + Box::new(self.clone()) } - fn flush(&mut self) -> Result<(), OutputStreamError> { - self.0.flush() + + fn isatty(&self) -> bool { + false + } +} + +impl StdoutStream for pipe::ClosedOutputStream { + fn stream(&self) -> Box { + Box::new(self.clone()) } - async fn write_ready(&mut self) -> Result { - self.0.write_ready().await + + fn isatty(&self) -> bool { + false } } -pub struct Stderr(AsyncWriteStream); +pub struct Stdout; -pub fn stderr() -> Stderr { - Stderr(AsyncWriteStream::new( - STDIO_BUFFER_SIZE, - tokio::io::stderr(), - )) +pub fn stdout() -> Stdout { + Stdout } -impl IsTerminal for Stderr { - fn is_terminal(&self) -> bool { - std::io::stderr().is_terminal() + +impl StdoutStream for Stdout { + fn stream(&self) -> Box { + Box::new(AsyncWriteStream::new( + STDIO_BUFFER_SIZE, + tokio::io::stdout(), + )) } -} -#[async_trait::async_trait] -impl HostOutputStream for Stderr { - fn write(&mut self, bytes: Bytes) -> Result<(), OutputStreamError> { - self.0.write(bytes) + + fn isatty(&self) -> bool { + std::io::stdout().is_terminal() } - fn flush(&mut self) -> Result<(), OutputStreamError> { - self.0.flush() +} + +pub struct Stderr; + +pub fn stderr() -> Stderr { + Stderr +} + +impl StdoutStream for Stderr { + fn stream(&self) -> Box { + Box::new(AsyncWriteStream::new( + STDIO_BUFFER_SIZE, + tokio::io::stderr(), + )) } - async fn write_ready(&mut self) -> Result { - self.0.write_ready().await + + fn isatty(&self) -> bool { + std::io::stderr().is_terminal() } } @@ -75,71 +157,75 @@ pub enum IsATTY { No, } -pub(crate) struct StdioInput { - pub input_stream: streams::InputStream, - pub isatty: IsATTY, -} - -pub(crate) struct StdioOutput { - pub output_stream: streams::OutputStream, - pub isatty: IsATTY, -} - impl stdin::Host for T { - fn get_stdin(&mut self) -> Result { - Ok(self.ctx().stdin.input_stream) + fn get_stdin(&mut self) -> Result, anyhow::Error> { + let stream = self.ctx_mut().stdin.stream(); + Ok(self.table_mut().push_input_stream(stream)?) } } impl stdout::Host for T { - fn get_stdout(&mut self) -> Result { - Ok(self.ctx().stdout.output_stream) + fn get_stdout(&mut self) -> Result, anyhow::Error> { + let stream = self.ctx_mut().stdout.stream(); + Ok(self.table_mut().push_output_stream(stream)?) } } impl stderr::Host for T { - fn get_stderr(&mut self) -> Result { - Ok(self.ctx().stderr.output_stream) + fn get_stderr(&mut self) -> Result, anyhow::Error> { + let stream = self.ctx_mut().stderr.stream(); + Ok(self.table_mut().push_output_stream(stream)?) } } -struct HostTerminalInput; -struct HostTerminalOutput; +pub struct HostTerminalInput; +pub struct HostTerminalOutput; -impl terminal_input::Host for T { - fn drop_terminal_input(&mut self, r: terminal_input::TerminalInput) -> anyhow::Result<()> { - self.table_mut().delete::(r)?; +impl terminal_input::Host for T {} +impl crate::preview2::bindings::cli::terminal_input::HostTerminalInput for T { + fn drop(&mut self, r: Resource) -> anyhow::Result<()> { + self.table_mut().delete::(r.rep())?; Ok(()) } } -impl terminal_output::Host for T { - fn drop_terminal_output(&mut self, r: terminal_output::TerminalOutput) -> anyhow::Result<()> { - self.table_mut().delete::(r)?; +impl terminal_output::Host for T {} +impl crate::preview2::bindings::cli::terminal_output::HostTerminalOutput for T { + fn drop(&mut self, r: Resource) -> anyhow::Result<()> { + self.table_mut().delete::(r.rep())?; Ok(()) } } impl terminal_stdin::Host for T { - fn get_terminal_stdin(&mut self) -> anyhow::Result> { - if let IsATTY::Yes = self.ctx().stdin.isatty { - Ok(Some(self.table_mut().push(Box::new(HostTerminalInput))?)) + fn get_terminal_stdin( + &mut self, + ) -> anyhow::Result>> { + if self.ctx().stdin.isatty() { + let fd = self.table_mut().push(Box::new(HostTerminalInput))?; + Ok(Some(Resource::new_own(fd))) } else { Ok(None) } } } impl terminal_stdout::Host for T { - fn get_terminal_stdout(&mut self) -> anyhow::Result> { - if let IsATTY::Yes = self.ctx().stdout.isatty { - Ok(Some(self.table_mut().push(Box::new(HostTerminalOutput))?)) + fn get_terminal_stdout( + &mut self, + ) -> anyhow::Result>> { + if self.ctx().stdout.isatty() { + let fd = self.table_mut().push(Box::new(HostTerminalOutput))?; + Ok(Some(Resource::new_own(fd))) } else { Ok(None) } } } impl terminal_stderr::Host for T { - fn get_terminal_stderr(&mut self) -> anyhow::Result> { - if let IsATTY::Yes = self.ctx().stderr.isatty { - Ok(Some(self.table_mut().push(Box::new(HostTerminalOutput))?)) + fn get_terminal_stderr( + &mut self, + ) -> anyhow::Result>> { + if self.ctx().stderr.isatty() { + let fd = self.table_mut().push(Box::new(HostTerminalOutput))?; + Ok(Some(Resource::new_own(fd))) } else { Ok(None) } diff --git a/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs b/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs index e1505dff844b..bf933ad8398e 100644 --- a/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs +++ b/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs @@ -23,10 +23,11 @@ //! This module is one that's likely to change over time though as new systems //! are encountered along with preexisting bugs. +use crate::preview2::stdio::StdinStream; use crate::preview2::{HostInputStream, StreamState}; use anyhow::Error; use bytes::{Bytes, BytesMut}; -use std::io::Read; +use std::io::{IsTerminal, Read}; use std::mem; use std::sync::{Condvar, Mutex, OnceLock}; use tokio::sync::Notify; @@ -103,8 +104,12 @@ pub fn stdin() -> Stdin { Stdin } -impl is_terminal::IsTerminal for Stdin { - fn is_terminal(&self) -> bool { +impl StdinStream for Stdin { + fn stream(&self) -> Box { + Box::new(Stdin) + } + + fn isatty(&self) -> bool { std::io::stdin().is_terminal() } } diff --git a/crates/wasi/src/preview2/stream.rs b/crates/wasi/src/preview2/stream.rs index 2c8a46b7f99d..7a61747d2511 100644 --- a/crates/wasi/src/preview2/stream.rs +++ b/crates/wasi/src/preview2/stream.rs @@ -1,8 +1,10 @@ +use crate::preview2::bindings::io::streams::{InputStream, OutputStream}; use crate::preview2::filesystem::FileInputStream; use crate::preview2::{Table, TableError}; use anyhow::Error; use bytes::Bytes; use std::fmt; +use wasmtime::component::Resource; /// An error which should be reported to Wasm as a runtime error, rather than /// an error which should trap Wasm execution. The definition for runtime @@ -175,94 +177,128 @@ pub(crate) trait InternalTableStreamExt { fn push_internal_input_stream( &mut self, istream: InternalInputStream, - ) -> Result; - fn push_internal_input_stream_child( + ) -> Result, TableError>; + fn push_internal_input_stream_child( &mut self, istream: InternalInputStream, - parent: u32, - ) -> Result; + parent: Resource, + ) -> Result, TableError>; fn get_internal_input_stream_mut( &mut self, - fd: u32, + fd: &Resource, ) -> Result<&mut InternalInputStream, TableError>; - fn delete_internal_input_stream(&mut self, fd: u32) -> Result; + fn delete_internal_input_stream( + &mut self, + fd: Resource, + ) -> Result; } impl InternalTableStreamExt for Table { fn push_internal_input_stream( &mut self, istream: InternalInputStream, - ) -> Result { - self.push(Box::new(istream)) + ) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(istream))?)) } - fn push_internal_input_stream_child( + fn push_internal_input_stream_child( &mut self, istream: InternalInputStream, - parent: u32, - ) -> Result { - self.push_child(Box::new(istream), parent) + parent: Resource, + ) -> Result, TableError> { + Ok(Resource::new_own( + self.push_child(Box::new(istream), parent.rep())?, + )) } fn get_internal_input_stream_mut( &mut self, - fd: u32, + fd: &Resource, ) -> Result<&mut InternalInputStream, TableError> { - self.get_mut(fd) + self.get_mut(fd.rep()) } - fn delete_internal_input_stream(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_internal_input_stream( + &mut self, + fd: Resource, + ) -> Result { + self.delete(fd.rep()) } } /// Extension trait for managing [`HostInputStream`]s and [`HostOutputStream`]s in the [`Table`]. pub trait TableStreamExt { /// Push a [`HostInputStream`] into a [`Table`], returning the table index. - fn push_input_stream(&mut self, istream: Box) -> Result; + fn push_input_stream( + &mut self, + istream: Box, + ) -> Result, TableError>; /// Same as [`push_input_stream`](Self::push_output_stream) except assigns a parent resource to /// the input-stream created. - fn push_input_stream_child( + fn push_input_stream_child( &mut self, istream: Box, - parent: u32, - ) -> Result; + parent: Resource, + ) -> Result, TableError>; /// Get a mutable reference to a [`HostInputStream`] in a [`Table`]. - fn get_input_stream_mut(&mut self, fd: u32) -> Result<&mut dyn HostInputStream, TableError>; + fn get_input_stream_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut dyn HostInputStream, TableError>; /// Remove [`HostInputStream`] from table: - fn delete_input_stream(&mut self, fd: u32) -> Result, TableError>; + fn delete_input_stream( + &mut self, + fd: Resource, + ) -> Result, TableError>; /// Push a [`HostOutputStream`] into a [`Table`], returning the table index. - fn push_output_stream(&mut self, ostream: Box) - -> Result; + fn push_output_stream( + &mut self, + ostream: Box, + ) -> Result, TableError>; /// Same as [`push_output_stream`](Self::push_output_stream) except assigns a parent resource /// to the output-stream created. - fn push_output_stream_child( + fn push_output_stream_child( &mut self, ostream: Box, - parent: u32, - ) -> Result; + parent: Resource, + ) -> Result, TableError>; /// Get a mutable reference to a [`HostOutputStream`] in a [`Table`]. - fn get_output_stream_mut(&mut self, fd: u32) -> Result<&mut dyn HostOutputStream, TableError>; + fn get_output_stream_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut dyn HostOutputStream, TableError>; /// Remove [`HostOutputStream`] from table: - fn delete_output_stream(&mut self, fd: u32) -> Result, TableError>; + fn delete_output_stream( + &mut self, + fd: Resource, + ) -> Result, TableError>; } impl TableStreamExt for Table { - fn push_input_stream(&mut self, istream: Box) -> Result { + fn push_input_stream( + &mut self, + istream: Box, + ) -> Result, TableError> { self.push_internal_input_stream(InternalInputStream::Host(istream)) } - fn push_input_stream_child( + fn push_input_stream_child( &mut self, istream: Box, - parent: u32, - ) -> Result { + parent: Resource, + ) -> Result, TableError> { self.push_internal_input_stream_child(InternalInputStream::Host(istream), parent) } - fn get_input_stream_mut(&mut self, fd: u32) -> Result<&mut dyn HostInputStream, TableError> { + fn get_input_stream_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut dyn HostInputStream, TableError> { match self.get_internal_input_stream_mut(fd)? { InternalInputStream::Host(ref mut h) => Ok(h.as_mut()), _ => Err(TableError::WrongType), } } - fn delete_input_stream(&mut self, fd: u32) -> Result, TableError> { - let occ = self.entry(fd)?; + fn delete_input_stream( + &mut self, + fd: Resource, + ) -> Result, TableError> { + let occ = self.entry(fd.rep())?; match occ.get().downcast_ref::() { Some(InternalInputStream::Host(_)) => { let any = occ.remove_entry()?; @@ -278,22 +314,30 @@ impl TableStreamExt for Table { fn push_output_stream( &mut self, ostream: Box, - ) -> Result { - self.push(Box::new(ostream)) + ) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(ostream))?)) } - fn push_output_stream_child( + fn push_output_stream_child( &mut self, ostream: Box, - parent: u32, - ) -> Result { - self.push_child(Box::new(ostream), parent) + parent: Resource, + ) -> Result, TableError> { + Ok(Resource::new_own( + self.push_child(Box::new(ostream), parent.rep())?, + )) } - fn get_output_stream_mut(&mut self, fd: u32) -> Result<&mut dyn HostOutputStream, TableError> { - let boxed: &mut Box = self.get_mut(fd)?; + fn get_output_stream_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut dyn HostOutputStream, TableError> { + let boxed: &mut Box = self.get_mut(fd.rep())?; Ok(boxed.as_mut()) } - fn delete_output_stream(&mut self, fd: u32) -> Result, TableError> { - self.delete(fd) + fn delete_output_stream( + &mut self, + fd: Resource, + ) -> Result, TableError> { + self.delete(fd.rep()) } } @@ -308,19 +352,9 @@ mod test { // Put it into the table: let ix = table.push_input_stream(Box::new(dummy)).unwrap(); // Get a mut ref to it: - let _ = table.get_input_stream_mut(ix).unwrap(); - // Fails at wrong type: - assert!(matches!( - table.get_output_stream_mut(ix), - Err(TableError::WrongType) - )); + let _ = table.get_input_stream_mut(&ix).unwrap(); // Delete it: let _ = table.delete_input_stream(ix).unwrap(); - // Now absent from table: - assert!(matches!( - table.get_input_stream_mut(ix), - Err(TableError::NotPresent) - )); } #[test] @@ -330,18 +364,8 @@ mod test { // Put it in the table: let ix = table.push_output_stream(Box::new(dummy)).unwrap(); // Get a mut ref to it: - let _ = table.get_output_stream_mut(ix).unwrap(); - // Fails at wrong type: - assert!(matches!( - table.get_input_stream_mut(ix), - Err(TableError::WrongType) - )); + let _ = table.get_output_stream_mut(&ix).unwrap(); // Delete it: let _ = table.delete_output_stream(ix).unwrap(); - // Now absent: - assert!(matches!( - table.get_output_stream_mut(ix), - Err(TableError::NotPresent) - )); } } diff --git a/crates/wasi/src/preview2/table.rs b/crates/wasi/src/preview2/table.rs index de9b50a6f772..c8d8cb142973 100644 --- a/crates/wasi/src/preview2/table.rs +++ b/crates/wasi/src/preview2/table.rs @@ -196,8 +196,7 @@ impl Table { } } - /// Get a mutable reference to a resource of a given type at a given index. Only one mutable - /// reference can be borrowed at any given time. Borrow failure results in a trapping error. + /// Get a mutable reference to a resource of a given type at a given index. pub fn get_mut(&mut self, key: u32) -> Result<&mut T, TableError> { if let Some(r) = self.map.get_mut(&key) { r.entry @@ -208,6 +207,15 @@ impl Table { } } + /// Get a mutable reference to a resource a a `&mut dyn Any`. + pub fn get_as_any_mut(&mut self, key: u32) -> Result<&mut dyn Any, TableError> { + if let Some(r) = self.map.get_mut(&key) { + Ok(&mut *r.entry) + } else { + Err(TableError::NotPresent) + } + } + /// Get an [`OccupiedEntry`] corresponding to a table entry, if it exists. This allows you to /// remove or replace the entry based on its contents. The methods available are a subset of /// [`std::collections::hash_map::OccupiedEntry`] - it does not give access to the key, it diff --git a/crates/wasi/src/preview2/tcp.rs b/crates/wasi/src/preview2/tcp.rs index 9721e2c3c37a..eb3dbe4cee0e 100644 --- a/crates/wasi/src/preview2/tcp.rs +++ b/crates/wasi/src/preview2/tcp.rs @@ -1,4 +1,5 @@ use super::{HostInputStream, HostOutputStream, OutputStreamError}; +use crate::preview2::bindings::sockets::tcp::TcpSocket; use crate::preview2::{ with_ambient_tokio_runtime, AbortOnDropJoinHandle, StreamState, Table, TableError, }; @@ -7,6 +8,7 @@ use cap_std::net::TcpListener; use io_lifetimes::raw::{FromRawSocketlike, IntoRawSocketlike}; use std::io; use std::sync::Arc; +use wasmtime::component::Resource; /// The state of a TCP socket. /// @@ -43,8 +45,8 @@ pub(crate) enum HostTcpState { /// /// The inner state is wrapped in an Arc because the same underlying socket is /// used for implementing the stream types. -pub(crate) struct HostTcpSocket { - /// The part of a `HostTcpSocket` which is reference-counted so that we +pub(crate) struct HostTcpSocketState { + /// The part of a `HostTcpSocketState` which is reference-counted so that we /// can pass it to async tasks. pub(crate) inner: Arc, @@ -213,7 +215,7 @@ impl HostOutputStream for TcpWriteStream { } } -impl HostTcpSocket { +impl HostTcpSocketState { /// Create a new socket in the given family. pub fn new(family: AddressFamily) -> io::Result { // Create a new host socket and set it to non-blocking, which is needed @@ -222,7 +224,7 @@ impl HostTcpSocket { Self::from_tcp_listener(tcp_listener) } - /// Create a `HostTcpSocket` from an existing socket. + /// Create a `HostTcpSocketState` from an existing socket. /// /// The socket must be in non-blocking mode. pub fn from_tcp_stream(tcp_socket: cap_std::net::TcpStream) -> io::Result { @@ -254,27 +256,45 @@ impl HostTcpSocket { } pub(crate) trait TableTcpSocketExt { - fn push_tcp_socket(&mut self, tcp_socket: HostTcpSocket) -> Result; - fn delete_tcp_socket(&mut self, fd: u32) -> Result; - fn is_tcp_socket(&self, fd: u32) -> bool; - fn get_tcp_socket(&self, fd: u32) -> Result<&HostTcpSocket, TableError>; - fn get_tcp_socket_mut(&mut self, fd: u32) -> Result<&mut HostTcpSocket, TableError>; + fn push_tcp_socket( + &mut self, + tcp_socket: HostTcpSocketState, + ) -> Result, TableError>; + fn delete_tcp_socket( + &mut self, + fd: Resource, + ) -> Result; + fn is_tcp_socket(&self, fd: &Resource) -> bool; + fn get_tcp_socket(&self, fd: &Resource) -> Result<&HostTcpSocketState, TableError>; + fn get_tcp_socket_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut HostTcpSocketState, TableError>; } impl TableTcpSocketExt for Table { - fn push_tcp_socket(&mut self, tcp_socket: HostTcpSocket) -> Result { - self.push(Box::new(tcp_socket)) + fn push_tcp_socket( + &mut self, + tcp_socket: HostTcpSocketState, + ) -> Result, TableError> { + Ok(Resource::new_own(self.push(Box::new(tcp_socket))?)) } - fn delete_tcp_socket(&mut self, fd: u32) -> Result { - self.delete(fd) + fn delete_tcp_socket( + &mut self, + fd: Resource, + ) -> Result { + self.delete(fd.rep()) } - fn is_tcp_socket(&self, fd: u32) -> bool { - self.is::(fd) + fn is_tcp_socket(&self, fd: &Resource) -> bool { + self.is::(fd.rep()) } - fn get_tcp_socket(&self, fd: u32) -> Result<&HostTcpSocket, TableError> { - self.get(fd) + fn get_tcp_socket(&self, fd: &Resource) -> Result<&HostTcpSocketState, TableError> { + self.get(fd.rep()) } - fn get_tcp_socket_mut(&mut self, fd: u32) -> Result<&mut HostTcpSocket, TableError> { - self.get_mut(fd) + fn get_tcp_socket_mut( + &mut self, + fd: &Resource, + ) -> Result<&mut HostTcpSocketState, TableError> { + self.get_mut(fd.rep()) } } diff --git a/crates/wasi/wit/command-extended.wit b/crates/wasi/wit/command-extended.wit index 314e26dfce1b..3c56808e4abe 100644 --- a/crates/wasi/wit/command-extended.wit +++ b/crates/wasi/wit/command-extended.wit @@ -16,7 +16,7 @@ world command-extended { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/exit diff --git a/crates/wasi/wit/deps/cli/reactor.wit b/crates/wasi/wit/deps/cli/reactor.wit index 46e3186e5636..274d0644dc41 100644 --- a/crates/wasi/wit/deps/cli/reactor.wit +++ b/crates/wasi/wit/deps/cli/reactor.wit @@ -16,7 +16,7 @@ world reactor { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import environment diff --git a/crates/wasi/wit/deps/cli/terminal.wit b/crates/wasi/wit/deps/cli/terminal.wit index f32e74437484..b0a5bec2a2c7 100644 --- a/crates/wasi/wit/deps/cli/terminal.wit +++ b/crates/wasi/wit/deps/cli/terminal.wit @@ -1,31 +1,19 @@ interface terminal-input { /// The input side of a terminal. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type terminal-input = u32 + resource terminal-input // In the future, this may include functions for disabling echoing, // disabling input buffering so that keyboard events are sent through // immediately, querying supported features, and so on. - - /// Dispose of the specified terminal-input after which it may no longer - /// be used. - drop-terminal-input: func(this: terminal-input) } interface terminal-output { /// The output side of a terminal. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type terminal-output = u32 + resource terminal-output // In the future, this may include functions for querying the terminal // size, being notified of terminal size changes, querying supported // features, and so on. - - /// Dispose of the specified terminal-output, after which it may no longer - /// be used. - drop-terminal-output: func(this: terminal-output) } /// An interface providing an optional `terminal-input` for stdin as a diff --git a/crates/wasi/wit/deps/clocks/monotonic-clock.wit b/crates/wasi/wit/deps/clocks/monotonic-clock.wit index 50eb4de111af..703a5fb7a503 100644 --- a/crates/wasi/wit/deps/clocks/monotonic-clock.wit +++ b/crates/wasi/wit/deps/clocks/monotonic-clock.wit @@ -1,5 +1,3 @@ -package wasi:clocks - /// WASI Monotonic Clock is a clock API intended to let users measure elapsed /// time. /// @@ -11,7 +9,7 @@ package wasi:clocks /// /// It is intended for measuring elapsed time. interface monotonic-clock { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} /// A timestamp in nanoseconds. type instant = u64 diff --git a/crates/wasi/wit/deps/clocks/timezone.wit b/crates/wasi/wit/deps/clocks/timezone.wit index 2b6855668e1d..a872bffc7414 100644 --- a/crates/wasi/wit/deps/clocks/timezone.wit +++ b/crates/wasi/wit/deps/clocks/timezone.wit @@ -1,17 +1,6 @@ -package wasi:clocks - interface timezone { use wall-clock.{datetime} - /// A timezone. - /// - /// In timezones that recognize daylight saving time, also known as daylight - /// time and summer time, the information returned from the functions varies - /// over time to reflect these adjustments. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type timezone = u32 - /// Return information needed to display the given `datetime`. This includes /// the UTC offset, the time zone name, and a flag indicating whether /// daylight saving time is active. @@ -19,14 +8,10 @@ interface timezone { /// If the timezone cannot be determined for the given `datetime`, return a /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight /// saving time. - display: func(this: timezone, when: datetime) -> timezone-display + display: func(when: datetime) -> timezone-display /// The same as `display`, but only return the UTC offset. - utc-offset: func(this: timezone, when: datetime) -> s32 - - /// Dispose of the specified input-stream, after which it may no longer - /// be used. - drop-timezone: func(this: timezone) + utc-offset: func(when: datetime) -> s32 /// Information useful for displaying the timezone of a specific `datetime`. /// diff --git a/crates/wasi/wit/deps/clocks/wall-clock.wit b/crates/wasi/wit/deps/clocks/wall-clock.wit index 6137724f60b1..dae44a7308cd 100644 --- a/crates/wasi/wit/deps/clocks/wall-clock.wit +++ b/crates/wasi/wit/deps/clocks/wall-clock.wit @@ -1,5 +1,3 @@ -package wasi:clocks - /// WASI Wall Clock is a clock API intended to let users query the current /// time. The name "wall" makes an analogy to a "clock on the wall", which /// is not necessarily monotonic as it may be reset. diff --git a/crates/wasi/wit/deps/clocks/world.wit b/crates/wasi/wit/deps/clocks/world.wit new file mode 100644 index 000000000000..5c2dd411d2d0 --- /dev/null +++ b/crates/wasi/wit/deps/clocks/world.wit @@ -0,0 +1,7 @@ +package wasi:clocks + +world imports { + import monotonic-clock + import wall-clock + import timezone +} diff --git a/crates/wasi/wit/deps/filesystem/types.wit b/crates/wasi/wit/deps/filesystem/types.wit index e72a742de6c8..3f69bf997a29 100644 --- a/crates/wasi/wit/deps/filesystem/types.wit +++ b/crates/wasi/wit/deps/filesystem/types.wit @@ -17,6 +17,11 @@ /// `..` and symbolic link steps, reaches a directory outside of the base /// directory, or reaches a symlink to an absolute or rooted path in the /// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { use wasi:io/streams.{input-stream, output-stream} use wasi:clocks/wall-clock.{datetime} @@ -102,11 +107,20 @@ interface types { /// length in bytes of the pathname contained in the symbolic link. size: filesize, /// Last data access timestamp. - data-access-timestamp: datetime, + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, /// Last data modification timestamp. - data-modification-timestamp: datetime, - /// Last file status change timestamp. - status-change-timestamp: datetime, + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, } /// Flags determining the method of how paths are resolved. @@ -277,13 +291,6 @@ interface types { no-reuse, } - /// A descriptor is a reference to a filesystem object, which may be a file, - /// directory, named pipe, special file, or other object on which filesystem - /// calls may be made. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type descriptor = u32 - /// A 128-bit hash value, split into parts because wasm doesn't have a /// 128-bit integer type. record metadata-hash-value { @@ -293,532 +300,499 @@ interface types { upper: u64, } - /// Return a stream for reading from a file, if available. - /// - /// May fail with an error-code describing why the file cannot be read. - /// - /// Multiple read, write, and append streams may be active on the same open - /// file and they do not interfere with each other. - /// - /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. - read-via-stream: func( - this: descriptor, - /// The offset within the file at which to start reading. - offset: filesize, - ) -> result - - /// Return a stream for writing to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be written. - /// - /// Note: This allows using `write-stream`, which is similar to `write` in - /// POSIX. - write-via-stream: func( - this: descriptor, - /// The offset within the file at which to start writing. - offset: filesize, - ) -> result - - /// Return a stream for appending to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be appended. - /// - /// Note: This allows using `write-stream`, which is similar to `write` with - /// `O_APPEND` in in POSIX. - append-via-stream: func( - this: descriptor, - ) -> result + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result - /// Provide file advisory information on a descriptor. - /// - /// This is similar to `posix_fadvise` in POSIX. - advise: func( - this: descriptor, - /// The offset within the file to which the advisory applies. - offset: filesize, - /// The length of the region to which the advisory applies. - length: filesize, - /// The advice. - advice: advice - ) -> result<_, error-code> - - /// Synchronize the data of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fdatasync` in POSIX. - sync-data: func(this: descriptor) -> result<_, error-code> + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result - /// Get flags associated with a descriptor. - /// - /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. - /// - /// Note: This returns the value that was the `fs_flags` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-flags: func(this: descriptor) -> result + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code> + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code> - /// Get the dynamic type of a descriptor. - /// - /// Note: This returns the same value as the `type` field of the `fd-stat` - /// returned by `stat`, `stat-at` and similar. - /// - /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided - /// by `fstat` in POSIX. - /// - /// Note: This returns the value that was the `fs_filetype` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-type: func(this: descriptor) -> result + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result - /// Adjust the size of an open file. If this increases the file's size, the - /// extra bytes are filled with zeros. - /// - /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. - set-size: func(this: descriptor, size: filesize) -> result<_, error-code> + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result - /// Adjust the timestamps of an open file or directory. - /// - /// Note: This is similar to `futimens` in POSIX. - /// - /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. - set-times: func( - this: descriptor, - /// The desired values of the data access timestamp. - data-access-timestamp: new-timestamp, - /// The desired values of the data modification timestamp. - data-modification-timestamp: new-timestamp, - ) -> result<_, error-code> - - /// Read from a descriptor, without using and updating the descriptor's offset. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a bool which, when true, indicates that the end of the - /// file was reached. The returned list will contain up to `length` bytes; it - /// may return fewer than requested, if the end of the file is reached or - /// if the I/O operation is interrupted. - /// - /// In the future, this may change to return a `stream`. - /// - /// Note: This is similar to `pread` in POSIX. - read: func( - this: descriptor, - /// The maximum number of bytes to read. - length: filesize, - /// The offset within the file at which to read. - offset: filesize, - ) -> result, bool>, error-code> - - /// Write to a descriptor, without using and updating the descriptor's offset. - /// - /// It is valid to write past the end of a file; the file is extended to the - /// extent of the write, with bytes between the previous end and the start of - /// the write set to zero. - /// - /// In the future, this may change to take a `stream`. - /// - /// Note: This is similar to `pwrite` in POSIX. - write: func( - this: descriptor, - /// Data to write - buffer: list, - /// The offset within the file at which to write. - offset: filesize, - ) -> result - - /// Read directory entries from a directory. - /// - /// On filesystems where directories contain entries referring to themselves - /// and their parents, often named `.` and `..` respectively, these entries - /// are omitted. - /// - /// This always returns a new stream which starts at the beginning of the - /// directory. Multiple streams may be active on the same directory, and they - /// do not interfere with each other. - read-directory: func( - this: descriptor - ) -> result - - /// Synchronize the data and metadata of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fsync` in POSIX. - sync: func(this: descriptor) -> result<_, error-code> + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code> - /// Create a directory. - /// - /// Note: This is similar to `mkdirat` in POSIX. - create-directory-at: func( - this: descriptor, - /// The relative path at which to create the directory. - path: string, - ) -> result<_, error-code> - - /// Return the attributes of an open file or directory. - /// - /// Note: This is similar to `fstat` in POSIX, except that it does not return - /// device and inode information. For testing whether two descriptors refer to - /// the same underlying filesystem object, use `is-same-object`. To obtain - /// additional data that can be used do determine whether a file has been - /// modified, use `metadata-hash`. - /// - /// Note: This was called `fd_filestat_get` in earlier versions of WASI. - stat: func(this: descriptor) -> result + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code> + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result - /// Return the attributes of a file or directory. - /// - /// Note: This is similar to `fstatat` in POSIX, except that it does not - /// return device and inode information. See the `stat` description for a - /// discussion of alternatives. - /// - /// Note: This was called `path_filestat_get` in earlier versions of WASI. - stat-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to inspect. - path: string, - ) -> result - - /// Adjust the timestamps of a file or directory. - /// - /// Note: This is similar to `utimensat` in POSIX. - /// - /// Note: This was called `path_filestat_set_times` in earlier versions of - /// WASI. - set-times-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to operate on. - path: string, - /// The desired values of the data access timestamp. - data-access-timestamp: new-timestamp, - /// The desired values of the data modification timestamp. - data-modification-timestamp: new-timestamp, - ) -> result<_, error-code> - - /// Create a hard link. - /// - /// Note: This is similar to `linkat` in POSIX. - link-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - old-path-flags: path-flags, - /// The relative source path from which to link. - old-path: string, - /// The base directory for `new-path`. - new-descriptor: descriptor, - /// The relative destination path at which to create the hard link. - new-path: string, - ) -> result<_, error-code> - - /// Open a file or directory. - /// - /// The returned descriptor is not guaranteed to be the lowest-numbered - /// descriptor not currently open/ it is randomized to prevent applications - /// from depending on making assumptions about indexes, since this is - /// error-prone in multi-threaded contexts. The returned descriptor is - /// guaranteed to be less than 2**31. - /// - /// If `flags` contains `descriptor-flags::mutate-directory`, and the base - /// descriptor doesn't have `descriptor-flags::mutate-directory` set, - /// `open-at` fails with `error-code::read-only`. - /// - /// If `flags` contains `write` or `mutate-directory`, or `open-flags` - /// contains `truncate` or `create`, and the base descriptor doesn't have - /// `descriptor-flags::mutate-directory` set, `open-at` fails with - /// `error-code::read-only`. - /// - /// Note: This is similar to `openat` in POSIX. - open-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the object to open. - path: string, - /// The method by which to open the file. - open-flags: open-flags, - /// Flags to use for the resulting descriptor. - %flags: descriptor-flags, - /// Permissions to use when creating a new file. - modes: modes - ) -> result - - /// Read the contents of a symbolic link. - /// - /// If the contents contain an absolute or rooted path in the underlying - /// filesystem, this function fails with `error-code::not-permitted`. - /// - /// Note: This is similar to `readlinkat` in POSIX. - readlink-at: func( - this: descriptor, - /// The relative path of the symbolic link from which to read. - path: string, - ) -> result - - /// Remove a directory. - /// - /// Return `error-code::not-empty` if the directory is not empty. - /// - /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - remove-directory-at: func( - this: descriptor, - /// The relative path to a directory to remove. - path: string, - ) -> result<_, error-code> - - /// Rename a filesystem object. - /// - /// Note: This is similar to `renameat` in POSIX. - rename-at: func( - this: descriptor, - /// The relative source path of the file or directory to rename. - old-path: string, - /// The base directory for `new-path`. - new-descriptor: descriptor, - /// The relative destination path to which to rename the file or directory. - new-path: string, - ) -> result<_, error-code> - - /// Create a symbolic link (also known as a "symlink"). - /// - /// If `old-path` starts with `/`, the function fails with - /// `error-code::not-permitted`. - /// - /// Note: This is similar to `symlinkat` in POSIX. - symlink-at: func( - this: descriptor, - /// The contents of the symbolic link. - old-path: string, - /// The relative destination path at which to create the symbolic link. - new-path: string, - ) -> result<_, error-code> - - /// Check accessibility of a filesystem path. - /// - /// Check whether the given filesystem path names an object which is - /// readable, writable, or executable, or whether it exists. - /// - /// This does not a guarantee that subsequent accesses will succeed, as - /// filesystem permissions may be modified asynchronously by external - /// entities. - /// - /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. - access-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to check. - path: string, - /// The type of check to perform. - %type: access-type - ) -> result<_, error-code> - - /// Unlink a filesystem object that is not a directory. - /// - /// Return `error-code::is-directory` if the path refers to a directory. - /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. - unlink-file-at: func( - this: descriptor, - /// The relative path to a file to unlink. - path: string, - ) -> result<_, error-code> - - /// Change the permissions of a filesystem object that is not a directory. - /// - /// Note that the ultimate meanings of these permissions is - /// filesystem-specific. - /// - /// Note: This is similar to `fchmodat` in POSIX. - change-file-permissions-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to operate on. - path: string, - /// The new permissions for the filesystem object. - modes: modes, - ) -> result<_, error-code> - - /// Change the permissions of a directory. - /// - /// Note that the ultimate meanings of these permissions is - /// filesystem-specific. - /// - /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" - /// flag. `read` on a directory implies readability and searchability, and - /// `execute` is not valid for directories. - /// - /// Note: This is similar to `fchmodat` in POSIX. - change-directory-permissions-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path to operate on. - path: string, - /// The new permissions for the directory. - modes: modes, - ) -> result<_, error-code> - - /// Request a shared advisory lock for an open file. - /// - /// This requests a *shared* lock; more than one shared lock can be held for - /// a file at the same time. - /// - /// If the open file has an exclusive lock, this function downgrades the lock - /// to a shared lock. If it has a shared lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified how shared locks interact with locks acquired by - /// non-WASI programs. - /// - /// This function blocks until the lock can be acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. - lock-shared: func(this: descriptor) -> result<_, error-code> + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code> - /// Request an exclusive advisory lock for an open file. - /// - /// This requests an *exclusive* lock; no other locks may be held for the - /// file while an exclusive lock is held. - /// - /// If the open file has a shared lock and there are no exclusive locks held - /// for the file, this function upgrades the lock to an exclusive lock. If the - /// open file already has an exclusive lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified whether this function succeeds if the file descriptor - /// is not opened for writing. It is unspecified how exclusive locks interact - /// with locks acquired by non-WASI programs. - /// - /// This function blocks until the lock can be acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. - lock-exclusive: func(this: descriptor) -> result<_, error-code> + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code> - /// Request a shared advisory lock for an open file. - /// - /// This requests a *shared* lock; more than one shared lock can be held for - /// a file at the same time. - /// - /// If the open file has an exclusive lock, this function downgrades the lock - /// to a shared lock. If it has a shared lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified how shared locks interact with locks acquired by - /// non-WASI programs. - /// - /// This function returns `error-code::would-block` if the lock cannot be - /// acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. - try-lock-shared: func(this: descriptor) -> result<_, error-code> + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result - /// Request an exclusive advisory lock for an open file. - /// - /// This requests an *exclusive* lock; no other locks may be held for the - /// file while an exclusive lock is held. - /// - /// If the open file has a shared lock and there are no exclusive locks held - /// for the file, this function upgrades the lock to an exclusive lock. If the - /// open file already has an exclusive lock, this function has no effect. - /// - /// This requests an *advisory* lock, meaning that the file could be accessed - /// by other programs that don't hold the lock. - /// - /// It is unspecified whether this function succeeds if the file descriptor - /// is not opened for writing. It is unspecified how exclusive locks interact - /// with locks acquired by non-WASI programs. - /// - /// This function returns `error-code::would-block` if the lock cannot be - /// acquired. - /// - /// Not all filesystems support locking; on filesystems which don't support - /// locking, this function returns `error-code::unsupported`. - /// - /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. - try-lock-exclusive: func(this: descriptor) -> result<_, error-code> + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code> + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + /// Permissions to use when creating a new file. + modes: modes + ) -> result + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result - /// Release a shared or exclusive lock on an open file. - /// - /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. - unlock: func(this: descriptor) -> result<_, error-code> + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code> - /// Dispose of the specified `descriptor`, after which it may no longer - /// be used. - drop-descriptor: func(this: descriptor) + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code> + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code> + + /// Check accessibility of a filesystem path. + /// + /// Check whether the given filesystem path names an object which is + /// readable, writable, or executable, or whether it exists. + /// + /// This does not a guarantee that subsequent accesses will succeed, as + /// filesystem permissions may be modified asynchronously by external + /// entities. + /// + /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. + access-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to check. + path: string, + /// The type of check to perform. + %type: access-type + ) -> result<_, error-code> + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code> + + /// Change the permissions of a filesystem object that is not a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-file-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + modes: modes, + ) -> result<_, error-code> + + /// Change the permissions of a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + /// flag. `read` on a directory implies readability and searchability, and + /// `execute` is not valid for directories. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-directory-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + modes: modes, + ) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + lock-shared: func() -> result<_, error-code> - /// A stream of directory entries. - /// - /// This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). - type directory-entry-stream = u32 + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + lock-exclusive: func() -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + try-lock-shared: func() -> result<_, error-code> - /// Read a single directory entry from a `directory-entry-stream`. - read-directory-entry: func( - this: directory-entry-stream - ) -> result, error-code> + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + try-lock-exclusive: func() -> result<_, error-code> - /// Dispose of the specified `directory-entry-stream`, after which it may no longer - /// be used. - drop-directory-entry-stream: func(this: directory-entry-stream) + /// Release a shared or exclusive lock on an open file. + /// + /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + unlock: func() -> result<_, error-code> - /// Test whether two descriptors refer to the same filesystem object. - /// - /// In POSIX, this corresponds to testing whether the two descriptors have the - /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. - /// wasi-filesystem does not expose device and inode numbers, so this function - /// may be used instead. - is-same-object: func(this: descriptor, other: descriptor) -> bool - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a descriptor. - /// - /// This returns a hash of the last-modification timestamp and file size, and - /// may also include the inode number, device number, birth timestamp, and - /// other metadata fields that may change when the file is modified or - /// replaced. It may also include a secret value chosen by the - /// implementation and not otherwise exposed. - /// - /// Implementations are encourated to provide the following properties: - /// - /// - If the file is not modified or replaced, the computed hash value should - /// usually not change. - /// - If the object is modified or replaced, the computed hash value should - /// usually change. - /// - The inputs to the hash should not be easily computable from the - /// computed hash. - /// - /// However, none of these is required. - metadata-hash: func( - this: descriptor, - ) -> result + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a directory descriptor and a relative path. - /// - /// This performs the same hash computation as `metadata-hash`. - metadata-hash-at: func( - this: descriptor, - /// Flags determining the method of how the path is resolved. - path-flags: path-flags, - /// The relative path of the file or directory to inspect. - path: string, - ) -> result + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code> + } } diff --git a/crates/wasi/wit/deps/filesystem/world.wit b/crates/wasi/wit/deps/filesystem/world.wit index b51f484f8383..5fa7eafdb850 100644 --- a/crates/wasi/wit/deps/filesystem/world.wit +++ b/crates/wasi/wit/deps/filesystem/world.wit @@ -1,6 +1,6 @@ package wasi:filesystem -world example-world { +world imports { import types import preopens } diff --git a/crates/wasi/wit/deps/http/types.wit b/crates/wasi/wit/deps/http/types.wit index dfcacd8feb73..1abc7a1ff2c4 100644 --- a/crates/wasi/wit/deps/http/types.wit +++ b/crates/wasi/wit/deps/http/types.wit @@ -3,7 +3,7 @@ // imported and exported interfaces. interface types { use wasi:io/streams.{input-stream, output-stream} - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} // This type corresponds to HTTP standard Methods. variant method { @@ -100,7 +100,7 @@ interface types { // Additional optional parameters that can be set when making a request. record request-options { // The following timeouts are specific to the HTTP protocol and work - // independently of the overall timeouts passed to `io.poll.poll-oneoff`. + // independently of the overall timeouts passed to `io.poll.poll-list`. // The timeout for the initial connect. connect-timeout-ms: option, @@ -142,19 +142,18 @@ interface types { // incoming-body. incoming-response-consume: func(response: /* borrow */ incoming-response) -> result - type incoming-body = u32 - drop-incoming-body: func(this: /* own */ incoming-body) - - // returned input-stream is a child - the implementation may trap if - // incoming-body is dropped (or consumed by call to - // incoming-body-finish) before the input-stream is dropped. - // May be called at most once. returns error if called additional times. - incoming-body-stream: func(this: /* borrow */ incoming-body) -> - result - // takes ownership of incoming-body. this will trap if the - // incoming-body-stream child is still alive! - incoming-body-finish: func(this: /* own */ incoming-body) -> - /* transitive child of the incoming-response of incoming-body */ future-trailers + resource incoming-body { + // returned input-stream is a child - the implementation may trap if + // incoming-body is dropped (or consumed by call to + // incoming-body-finish) before the input-stream is dropped. + // May be called at most once. returns error if called additional times. + %stream: func() -> + result + // takes ownership of incoming-body. this will trap if the + // incoming-body-stream child is still alive! + finish: func() -> + /* transitive child of the incoming-response of incoming-body */ future-trailers + } type future-trailers = u32 drop-future-trailers: func(this: /* own */ future-trailers) @@ -193,7 +192,7 @@ interface types { /// `future-incoming-response`, the client can call the non-blocking `get` /// method to get the result if it is available. If the result is not available, /// the client can call `listen` to get a `pollable` that can be passed to - /// `io.poll.poll-oneoff`. + /// `wasi:io/poll.poll-list`. type future-incoming-response = u32 drop-future-incoming-response: func(f: /* own */ future-incoming-response) /// option indicates readiness. diff --git a/crates/wasi/wit/deps/io/poll.wit b/crates/wasi/wit/deps/io/poll.wit new file mode 100644 index 000000000000..e95762b915db --- /dev/null +++ b/crates/wasi/wit/deps/io/poll.wit @@ -0,0 +1,34 @@ +package wasi:io + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// A "pollable" handle. + resource pollable + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll-list: func(in: list>) -> list + + /// Poll for completion on a single pollable. + /// + /// This function is similar to `poll-list`, but operates on only a single + /// pollable. When it returns, the handle is ready for I/O. + poll-one: func(in: borrow) +} diff --git a/crates/wasi/wit/deps/io/streams.wit b/crates/wasi/wit/deps/io/streams.wit index e2631f66a569..eeeff505890a 100644 --- a/crates/wasi/wit/deps/io/streams.wit +++ b/crates/wasi/wit/deps/io/streams.wit @@ -6,7 +6,7 @@ package wasi:io /// In the future, the component model is expected to add built-in stream types; /// when it does, they are expected to subsume this API. interface streams { - use wasi:poll/poll.{pollable} + use poll.{pollable} /// Streams provide a sequence of data and then end; once they end, they /// no longer provide any further data. @@ -24,115 +24,81 @@ interface streams { ended, } - /// An input bytestream. In the future, this will be replaced by handle - /// types. + /// An input bytestream. /// /// `input-stream`s are *non-blocking* to the extent practical on underlying /// platforms. I/O operations always return promptly; if fewer bytes are /// promptly available than requested, they return the number of bytes promptly /// available, which could even be zero. To wait for data to be available, - /// use the `subscribe-to-input-stream` function to obtain a `pollable` which - /// can be polled for using `wasi:poll/poll.poll_oneoff`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type input-stream = u32 - - /// Perform a non-blocking read from the stream. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a `stream-status` which, indicates whether further - /// reads are expected to produce data. The returned list will contain up to - /// `len` bytes; it may return fewer than requested, but not more. An - /// empty list and `stream-status:open` indicates no more data is - /// available at this time, and that the pollable given by - /// `subscribe-to-input-stream` will be ready when more data is available. - /// - /// Once a stream has reached the end, subsequent calls to `read` or - /// `skip` will always report `stream-status:ended` rather than producing more - /// data. - /// - /// When the caller gives a `len` of 0, it represents a request to read 0 - /// bytes. This read should always succeed and return an empty list and - /// the current `stream-status`. - /// - /// The `len` parameter is a `u64`, which could represent a list of u8 which - /// is not possible to allocate in wasm32, or not desirable to allocate as - /// as a return value by the callee. The callee may return a list of bytes - /// less than `len` in size while more bytes are available for reading. - read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>> - - /// Read bytes from a stream, after blocking until at least one byte can - /// be read. Except for blocking, identical to `read`. - blocking-read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>> - - /// Skip bytes from a stream. - /// - /// This is similar to the `read` function, but avoids copying the - /// bytes into the instance. - /// - /// Once a stream has reached the end, subsequent calls to read or - /// `skip` will always report end-of-stream rather than producing more - /// data. - /// - /// This function returns the number of bytes skipped, along with a - /// `stream-status` indicating whether the end of the stream was - /// reached. The returned value will be at most `len`; it may be less. - skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result> + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a `stream-status` which, indicates whether further + /// reads are expected to produce data. The returned list will contain up to + /// `len` bytes; it may return fewer than requested, but not more. An + /// empty list and `stream-status:open` indicates no more data is + /// available at this time, and that the pollable given by `subscribe` + /// will be ready when more data is available. + /// + /// Once a stream has reached the end, subsequent calls to `read` or + /// `skip` will always report `stream-status:ended` rather than producing more + /// data. + /// + /// When the caller gives a `len` of 0, it represents a request to read 0 + /// bytes. This read should always succeed and return an empty list and + /// the current `stream-status`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-status>> - /// Skip bytes from a stream, after blocking until at least one byte - /// can be skipped. Except for blocking behavior, identical to `skip`. - blocking-skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result> + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-status>> - /// Create a `pollable` which will resolve once either the specified stream - /// has bytes available to read or the other end of the stream has been - /// closed. - /// The created `pollable` is a child resource of the `input-stream`. - /// Implementations may trap if the `input-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe-to-input-stream: func(this: input-stream) -> pollable + /// Skip bytes from a stream. + /// + /// This is similar to the `read` function, but avoids copying the + /// bytes into the instance. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// This function returns the number of bytes skipped, along with a + /// `stream-status` indicating whether the end of the stream was + /// reached. The returned value will be at most `len`; it may be less. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result> - /// Dispose of the specified `input-stream`, after which it may no longer - /// be used. - /// Implementations may trap if this `input-stream` is dropped while child - /// `pollable` resources are still alive. - /// After this `input-stream` is dropped, implementations may report any - /// corresponding `output-stream` has `stream-state.closed`. - drop-input-stream: func(this: input-stream) + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result> - /// An output bytestream. In the future, this will be replaced by handle - /// types. - /// - /// `output-stream`s are *non-blocking* to the extent practical on - /// underlying platforms. Except where specified otherwise, I/O operations also - /// always return promptly, after the number of bytes that can be written - /// promptly, which could even be zero. To wait for the stream to be ready to - /// accept data, the `subscribe-to-output-stream` function to obtain a - /// `pollable` which can be polled for using `wasi:poll`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type output-stream = u32 + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable + } /// An error for output-stream operations. /// @@ -146,155 +112,174 @@ interface streams { /// future operations. closed } - /// Check readiness for writing. This function never blocks. - /// - /// Returns the number of bytes permitted for the next call to `write`, - /// or an error. Calling `write` with more bytes than this function has - /// permitted will trap. - /// - /// When this function returns 0 bytes, the `subscribe-to-output-stream` - /// pollable will become ready when this function will report at least - /// 1 byte, or an error. - check-write: func( - this: output-stream - ) -> result - /// Perform a write. This function never blocks. - /// - /// Precondition: check-write gave permit of Ok(n) and contents has a - /// length of less than or equal to n. Otherwise, this function will trap. + /// An output bytestream. /// - /// returns Err(closed) without writing if the stream has closed since - /// the last call to check-write provided a permit. - write: func( - this: output-stream, - contents: list - ) -> result<_, write-error> + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result - /// Perform a write of up to 4096 bytes, and then flush the stream. Block - /// until all of these operations are complete, or an error occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe-to-output-stream`, `write`, and `flush`, and is implemented - /// with the following pseudo-code: - /// - /// ```text - /// let pollable = subscribe-to-output-stream(this); - /// while !contents.is_empty() { - /// // Wait for the stream to become writable - /// poll-oneoff(pollable); - /// let Ok(n) = check-write(this); // eliding error handling - /// let len = min(n, contents.len()); - /// let (chunk, rest) = contents.split_at(len); - /// write(this, chunk); // eliding error handling - /// contents = rest; - /// } - /// flush(this); - /// // Wait for completion of `flush` - /// poll-oneoff(pollable); - /// // Check for any errors that arose during `flush` - /// let _ = check-write(this); // eliding error handling - /// ``` - blocking-write-and-flush: func( - this: output-stream, - contents: list - ) -> result<_, write-error> + /// Perform a write. This function never blocks. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, write-error> - /// Request to flush buffered output. This function never blocks. - /// - /// This tells the output-stream that the caller intends any buffered - /// output to be flushed. the output which is expected to be flushed - /// is all that has been passed to `write` prior to this call. - /// - /// Upon calling this function, the `output-stream` will not accept any - /// writes (`check-write` will return `ok(0)`) until the flush has - /// completed. The `subscribe-to-output-stream` pollable will become ready - /// when the flush has completed and the stream can accept more writes. - flush: func( - this: output-stream, - ) -> result<_, write-error> + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, write-error> - /// Request to flush buffered output, and block until flush completes - /// and stream is ready for writing again. - blocking-flush: func( - this: output-stream, - ) -> result<_, write-error> + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, write-error> - /// Create a `pollable` which will resolve once the output-stream - /// is ready for more writing, or an error has occured. When this - /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an - /// error. - /// - /// If the stream is closed, this pollable is always ready immediately. - /// - /// The created `pollable` is a child resource of the `output-stream`. - /// Implementations may trap if the `output-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe-to-output-stream: func(this: output-stream) -> pollable + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, write-error> - /// Write zeroes to a stream. - /// - /// this should be used precisely like `write` with the exact same - /// preconditions (must use check-write first), but instead of - /// passing a list of bytes, you simply pass the number of zero-bytes - /// that should be written. - write-zeroes: func( - this: output-stream, - /// The number of zero-bytes to write - len: u64 - ) -> result<_, write-error> + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable - /// Read from one stream and write to another. - /// - /// This function returns the number of bytes transferred; it may be less - /// than `len`. - /// - /// Unlike other I/O functions, this function blocks until all the data - /// read from the input stream has been written to the output stream. - splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result> + /// Write zeroes to a stream. + /// + /// this should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, write-error> - /// Read from one stream and write to another, with blocking. - /// - /// This is similar to `splice`, except that it blocks until at least - /// one byte can be read. - blocking-splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result> + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, write-error> - /// Forward the entire contents of an input stream to an output stream. - /// - /// This function repeatedly reads from the input stream and writes - /// the data to the output stream, until the end of the input stream - /// is reached, or an error is encountered. - /// - /// Unlike other I/O functions, this function blocks until the end - /// of the input stream is seen and all the data has been written to - /// the output stream. - /// - /// This function returns the number of bytes transferred, and the status of - /// the output stream. - forward: func( - this: output-stream, - /// The stream to read from - src: input-stream - ) -> result> + /// Read from one stream and write to another. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + /// + /// Unlike other I/O functions, this function blocks until all the data + /// read from the input stream has been written to the output stream. + splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result> + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until at least + /// one byte can be read. + blocking-splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result> - /// Dispose of the specified `output-stream`, after which it may no longer - /// be used. - /// Implementations may trap if this `output-stream` is dropped while - /// child `pollable` resources are still alive. - /// After this `output-stream` is dropped, implementations may report any - /// corresponding `input-stream` has `stream-state.closed`. - drop-output-stream: func(this: output-stream) + /// Forward the entire contents of an input stream to an output stream. + /// + /// This function repeatedly reads from the input stream and writes + /// the data to the output stream, until the end of the input stream + /// is reached, or an error is encountered. + /// + /// Unlike other I/O functions, this function blocks until the end + /// of the input stream is seen and all the data has been written to + /// the output stream. + /// + /// This function returns the number of bytes transferred, and the status of + /// the output stream. + forward: func( + /// The stream to read from + src: input-stream + ) -> result> + } } diff --git a/crates/wasi/wit/deps/io/world.wit b/crates/wasi/wit/deps/io/world.wit new file mode 100644 index 000000000000..8738dba756d9 --- /dev/null +++ b/crates/wasi/wit/deps/io/world.wit @@ -0,0 +1,6 @@ +package wasi:io + +world imports { + import streams + import poll +} diff --git a/crates/wasi/wit/deps/logging/world.wit b/crates/wasi/wit/deps/logging/world.wit new file mode 100644 index 000000000000..7d49acfaddaa --- /dev/null +++ b/crates/wasi/wit/deps/logging/world.wit @@ -0,0 +1,5 @@ +package wasi:logging + +world imports { + import logging +} diff --git a/crates/wasi/wit/deps/poll/poll.wit b/crates/wasi/wit/deps/poll/poll.wit deleted file mode 100644 index a6334c5570fc..000000000000 --- a/crates/wasi/wit/deps/poll/poll.wit +++ /dev/null @@ -1,39 +0,0 @@ -package wasi:poll - -/// A poll API intended to let users wait for I/O events on multiple handles -/// at once. -interface poll { - /// A "pollable" handle. - /// - /// This is conceptually represents a `stream<_, _>`, or in other words, - /// a stream that one can wait on, repeatedly, but which does not itself - /// produce any data. It's temporary scaffolding until component-model's - /// async features are ready. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// `pollable` lifetimes are not automatically managed. Users must ensure - /// that they do not outlive the resource they reference. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type pollable = u32 - - /// Dispose of the specified `pollable`, after which it may no longer - /// be used. - drop-pollable: func(this: pollable) - - /// Poll for completion on a set of pollables. - /// - /// The "oneoff" in the name refers to the fact that this function must do a - /// linear scan through the entire list of subscriptions, which may be - /// inefficient if the number is large and the same subscriptions are used - /// many times. In the future, this is expected to be obsoleted by the - /// component model async proposal, which will include a scalable waiting - /// facility. - /// - /// The result list is the same length as the argument - /// list, and indicates the readiness of each corresponding - /// element in that / list, with true indicating ready. - poll-oneoff: func(in: list) -> list -} diff --git a/crates/wasi/wit/deps/random/random.wit b/crates/wasi/wit/deps/random/random.wit index f2bd6358c139..2a282dab7eb9 100644 --- a/crates/wasi/wit/deps/random/random.wit +++ b/crates/wasi/wit/deps/random/random.wit @@ -1,25 +1,25 @@ -package wasi:random - /// WASI Random is a random data API. /// /// It is intended to be portable at least between Unix-family platforms and /// Windows. interface random { - /// Return `len` cryptographically-secure pseudo-random bytes. + /// Return `len` cryptographically-secure random or pseudo-random bytes. /// - /// This function must produce data from an adequately seeded - /// cryptographically-secure pseudo-random number generator (CSPRNG), so it - /// must not block, from the perspective of the calling program, and the - /// returned data is always unpredictable. + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. /// - /// This function must always return fresh pseudo-random data. Deterministic - /// environments must omit this function, rather than implementing it with - /// deterministic data. + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. get-random-bytes: func(len: u64) -> list - /// Return a cryptographically-secure pseudo-random `u64` value. + /// Return a cryptographically-secure random or pseudo-random `u64` value. /// - /// This function returns the same type of pseudo-random data as - /// `get-random-bytes`, represented as a `u64`. + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. get-random-u64: func() -> u64 } diff --git a/crates/wasi/wit/deps/random/world.wit b/crates/wasi/wit/deps/random/world.wit new file mode 100644 index 000000000000..41dc9ed10353 --- /dev/null +++ b/crates/wasi/wit/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random + +world imports { + import random + import insecure + import insecure-seed +} diff --git a/crates/wasi/wit/deps/sockets/ip-name-lookup.wit b/crates/wasi/wit/deps/sockets/ip-name-lookup.wit index f15d19d037da..f998aae140ab 100644 --- a/crates/wasi/wit/deps/sockets/ip-name-lookup.wit +++ b/crates/wasi/wit/deps/sockets/ip-name-lookup.wit @@ -1,6 +1,6 @@ interface ip-name-lookup { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-address, ip-address-family} @@ -34,36 +34,28 @@ interface ip-name-lookup { /// - /// - /// - - resolve-addresses: func(network: network, name: string, address-family: option, include-unavailable: bool) -> result - - - - type resolve-address-stream = u32 - - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// After which, you should release the stream with `drop-resolve-address-stream`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - resolve-next-address: func(this: resolve-address-stream) -> result, error-code> - - /// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-resolve-address-stream: func(this: resolve-address-stream) - - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: resolve-address-stream) -> pollable + resolve-addresses: func(network: borrow, name: string, address-family: option, include-unavailable: bool) -> result + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code> + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + } } diff --git a/crates/wasi/wit/deps/sockets/network.wit b/crates/wasi/wit/deps/sockets/network.wit index a198ea8017de..8214eaaf7211 100644 --- a/crates/wasi/wit/deps/sockets/network.wit +++ b/crates/wasi/wit/deps/sockets/network.wit @@ -1,18 +1,9 @@ -package wasi:sockets interface network { - /// An opaque resource that represents access to (a subset of) the network. + /// An opaque resource that represents access to (a subset of) the network. /// This enables context-based security for networking. /// There is no need for this to map 1:1 to a physical network interface. - /// - /// FYI, In the future this will be replaced by handle types. - type network = u32 - - /// Dispose of the specified `network`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-network: func(this: network) - + resource network /// Error codes. /// diff --git a/crates/wasi/wit/deps/sockets/tcp.wit b/crates/wasi/wit/deps/sockets/tcp.wit index 3922769b308e..175626cc7620 100644 --- a/crates/wasi/wit/deps/sockets/tcp.wit +++ b/crates/wasi/wit/deps/sockets/tcp.wit @@ -1,13 +1,9 @@ interface tcp { use wasi:io/streams.{input-stream, output-stream} - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-socket-address, ip-address-family} - /// A TCP socket handle. - type tcp-socket = u32 - - enum shutdown-type { /// Similar to `SHUT_RD` in POSIX. receive, @@ -20,249 +16,234 @@ interface tcp { } - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will - /// implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) - /// - `already-bound`: The socket is already bound. (EINVAL) - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(this: tcp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> - finish-bind: func(this: tcp-socket) -> result<_, error-code> - - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the Connection state - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `already-connected`: The socket is already in the Connection state. (EISCONN) - /// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(this: tcp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> - /// Note: the returned `input-stream` and `output-stream` are child - /// resources of the `tcp-socket`. Implementations may trap if the - /// `tcp-socket` is dropped before both of these streams are dropped. - finish-connect: func(this: tcp-socket) -> result, error-code> - - /// Start listening for new connections. - /// - /// Transitions the socket into the Listener state. - /// - /// Unlike POSIX: - /// - this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - the socket must already be explicitly bound. - /// - /// # Typical `start` errors - /// - `not-bound`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) - /// - `already-listening`: The socket is already in the Listener state. - /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A `listen` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-listen: func(this: tcp-socket) -> result<_, error-code> - finish-listen: func(this: tcp-socket) -> result<_, error-code> - - /// Accept a new client socket. - /// - /// The returned socket is bound and in the Connection state. - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// Note: the returned `input-stream` and `output-stream` are child - /// resources of the returned `tcp-socket`. Implementations may trap if the - /// `tcp-socket` is dropped before its child streams are dropped. - /// - /// # Typical errors - /// - `not-listening`: Socket is not in the Listener state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - /// Host implementations must skip over transient errors returned by the native accept syscall. - /// - /// # References - /// - - /// - - /// - - /// - - accept: func(this: tcp-socket) -> result, error-code> - - /// Get the bound local address. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func(this: tcp-socket) -> result - - /// Get the bound remote address. - /// - /// # Typical errors - /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func(this: tcp-socket) -> result - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func(this: tcp-socket) -> ip-address-family + /// A TCP socket handle. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func() -> result<_, error-code> + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `already-connected`: The socket is already in the Connection state. (EISCONN) + /// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func() -> result, error-code> + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike POSIX: + /// - this function is async. This enables interactive WASI hosts to inject permission prompts. + /// - the socket must already be explicitly bound. + /// + /// # Typical `start` errors + /// - `not-bound`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `already-listening`: The socket is already in the Listener state. + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code> + finish-listen: func() -> result<_, error-code> + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `not-listening`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// Host implementations must skip over transient errors returned by the native accept syscall. + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code> + + /// Get the bound local address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result + + /// Get the bound remote address. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. - /// - `already-bound`: (set) The socket is already bound. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - ipv6-only: func(this: tcp-socket) -> result - set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error-code> - - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - set-listen-backlog-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - - /// Equivalent to the SO_KEEPALIVE socket option. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - keep-alive: func(this: tcp-socket) -> result - set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error-code> - - /// Equivalent to the TCP_NODELAY socket option. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - no-delay: func(this: tcp-socket) -> result - set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error-code> + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + ipv6-only: func() -> result + set-ipv6-only: func(value: bool) -> result<_, error-code> + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + set-listen-backlog-size: func(value: u64) -> result<_, error-code> + + /// Equivalent to the SO_KEEPALIVE socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + keep-alive: func() -> result + set-keep-alive: func(value: bool) -> result<_, error-code> + + /// Equivalent to the TCP_NODELAY socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + no-delay: func() -> result + set-no-delay: func(value: bool) -> result<_, error-code> - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `already-listening`: (set) The socket is already in the Listener state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - unicast-hop-limit: func(this: tcp-socket) -> result - set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error-code> - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `already-connected`: (set) The socket is already in the Connection state. - /// - `already-listening`: (set) The socket is already in the Listener state. - /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) - receive-buffer-size: func(this: tcp-socket) -> result - set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - send-buffer-size: func(this: tcp-socket) -> result - set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// The created `pollable` is a child resource of the `tcp-socket`. - /// Implementations may trap if the `tcp-socket` is dropped before all - /// derived `pollable`s created with this function are dropped. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: tcp-socket) -> pollable - - /// Initiate a graceful shutdown. - /// - /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read - /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. - /// Any data still in the receive queue at time of calling `shutdown` will be discarded. - /// - send: the socket is not expecting to send any more data to the peer. All subsequent write - /// operations on the `output-stream` associated with this socket will return an error. - /// - both: same effect as receive & send combined. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error-code> - - /// Dispose of the specified `tcp-socket`, after which it may no longer be used. - /// - /// Similar to the POSIX `close` function. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-tcp-socket: func(this: tcp-socket) + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + unicast-hop-limit: func() -> result + set-unicast-hop-limit: func(value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + receive-buffer-size: func() -> result + set-receive-buffer-size: func(value: u64) -> result<_, error-code> + send-buffer-size: func() -> result + set-send-buffer-size: func(value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + + /// Initiate a graceful shutdown. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> + } } diff --git a/crates/wasi/wit/deps/sockets/udp.wit b/crates/wasi/wit/deps/sockets/udp.wit index 700b9e247692..01e5b95b97b7 100644 --- a/crates/wasi/wit/deps/sockets/udp.wit +++ b/crates/wasi/wit/deps/sockets/udp.wit @@ -1,13 +1,9 @@ interface udp { - use wasi:poll/poll.{pollable} + use wasi:io/poll.{pollable} use network.{network, error-code, ip-socket-address, ip-address-family} - /// A UDP socket handle. - type udp-socket = u32 - - record datagram { data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. remote-address: ip-socket-address, @@ -22,199 +18,197 @@ interface udp { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) - /// - `already-bound`: The socket is already bound. (EINVAL) - /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(this: udp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> - finish-bind: func(this: udp-socket) -> result<_, error-code> - - /// Set the destination address. - /// - /// The local-address is updated based on the best network path to `remote-address`. - /// - /// When a destination address is set: - /// - all receive operations will only return datagrams sent from the provided `remote-address`. - /// - the `send` function can only be used to send to this destination. - /// - /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) - /// - /// # Typical `finish` errors - /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(this: udp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> - finish-connect: func(this: udp-socket) -> result<_, error-code> - - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// If `max-results` is 0, this function returns successfully with an empty list. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. (EINVAL) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - receive: func(this: udp-socket, max-results: u64) -> result, error-code> - - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without blocking and - /// returns how many messages were actually sent (or queued for sending). - /// - /// This function semantically behaves the same as iterating the `datagrams` list and sequentially - /// sending each individual datagram until either the end of the list has been reached or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// The remote address option is required. To send a message to the "connected" peer, - /// call `remote-address` to get their address. - /// - /// # Typical errors - /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) - /// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - send: func(this: udp-socket, datagrams: list) -> result - - /// Get the current bound address. - /// - /// # Typical errors - /// - `not-bound`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func(this: udp-socket) -> result - - /// Get the address set with `connect`. - /// - /// # Typical errors - /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func(this: udp-socket) -> result - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func(this: udp-socket) -> ip-address-family - - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. - /// - `already-bound`: (set) The socket is already bound. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - ipv6-only: func(this: udp-socket) -> result - set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error-code> - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - unicast-hop-limit: func(this: udp-socket) -> result - set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error-code> - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) - receive-buffer-size: func(this: udp-socket) -> result - set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> - send-buffer-size: func(this: udp-socket) -> result - set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func(this: udp-socket) -> pollable - - /// Dispose of the specified `udp-socket`, after which it may no longer be used. - /// - /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. - drop-udp-socket: func(this: udp-socket) + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func() -> result<_, error-code> + + /// Set the destination address. + /// + /// The local-address is updated based on the best network path to `remote-address`. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can only be used to send to this destination. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func() -> result<_, error-code> + + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// If `max-results` is 0, this function returns successfully with an empty list. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. (EINVAL) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code> + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// # Typical errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) + /// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result + + /// Get the current bound address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result + + /// Get the address set with `connect`. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + ipv6-only: func() -> result + set-ipv6-only: func(value: bool) -> result<_, error-code> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + unicast-hop-limit: func() -> result + set-unicast-hop-limit: func(value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + receive-buffer-size: func() -> result + set-receive-buffer-size: func(value: u64) -> result<_, error-code> + send-buffer-size: func() -> result + set-send-buffer-size: func(value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable + } } diff --git a/crates/wasi/wit/deps/sockets/world.wit b/crates/wasi/wit/deps/sockets/world.wit new file mode 100644 index 000000000000..12f3c2868177 --- /dev/null +++ b/crates/wasi/wit/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets + +world imports { + import instance-network + import network + import udp + import udp-create-socket + import tcp + import tcp-create-socket + import ip-name-lookup +} diff --git a/crates/wasi/wit/main.wit b/crates/wasi/wit/main.wit index 753770ad22ab..739e1bd4ac48 100644 --- a/crates/wasi/wit/main.wit +++ b/crates/wasi/wit/main.wit @@ -18,7 +18,7 @@ world preview1-adapter-reactor { import wasi:random/random import wasi:random/insecure import wasi:random/insecure-seed - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/exit diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit index 4543cb194af1..0b6bd28e997d 100644 --- a/crates/wasi/wit/test.wit +++ b/crates/wasi/wit/test.wit @@ -2,6 +2,7 @@ world test-reactor { import wasi:cli/environment + import wasi:io/poll import wasi:io/streams import wasi:filesystem/types import wasi:filesystem/preopens @@ -19,7 +20,7 @@ world test-reactor { } world test-command { - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/stdin @@ -28,7 +29,7 @@ world test-command { } world test-command-with-sockets { - import wasi:poll/poll + import wasi:io/poll import wasi:io/streams import wasi:cli/environment import wasi:cli/stdin diff --git a/crates/wasmtime/src/component/resources.rs b/crates/wasmtime/src/component/resources.rs index ded0665a66a1..9ed13faf3208 100644 --- a/crates/wasmtime/src/component/resources.rs +++ b/crates/wasmtime/src/component/resources.rs @@ -208,7 +208,7 @@ pub struct Resource { /// discard this resource when the borrow duration has finished. /// /// * `NOT_IN_TABLE` / `u32::MAX - 1` - this indicates that this is an owned - /// resource not present in any store's stable. This resource is not lent + /// resource not present in any store's table. This resource is not lent /// out. It can be passed as an `(own $t)` directly into a guest's table /// or it can be passed as a borrow to a guest which will insert it into /// a host store's table for dynamic borrow tracking. @@ -451,7 +451,16 @@ unsafe impl Lift for Resource { impl fmt::Debug for Resource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Resource").field("rep", &self.rep).finish() + let state = match self.state.load(Relaxed) { + BORROW => "borrow", + NOT_IN_TABLE => "own (not in table)", + TAKEN => "taken", + _ => "own", + }; + f.debug_struct("Resource") + .field("rep", &self.rep) + .field("state", &state) + .finish() } } diff --git a/src/commands/run.rs b/src/commands/run.rs index 93c67c1463b1..a75dfdb6c2a1 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -746,7 +746,7 @@ impl RunCommand { #[cfg(feature = "component-model")] fn ensure_allow_components(&self) -> Result<()> { if self.common.wasm.component_model != Some(true) { - bail!("cannot execute a component without `--wasm-features component-model`"); + bail!("cannot execute a component without `--wasm component-model`"); } Ok(()) @@ -937,10 +937,7 @@ impl RunCommand { builder.inherit_network(ambient_authority()); } - let data = store.data_mut(); - let table = Arc::get_mut(&mut data.preview2_table).unwrap(); - let ctx = builder.build(table)?; - data.preview2_ctx = Some(Arc::new(ctx)); + store.data_mut().preview2_ctx = Some(Arc::new(builder.build())); Ok(()) } } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 90803257ae06..075dd2052ca5 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -3277,6 +3277,12 @@ user-id = 1 # Alex Crichton (alexcrichton) start = "2019-03-04" end = "2024-07-14" +[[trusted.wasmtime-wmemcheck]] +criteria = "safe-to-deploy" +user-id = 73222 # wasmtime-publish +start = "2023-09-20" +end = "2024-09-27" + [[trusted.winapi-util]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 3908247ffcec..c1301305ab8d 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1912,6 +1912,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen]] +version = "0.12.0" +when = "2023-09-18" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-bindgen-core]] version = "0.9.0" when = "2023-07-15" @@ -1926,6 +1933,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen-core]] +version = "0.12.0" +when = "2023-09-18" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-bindgen-rust]] version = "0.9.0" when = "2023-07-15" @@ -1940,6 +1954,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen-rust]] +version = "0.12.0" +when = "2023-09-18" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-bindgen-rust-lib]] version = "0.9.0" when = "2023-07-15" @@ -1954,6 +1975,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen-rust-lib]] +version = "0.12.0" +when = "2023-09-18" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-bindgen-rust-macro]] version = "0.9.0" when = "2023-07-15" @@ -1968,6 +1996,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen-rust-macro]] +version = "0.12.0" +when = "2023-09-18" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-component]] version = "0.12.0" when = "2023-07-11" diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 4f7dd4968dc0..da2e7adedc97 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -737,7 +737,7 @@ fn component_missing_feature() -> Result<()> { assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("cannot execute a component without `--wasm-features component-model`"), + stderr.contains("cannot execute a component without `--wasm component-model`"), "bad stderr: {stderr}" ); @@ -749,7 +749,7 @@ fn component_missing_feature() -> Result<()> { assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("cannot execute a component without `--wasm-features component-model`"), + stderr.contains("cannot execute a component without `--wasm component-model`"), "bad stderr: {stderr}" );