Browse Source

Sync wasi-cli with wit definitions in standards repo (#6806)

* rename wasi-cli-base to wasi-cli, delete `preview` package, import wasi-cli

wasi-cli import is sum of
https://github.com/WebAssembly/wasi-cli/pull/19 and
https://github.com/WebAssembly/wasi-cli/pull/20

* wasi impl: change bindgen arguments and mod paths from cli_base to cli

* correct name of wasi-cli deps dir to just `deps/cli/`

it turns out this isnt semantically meaningful, since the package name
is in the document itself now, but lets be consistient

* track whether stdio isatty in ctx, and impl the cli/terminal-* interfaces

* rebase fixup

* wasi wits: define the reactor adapter's world

* component adapter: fixes

* test-programs/command-tests: fix renaming cli_base to cli

* component adapter: fix manually-defined export and import names

* test harness fixes

* preview1 component adapter: fill in isatty detection

* implement isatty in preview2-to-preview1 host adapter

* test-programs: cover both when stdio isatty and not

prtest:full

* split isatty test for regular file and stdio, detect host stdio is_terminal

CI environments vary - let the test runner make sure the host process's
stdio is in fact a terminal before asserting that the guest sees it is a
terminal.

* provide an is-terminal impl for all preview2's stdio types

which means making a newtype around Stdout and Stderr instead of using
a type alias there.

and then use the is-terminal impl to fill in the isatty field in the
builder when inheriting. if you need to override it you can always
builder.stdin(stdio::stdin(), your_own_idea_of_isatty)

* finally, rename IsATTY variants to Yes and No

* Fix the reference to IsATTY::No

* more forgotten renamings

---------

Co-authored-by: Trevor Elliott <telliott@fastly.com>
pull/6846/head
Pat Hickey 1 year ago
committed by GitHub
parent
commit
e3b4954610
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Cargo.lock
  2. 2
      Cargo.toml
  3. 2
      crates/test-programs/Cargo.toml
  4. 4
      crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs
  5. 7
      crates/test-programs/src/lib.rs
  6. 46
      crates/test-programs/tests/command.rs
  7. 30
      crates/test-programs/tests/reactor.rs
  8. 17
      crates/test-programs/tests/wasi-cap-std-sync.rs
  9. 23
      crates/test-programs/tests/wasi-preview1-host-in-preview2.rs
  10. 24
      crates/test-programs/tests/wasi-preview2-components-sync.rs
  11. 24
      crates/test-programs/tests/wasi-preview2-components.rs
  12. 17
      crates/test-programs/tests/wasi-tokio.rs
  13. 4
      crates/test-programs/wasi-tests/src/bin/regular_file_isatty.rs
  14. 22
      crates/test-programs/wasi-tests/src/bin/stdio_isatty.rs
  15. 22
      crates/test-programs/wasi-tests/src/bin/stdio_not_isatty.rs
  16. 2
      crates/wasi-common/cap-std-sync/Cargo.toml
  17. 50
      crates/wasi-preview1-component-adapter/src/descriptors.rs
  18. 37
      crates/wasi-preview1-component-adapter/src/lib.rs
  19. 2
      crates/wasi/Cargo.toml
  20. 64
      crates/wasi/src/preview2/command.rs
  21. 81
      crates/wasi/src/preview2/ctx.rs
  22. 23
      crates/wasi/src/preview2/host/env.rs
  23. 2
      crates/wasi/src/preview2/host/exit.rs
  24. 18
      crates/wasi/src/preview2/mod.rs
  25. 215
      crates/wasi/src/preview2/preview1.rs
  26. 123
      crates/wasi/src/preview2/stdio.rs
  27. 6
      crates/wasi/src/preview2/stdio/unix.rs
  28. 6
      crates/wasi/src/preview2/stdio/worker_thread_stdin.rs
  29. 20
      crates/wasi/wit/deps/cli/command.wit
  30. 6
      crates/wasi/wit/deps/cli/environment.wit
  31. 4
      crates/wasi/wit/deps/cli/exit.wit
  32. 4
      crates/wasi/wit/deps/cli/run.wit
  33. 0
      crates/wasi/wit/deps/cli/stdio.wit
  34. 59
      crates/wasi/wit/deps/cli/terminal.wit
  35. 36
      crates/wasi/wit/deps/preview/command-extended.wit
  36. 9
      crates/wasi/wit/deps/preview/proxy.wit
  37. 24
      crates/wasi/wit/deps/preview/reactor.wit
  38. 4
      crates/wasi/wit/deps/wasi-cli-base/exit.wit
  39. 34
      crates/wasi/wit/main.wit
  40. 12
      crates/wasi/wit/test.wit

2
Cargo.lock

@ -2648,6 +2648,7 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"is-terminal",
"lazy_static",
"tempfile",
"test-log",
@ -3733,6 +3734,7 @@ dependencies = [
"fs-set-times",
"futures",
"io-extras",
"is-terminal",
"libc",
"once_cell",
"rustix 0.38.4",

2
Cargo.toml

@ -195,7 +195,7 @@ system-interface = { version = "0.26.0", features = ["cap_std_impls"] }
io-lifetimes = { version = "2.0.2", default-features = false }
io-extras = "0.18.0"
rustix = "0.38.4"
is-terminal = "0.4.0"
# wit-bindgen:
wit-bindgen = { version = "0.9.0", default-features = false }

2
crates/test-programs/Cargo.toml

@ -13,6 +13,8 @@ cargo_metadata = "0.15.3"
wit-component = { workspace = true }
heck = { workspace = true }
[dependencies]
is-terminal = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }

4
crates/test-programs/command-tests/src/bin/stream_pollable_lifetimes.rs

@ -1,5 +1,5 @@
use command_tests::wasi::cli_base::environment;
use command_tests::wasi::cli_base::stdin;
use command_tests::wasi::cli::environment;
use command_tests::wasi::cli::stdin;
use command_tests::wasi::io::streams;
use command_tests::wasi::poll::poll;

7
crates/test-programs/src/lib.rs

@ -35,3 +35,10 @@ pub fn wasi_tests_environment() -> &'static [(&'static str, &'static str)] {
]
}
}
pub fn stdio_is_terminal() -> bool {
use is_terminal::is_terminal;
is_terminal(&std::io::stdin())
&& is_terminal(&std::io::stdout())
&& is_terminal(&std::io::stderr())
}

46
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, Table, WasiCtx, WasiCtxBuilder,
DirPerms, FilePerms, HostMonotonicClock, HostWallClock, IsATTY, Table, WasiCtx, WasiCtxBuilder,
WasiView,
};
@ -68,6 +68,7 @@ async fn hello_stdout() -> Result<()> {
let (mut store, command) =
instantiate(get_component("hello_stdout"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -90,7 +91,7 @@ async fn panic() -> Result<()> {
.build(&mut table)?;
let (mut store, command) =
instantiate(get_component("panic"), CommandCtx { table, wasi }).await?;
let r = command.call_run(&mut store).await;
let r = command.wasi_cli_run().call_run(&mut store).await;
assert!(r.is_err());
println!("{:?}", r);
Ok(())
@ -105,6 +106,7 @@ async fn args() -> Result<()> {
let (mut store, command) =
instantiate(get_component("args"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -118,6 +120,7 @@ async fn random() -> Result<()> {
instantiate(get_component("random"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -164,6 +167,7 @@ async fn time() -> Result<()> {
instantiate(get_component("time"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -173,15 +177,17 @@ async fn time() -> Result<()> {
async fn stdin() -> Result<()> {
let mut table = Table::new();
let wasi = WasiCtxBuilder::new()
.stdin(MemoryInputPipe::new(
"So rested he by the Tumtum tree".into(),
))
.stdin(
MemoryInputPipe::new("So rested he by the Tumtum tree".into()),
IsATTY::No,
)
.build(&mut table)?;
let (mut store, command) =
instantiate(get_component("stdin"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -191,15 +197,17 @@ async fn stdin() -> Result<()> {
async fn poll_stdin() -> Result<()> {
let mut table = Table::new();
let wasi = WasiCtxBuilder::new()
.stdin(MemoryInputPipe::new(
"So rested he by the Tumtum tree".into(),
))
.stdin(
MemoryInputPipe::new("So rested he by the Tumtum tree".into()),
IsATTY::No,
)
.build(&mut table)?;
let (mut store, command) =
instantiate(get_component("poll_stdin"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -217,6 +225,7 @@ async fn env() -> Result<()> {
instantiate(get_component("env"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -239,6 +248,7 @@ async fn file_read() -> Result<()> {
instantiate(get_component("file_read"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -261,6 +271,7 @@ async fn file_append() -> Result<()> {
let (mut store, command) =
instantiate(get_component("file_append"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))?;
@ -294,6 +305,7 @@ async fn file_dir_sync() -> Result<()> {
instantiate(get_component("file_dir_sync"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -307,7 +319,7 @@ async fn exit_success() -> Result<()> {
let (mut store, command) =
instantiate(get_component("exit_success"), CommandCtx { table, wasi }).await?;
let r = command.call_run(&mut store).await;
let r = command.wasi_cli_run().call_run(&mut store).await;
let err = r.unwrap_err();
let status = err
.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
@ -324,7 +336,7 @@ async fn exit_default() -> Result<()> {
let (mut store, command) =
instantiate(get_component("exit_default"), CommandCtx { table, wasi }).await?;
let r = command.call_run(&mut store).await?;
let r = command.wasi_cli_run().call_run(&mut store).await?;
assert!(r.is_ok());
Ok(())
}
@ -337,7 +349,7 @@ async fn exit_failure() -> Result<()> {
let (mut store, command) =
instantiate(get_component("exit_failure"), CommandCtx { table, wasi }).await?;
let r = command.call_run(&mut store).await;
let r = command.wasi_cli_run().call_run(&mut store).await;
let err = r.unwrap_err();
let status = err
.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
@ -354,7 +366,7 @@ async fn exit_panic() -> Result<()> {
let (mut store, command) =
instantiate(get_component("exit_panic"), CommandCtx { table, wasi }).await?;
let r = command.call_run(&mut store).await;
let r = command.wasi_cli_run().call_run(&mut store).await;
let err = r.unwrap_err();
// The panic should trap.
assert!(err
@ -387,6 +399,7 @@ async fn directory_list() -> Result<()> {
instantiate(get_component("directory_list"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -401,6 +414,7 @@ async fn default_clocks() -> Result<()> {
instantiate(get_component("default_clocks"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -417,6 +431,7 @@ async fn export_cabi_realloc() -> Result<()> {
.await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -439,6 +454,7 @@ async fn read_only() -> Result<()> {
instantiate(get_component("read_only"), CommandCtx { table, wasi }).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))
@ -452,7 +468,7 @@ async fn stream_pollable_lifetimes() -> Result<()> {
let mut table = Table::new();
let wasi = WasiCtxBuilder::new()
.args(&["correct"])
.stdin(MemoryInputPipe::new(" ".into()))
.stdin(MemoryInputPipe::new(" ".into()), IsATTY::No)
.build(&mut table)?;
let (mut store, command) = instantiate(
@ -462,6 +478,7 @@ async fn stream_pollable_lifetimes() -> Result<()> {
.await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("command returned with failing exit status"))?;
@ -471,7 +488,7 @@ async fn stream_pollable_lifetimes() -> Result<()> {
let mut table = Table::new();
let wasi = WasiCtxBuilder::new()
.args(&["trap"])
.stdin(MemoryInputPipe::new(" ".into()))
.stdin(MemoryInputPipe::new(" ".into()), IsATTY::No)
.build(&mut table)?;
let (mut store, command) = instantiate(
@ -481,6 +498,7 @@ async fn stream_pollable_lifetimes() -> Result<()> {
.await?;
let trap = command
.wasi_cli_run()
.call_run(&mut store)
.await
.err()

30
crates/test-programs/tests/reactor.rs

@ -30,11 +30,16 @@ wasmtime::component::bindgen!({
"wasi:io/streams": preview2::bindings::io::streams,
"wasi:filesystem/types": preview2::bindings::filesystem::types,
"wasi:filesystem/preopens": preview2::bindings::filesystem::preopens,
"wasi:cli-base/environment": preview2::bindings::cli_base::environment,
"wasi:cli-base/exit": preview2::bindings::cli_base::exit,
"wasi:cli-base/stdin": preview2::bindings::cli_base::stdin,
"wasi:cli-base/stdout": preview2::bindings::cli_base::stdout,
"wasi:cli-base/stderr": preview2::bindings::cli_base::stderr,
"wasi:cli/environment": preview2::bindings::cli::environment,
"wasi:cli/exit": preview2::bindings::cli::exit,
"wasi:cli/stdin": preview2::bindings::cli::stdin,
"wasi:cli/stdout": preview2::bindings::cli::stdout,
"wasi:cli/stderr": preview2::bindings::cli::stderr,
"wasi:cli/terminal_input": preview2::bindings::cli::terminal_input,
"wasi:cli/terminal_output": preview2::bindings::cli::terminal_output,
"wasi:cli/terminal_stdin": preview2::bindings::cli::terminal_stdin,
"wasi:cli/terminal_stdout": preview2::bindings::cli::terminal_stdout,
"wasi:cli/terminal_stderr": preview2::bindings::cli::terminal_stderr,
},
ownership: Borrowing {
duplicate_if_necessary: false
@ -71,11 +76,16 @@ async fn instantiate(
preview2::bindings::filesystem::types::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::filesystem::preopens::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::io::streams::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli_base::environment::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli_base::exit::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli_base::stdin::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli_base::stdout::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli_base::stderr::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::environment::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::exit::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::stdin::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::stdout::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::stderr::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::terminal_input::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::terminal_output::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::terminal_stdin::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::terminal_stdout::add_to_linker(&mut linker, |x| x)?;
preview2::bindings::cli::terminal_stderr::add_to_linker(&mut linker, |x| x)?;
let mut store = Store::new(&ENGINE, wasi_ctx);

17
crates/test-programs/tests/wasi-cap-std-sync.rs

@ -160,8 +160,8 @@ fn interesting_paths() {
run("interesting_paths", true).unwrap()
}
#[test_log::test]
fn isatty() {
run("isatty", true).unwrap()
fn regular_file_isatty() {
run("regular_file_isatty", true).unwrap()
}
#[test_log::test]
fn nofollow_errors() {
@ -253,6 +253,19 @@ fn sched_yield() {
fn stdio() {
run("stdio", true).unwrap()
}
#[test_log::test]
fn stdio_isatty() {
if test_programs::stdio_is_terminal() {
// Inherit stdio, which is a terminal in the test runner's environment:
run("stdio_isatty", true).unwrap()
}
}
#[test_log::test]
fn stdio_not_isatty() {
// Don't inherit stdio, test asserts each is not tty:
run("stdio_not_isatty", false).unwrap()
}
#[test_log::test]
fn symlink_create() {
run("symlink_create", true).unwrap()

23
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, WasiPreview1Adapter, WasiPreview1View},
DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView,
DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
};
lazy_static::lazy_static! {
@ -43,7 +43,9 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> {
if inherit_stdio {
builder.inherit_stdio();
} else {
builder.stdout(stdout.clone()).stderr(stderr.clone());
builder
.stdout(stdout.clone(), IsATTY::No)
.stderr(stderr.clone(), IsATTY::No);
}
builder.args(&[name, "."]);
println!("preopen: {:?}", workspace);
@ -193,8 +195,8 @@ async fn interesting_paths() {
run("interesting_paths", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn isatty() {
run("isatty", false).await.unwrap()
async fn regular_file_isatty() {
run("regular_file_isatty", true).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn nofollow_errors() {
@ -296,6 +298,19 @@ async fn stdio() {
run("stdio", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_isatty() {
// If the test process is setup such that stdio is a terminal:
if test_programs::stdio_is_terminal() {
// Inherit stdio, test asserts each is a tty:
run("stdio_isatty", true).await.unwrap()
}
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_not_isatty() {
// Don't inherit stdio, test asserts each is not tty:
run("stdio_not_isatty", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn symlink_create() {
run("symlink_create", false).await.unwrap()
}

24
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, Table, WasiCtx, WasiCtxBuilder, WasiView,
DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
};
lazy_static::lazy_static! {
@ -43,7 +43,9 @@ fn run(name: &str, inherit_stdio: bool) -> Result<()> {
if inherit_stdio {
builder.inherit_stdio();
} else {
builder.stdout(stdout.clone()).stderr(stderr.clone());
builder
.stdout(stdout.clone(), IsATTY::No)
.stderr(stderr.clone(), IsATTY::No);
}
builder.args(&[name, "."]);
println!("preopen: {:?}", workspace);
@ -79,6 +81,7 @@ fn run(name: &str, inherit_stdio: bool) -> Result<()> {
let mut store = Store::new(&ENGINE, ctx);
let (command, _instance) = Command::instantiate(&mut store, &get_component(name), &linker)?;
command
.wasi_cli_run()
.call_run(&mut store)?
.map_err(|()| anyhow::anyhow!("run returned a failure"))?;
Ok(())
@ -178,8 +181,8 @@ fn interesting_paths() {
run("interesting_paths", false).unwrap()
}
#[test_log::test]
fn isatty() {
run("isatty", false).unwrap()
fn regular_file_isatty() {
run("regular_file_isatty", false).unwrap()
}
#[test_log::test]
fn nofollow_errors() {
@ -273,6 +276,19 @@ fn stdio() {
run("stdio", false).unwrap()
}
#[test_log::test]
fn stdio_isatty() {
// If the test process is setup such that stdio is a terminal:
if test_programs::stdio_is_terminal() {
// Inherit stdio, test asserts each is a tty:
run("stdio_isatty", true).unwrap()
}
}
#[test_log::test]
fn stdio_not_isatty() {
// Don't inherit stdio, test asserts not isatty:
run("stdio_not_isatty", false).unwrap()
}
#[test_log::test]
fn symlink_create() {
run("symlink_create", false).unwrap()
}

24
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, Table, WasiCtx, WasiCtxBuilder, WasiView,
DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
};
lazy_static::lazy_static! {
@ -43,7 +43,9 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> {
if inherit_stdio {
builder.inherit_stdio();
} else {
builder.stdout(stdout.clone()).stderr(stderr.clone());
builder
.stdout(stdout.clone(), IsATTY::No)
.stderr(stderr.clone(), IsATTY::No);
}
builder.args(&[name, "."]);
println!("preopen: {:?}", workspace);
@ -80,6 +82,7 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> {
let (command, _instance) =
Command::instantiate_async(&mut store, &get_component(name), &linker).await?;
command
.wasi_cli_run()
.call_run(&mut store)
.await?
.map_err(|()| anyhow::anyhow!("run returned a failure"))?;
@ -180,8 +183,8 @@ async fn interesting_paths() {
run("interesting_paths", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn isatty() {
run("isatty", false).await.unwrap()
async fn regular_file_isatty() {
run("regular_file_isatty", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn nofollow_errors() {
@ -281,6 +284,19 @@ async fn stdio() {
run("stdio", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_isatty() {
// If the test process is setup such that stdio is a terminal:
if test_programs::stdio_is_terminal() {
// Inherit stdio, test asserts each is not tty:
run("stdio_isatty", true).await.unwrap()
}
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_not_isatty() {
// Don't inherit stdio, test asserts each is not tty:
run("stdio_not_isatty", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn symlink_create() {
run("symlink_create", false).await.unwrap()
}

17
crates/test-programs/tests/wasi-tokio.rs

@ -162,8 +162,8 @@ async fn interesting_paths() {
run("interesting_paths", true).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn isatty() {
run("isatty", true).await.unwrap()
async fn regular_file_isatty() {
run("regular_file_isatty", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn nofollow_errors() {
@ -260,6 +260,19 @@ async fn stdio() {
run("stdio", true).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_isatty() {
// Only a valid test if the host executable's stdio is a terminal:
if test_programs::stdio_is_terminal() {
// Inherit stdio, test asserts it is a tty:
run("stdio_isatty", true).await.unwrap()
}
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn stdio_not_isatty() {
// Don't inherit stdio, test asserts each is not tty:
run("stdio_not_isatty", false).await.unwrap()
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn symlink_create() {
run("symlink_create", true).await.unwrap()
}

4
crates/test-programs/wasi-tests/src/bin/isatty.rs → crates/test-programs/wasi-tests/src/bin/regular_file_isatty.rs

@ -1,7 +1,7 @@
use std::{env, process};
use wasi_tests::open_scratch_directory;
unsafe fn test_isatty(dir_fd: wasi::Fd) {
unsafe fn test_file_isatty(dir_fd: wasi::Fd) {
// Create a file in the scratch directory and test if it's a tty.
let file_fd =
wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("opening a file");
@ -38,5 +38,5 @@ fn main() {
};
// Run the tests.
unsafe { test_isatty(dir_fd) }
unsafe { test_file_isatty(dir_fd) }
}

22
crates/test-programs/wasi-tests/src/bin/stdio_isatty.rs

@ -0,0 +1,22 @@
unsafe fn test_stdio_isatty() {
assert_eq!(
libc::isatty(libc::STDIN_FILENO as std::os::raw::c_int),
1,
"stdin is a tty"
);
assert_eq!(
libc::isatty(libc::STDOUT_FILENO as std::os::raw::c_int),
1,
"stdout is a tty"
);
assert_eq!(
libc::isatty(libc::STDERR_FILENO as std::os::raw::c_int),
1,
"stderr is a tty"
);
}
fn main() {
// Run the tests.
unsafe { test_stdio_isatty() }
}

22
crates/test-programs/wasi-tests/src/bin/stdio_not_isatty.rs

@ -0,0 +1,22 @@
unsafe fn test_stdio_not_isatty() {
assert_eq!(
libc::isatty(libc::STDIN_FILENO as std::os::raw::c_int),
0,
"stdin is not a tty"
);
assert_eq!(
libc::isatty(libc::STDOUT_FILENO as std::os::raw::c_int),
0,
"stdout is not a tty"
);
assert_eq!(
libc::isatty(libc::STDERR_FILENO as std::os::raw::c_int),
0,
"stderr is not a tty"
);
}
fn main() {
// Run the tests.
unsafe { test_stdio_not_isatty() }
}

2
crates/wasi-common/cap-std-sync/Cargo.toml

@ -23,7 +23,7 @@ fs-set-times = { workspace = true }
system-interface = { workspace = true, features = ["cap_std_impls"] }
tracing = { workspace = true }
io-lifetimes = { workspace = true }
is-terminal = "0.4.0"
is-terminal = { workspace = true }
[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["fs", "event"] }

50
crates/wasi-preview1-component-adapter/src/descriptors.rs

@ -1,4 +1,7 @@
use crate::bindings::wasi::cli_base::{stderr, stdin, stdout};
use crate::bindings::wasi::cli::{
stderr, stdin, stdout, terminal_input, terminal_output, 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::sockets::tcp;
@ -32,7 +35,7 @@ impl Drop for Descriptor {
match &stream.type_ {
StreamType::File(file) => filesystem::drop_descriptor(file.fd),
StreamType::Socket(_) => unreachable!(),
StreamType::Stdio => {}
StreamType::Stdio(_) => {}
}
}
Descriptor::Closed(_) => {}
@ -109,7 +112,7 @@ impl Streams {
#[allow(dead_code)] // until Socket is implemented
pub enum StreamType {
/// Stream is used for implementing stdio.
Stdio,
Stdio(IsATTY),
/// Streaming data with a file.
File(File),
@ -118,6 +121,20 @@ pub enum StreamType {
Socket(tcp::TcpSocket),
}
pub enum IsATTY {
Yes,
No,
}
impl IsATTY {
pub fn filetype(&self) -> wasi::Filetype {
match self {
IsATTY::Yes => wasi::FILETYPE_CHARACTER_DEVICE,
IsATTY::No => wasi::FILETYPE_UNKNOWN,
}
}
}
#[repr(C)]
pub struct Descriptors {
/// Storage of mapping from preview1 file descriptors to preview2 file
@ -143,26 +160,47 @@ impl Descriptors {
};
let stdin = stdin::get_stdin();
let stdin_isatty = match terminal_stdin::get_terminal_stdin() {
Some(t) => {
terminal_input::drop_terminal_input(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
}
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
}
None => IsATTY::No,
};
d.push(Descriptor::Streams(Streams {
input: Cell::new(Some(stdin)),
output: Cell::new(None),
type_: StreamType::Stdio,
type_: StreamType::Stdio(stdin_isatty),
}))
.trapping_unwrap();
d.push(Descriptor::Streams(Streams {
input: Cell::new(None),
output: Cell::new(Some(stdout)),
type_: StreamType::Stdio,
type_: StreamType::Stdio(stdout_isatty),
}))
.trapping_unwrap();
d.push(Descriptor::Streams(Streams {
input: Cell::new(None),
output: Cell::new(Some(stderr)),
type_: StreamType::Stdio,
type_: StreamType::Stdio(stderr_isatty),
}))
.trapping_unwrap();

37
crates/wasi-preview1-component-adapter/src/lib.rs

@ -1,6 +1,6 @@
#![allow(unused_variables)] // TODO: remove this when more things are implemented
use crate::bindings::wasi::cli_base::exit;
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::streams;
@ -25,13 +25,13 @@ compile_error!("only one of the `command` and `reactor` features may be selected
mod macros;
mod descriptors;
use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams};
use crate::descriptors::{Descriptor, Descriptors, IsATTY, StreamType, Streams};
pub mod bindings {
#[cfg(feature = "command")]
wit_bindgen::generate!({
path: "../wasi/wit",
world: "wasi:preview/command",
world: "wasi:cli/command",
std_feature,
raw_strings,
// Automatically generated bindings for these functions will allocate
@ -46,7 +46,7 @@ pub mod bindings {
#[cfg(feature = "reactor")]
wit_bindgen::generate!({
path: "../wasi/wit",
world: "wasi:preview/reactor",
world: "wasmtime:wasi/preview1-adapter-reactor",
std_feature,
raw_strings,
// Automatically generated bindings for these functions will allocate
@ -59,7 +59,7 @@ pub mod bindings {
});
}
#[no_mangle]
#[export_name = "wasi:cli/run#run"]
#[cfg(feature = "command")]
pub unsafe extern "C" fn run() -> u32 {
#[link(wasm_import_module = "__main_module__")]
@ -564,14 +564,8 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
Descriptor::Streams(Streams {
input,
output,
type_: StreamType::Socket(_),
})
| Descriptor::Streams(Streams {
input,
output,
type_: StreamType::Stdio,
type_: StreamType::Stdio(isatty),
}) => {
let fs_filetype = FILETYPE_CHARACTER_DEVICE;
let fs_flags = 0;
let mut fs_rights_base = 0;
if input.get().is_some() {
@ -582,7 +576,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
}
let fs_rights_inheriting = fs_rights_base;
stat.write(Fdstat {
fs_filetype,
fs_filetype: isatty.filetype(),
fs_flags,
fs_rights_base,
fs_rights_inheriting,
@ -590,6 +584,11 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
Ok(())
}
Descriptor::Closed(_) => Err(ERRNO_BADF),
Descriptor::Streams(Streams {
input,
output,
type_: StreamType::Socket(_),
}) => unreachable!(),
})
}
@ -657,13 +656,13 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno {
}
// Stdio is all zero fields, except for filetype character device
Descriptor::Streams(Streams {
type_: StreamType::Stdio,
type_: StreamType::Stdio(isatty),
..
}) => {
*buf = Filestat {
dev: 0,
ino: 0,
filetype: FILETYPE_CHARACTER_DEVICE,
filetype: isatty.filetype(),
nlink: 0,
size: 0,
atim: 0,
@ -1848,7 +1847,7 @@ pub unsafe extern "C" fn poll_oneoff(
}
*/
}
StreamType::Stdio => {
StreamType::Stdio(_) => {
error = ERRNO_SUCCESS;
nbytes = 1;
flags = 0;
@ -1865,7 +1864,7 @@ pub unsafe extern "C" fn poll_oneoff(
.trapping_unwrap();
match desc {
Descriptor::Streams(streams) => match streams.type_ {
StreamType::File(_) | StreamType::Stdio => {
StreamType::File(_) | StreamType::Stdio(_) => {
error = ERRNO_SUCCESS;
nbytes = 1;
flags = 0;
@ -2430,7 +2429,7 @@ impl State {
fn get_environment(&self) -> &[StrTuple] {
if self.env_vars.get().is_none() {
#[link(wasm_import_module = "wasi:cli-base/environment")]
#[link(wasm_import_module = "wasi:cli/environment")]
extern "C" {
#[link_name = "get-environment"]
fn get_environment_import(rval: *mut StrTupleList);
@ -2454,7 +2453,7 @@ impl State {
fn get_args(&self) -> &[WasmStr] {
if self.args.get().is_none() {
#[link(wasm_import_module = "wasi:cli-base/environment")]
#[link(wasm_import_module = "wasi:cli/environment")]
extern "C" {
#[link_name = "get-arguments"]
fn get_args_import(rval: *mut WasmStrList);

2
crates/wasi/Cargo.toml

@ -31,6 +31,7 @@ cap-rand = { workspace = true, optional = true }
cap-fs-ext = { workspace = true, optional = true }
cap-time-ext = { 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}
@ -64,6 +65,7 @@ preview2 = [
'dep:cap-fs-ext',
'dep:cap-time-ext',
'dep:fs-set-times',
'dep:is-terminal',
'dep:bitflags',
'dep:async-trait',
'dep:system-interface',

64
crates/wasi/src/preview2/command.rs

@ -1,7 +1,7 @@
use crate::preview2::WasiView;
wasmtime::component::bindgen!({
world: "wasi:preview/command",
world: "wasi:cli/command",
tracing: true,
async: true,
trappable_error_type: {
@ -17,11 +17,16 @@ wasmtime::component::bindgen!({
"wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone,
"wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock,
"wasi:random/random": crate::preview2::bindings::random::random,
"wasi:cli_base/environment": crate::preview2::bindings::cli_base::environment,
"wasi:cli_base/exit": crate::preview2::bindings::cli_base::exit,
"wasi:cli_base/stdin": crate::preview2::bindings::cli_base::stdin,
"wasi:cli_base/stdout": crate::preview2::bindings::cli_base::stdout,
"wasi:cli_base/stderr": crate::preview2::bindings::cli_base::stderr,
"wasi:cli/environment": crate::preview2::bindings::cli::environment,
"wasi:cli/exit": crate::preview2::bindings::cli::exit,
"wasi:cli/stdin": crate::preview2::bindings::cli::stdin,
"wasi:cli/stdout": crate::preview2::bindings::cli::stdout,
"wasi:cli/stderr": crate::preview2::bindings::cli::stderr,
"wasi:cli/terminal-input": crate::preview2::bindings::cli::terminal_input,
"wasi:cli/terminal-output": crate::preview2::bindings::cli::terminal_output,
"wasi:cli/terminal-stdin": crate::preview2::bindings::cli::terminal_stdin,
"wasi:cli/terminal-stdout": crate::preview2::bindings::cli::terminal_stdout,
"wasi:cli/terminal-stderr": crate::preview2::bindings::cli::terminal_stderr,
},
});
@ -34,11 +39,16 @@ pub fn add_to_linker<T: WasiView>(l: &mut wasmtime::component::Linker<T>) -> any
crate::preview2::bindings::poll::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_base::exit::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::environment::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stderr::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::exit::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::environment::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stderr::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
Ok(())
}
@ -46,7 +56,7 @@ pub mod sync {
use crate::preview2::WasiView;
wasmtime::component::bindgen!({
world: "wasi:preview/command",
world: "wasi:cli/command",
tracing: true,
async: false,
trappable_error_type: {
@ -62,11 +72,16 @@ pub mod sync {
"wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone,
"wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock,
"wasi:random/random": crate::preview2::bindings::random::random,
"wasi:cli_base/environment": crate::preview2::bindings::cli_base::environment,
"wasi:cli_base/exit": crate::preview2::bindings::cli_base::exit,
"wasi:cli_base/stdin": crate::preview2::bindings::cli_base::stdin,
"wasi:cli_base/stdout": crate::preview2::bindings::cli_base::stdout,
"wasi:cli_base/stderr": crate::preview2::bindings::cli_base::stderr,
"wasi:cli/environment": crate::preview2::bindings::cli::environment,
"wasi:cli/exit": crate::preview2::bindings::cli::exit,
"wasi:cli/stdin": crate::preview2::bindings::cli::stdin,
"wasi:cli/stdout": crate::preview2::bindings::cli::stdout,
"wasi:cli/stderr": crate::preview2::bindings::cli::stderr,
"wasi:cli/terminal-input": crate::preview2::bindings::cli::terminal_input,
"wasi:cli/terminal-output": crate::preview2::bindings::cli::terminal_output,
"wasi:cli/terminal-stdin": crate::preview2::bindings::cli::terminal_stdin,
"wasi:cli/terminal-stdout": crate::preview2::bindings::cli::terminal_stdout,
"wasi:cli/terminal-stderr": crate::preview2::bindings::cli::terminal_stderr,
},
});
@ -81,11 +96,16 @@ pub mod sync {
crate::preview2::bindings::sync_io::poll::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_base::exit::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::environment::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli_base::stderr::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::exit::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::environment::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::stderr::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_input::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_output::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?;
crate::preview2::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?;
Ok(())
}
}

81
crates/wasi/src/preview2/ctx.rs

@ -3,16 +3,17 @@ use crate::preview2::{
clocks::{self, HostMonotonicClock, HostWallClock},
filesystem::{Dir, TableFsExt},
pipe, random, stdio,
stdio::{StdioInput, StdioOutput},
stream::{HostInputStream, HostOutputStream, TableStreamExt},
DirPerms, FilePerms, Table,
DirPerms, FilePerms, IsATTY, Table,
};
use cap_rand::{Rng, RngCore, SeedableRng};
use std::mem;
pub struct WasiCtxBuilder {
stdin: Box<dyn HostInputStream>,
stdout: Box<dyn HostOutputStream>,
stderr: Box<dyn HostOutputStream>,
stdin: (Box<dyn HostInputStream>, IsATTY),
stdout: (Box<dyn HostOutputStream>, IsATTY),
stderr: (Box<dyn HostOutputStream>, IsATTY),
env: Vec<(String, String)>,
args: Vec<String>,
preopens: Vec<(Dir, String)>,
@ -56,9 +57,9 @@ impl WasiCtxBuilder {
let insecure_random_seed =
cap_rand::thread_rng(cap_rand::ambient_authority()).gen::<u128>();
Self {
stdin: Box::new(pipe::ClosedInputStream),
stdout: Box::new(pipe::SinkOutputStream),
stderr: Box::new(pipe::SinkOutputStream),
stdin: (Box::new(pipe::ClosedInputStream), IsATTY::No),
stdout: (Box::new(pipe::SinkOutputStream), IsATTY::No),
stderr: (Box::new(pipe::SinkOutputStream), IsATTY::No),
env: Vec::new(),
args: Vec::new(),
preopens: Vec::new(),
@ -71,31 +72,52 @@ impl WasiCtxBuilder {
}
}
pub fn stdin(&mut self, stdin: impl HostInputStream + 'static) -> &mut Self {
self.stdin = Box::new(stdin);
pub fn stdin(&mut self, stdin: impl HostInputStream + 'static, isatty: IsATTY) -> &mut Self {
self.stdin = (Box::new(stdin), isatty);
self
}
pub fn stdout(&mut self, stdout: impl HostOutputStream + 'static) -> &mut Self {
self.stdout = Box::new(stdout);
pub fn stdout(&mut self, stdout: impl HostOutputStream + 'static, isatty: IsATTY) -> &mut Self {
self.stdout = (Box::new(stdout), isatty);
self
}
pub fn stderr(&mut self, stderr: impl HostOutputStream + 'static) -> &mut Self {
self.stderr = Box::new(stderr);
pub fn stderr(&mut self, stderr: impl HostOutputStream + 'static, isatty: IsATTY) -> &mut Self {
self.stderr = (Box::new(stderr), isatty);
self
}
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin(stdio::stdin())
use is_terminal::IsTerminal;
let inherited = stdio::stdin();
let isatty = if inherited.is_terminal() {
IsATTY::Yes
} else {
IsATTY::No
};
self.stdin(inherited, isatty)
}
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout(stdio::stdout())
use is_terminal::IsTerminal;
let inherited = stdio::stdout();
let isatty = if inherited.is_terminal() {
IsATTY::Yes
} else {
IsATTY::No
};
self.stdout(inherited, isatty)
}
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr(stdio::stderr())
use is_terminal::IsTerminal;
let inherited = stdio::stderr();
let isatty = if inherited.is_terminal() {
IsATTY::Yes
} else {
IsATTY::No
};
self.stderr(inherited, isatty)
}
pub fn inherit_stdio(&mut self) -> &mut Self {
@ -208,9 +230,9 @@ impl WasiCtxBuilder {
} = mem::replace(self, Self::new());
self.built = true;
let stdin = table.push_input_stream(stdin).context("stdin")?;
let stdout = table.push_output_stream(stdout).context("stdout")?;
let stderr = table.push_output_stream(stderr).context("stderr")?;
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()
@ -223,9 +245,18 @@ impl WasiCtxBuilder {
.collect::<anyhow::Result<Vec<_>>>()?;
Ok(WasiCtx {
stdin,
stdout,
stderr,
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,
},
env,
args,
preopens,
@ -254,7 +285,7 @@ pub struct WasiCtx {
pub(crate) env: Vec<(String, String)>,
pub(crate) args: Vec<String>,
pub(crate) preopens: Vec<(u32, String)>,
pub(crate) stdin: u32,
pub(crate) stdout: u32,
pub(crate) stderr: u32,
pub(crate) stdin: StdioInput,
pub(crate) stdout: StdioOutput,
pub(crate) stderr: StdioOutput,
}

23
crates/wasi/src/preview2/host/env.rs

@ -1,5 +1,4 @@
use crate::preview2::bindings::cli_base::{environment, stderr, stdin, stdout};
use crate::preview2::bindings::io::streams;
use crate::preview2::bindings::cli::environment;
use crate::preview2::WasiView;
impl<T: WasiView> environment::Host for T {
@ -9,22 +8,8 @@ impl<T: WasiView> environment::Host for T {
fn get_arguments(&mut self) -> anyhow::Result<Vec<String>> {
Ok(self.ctx().args.clone())
}
}
impl<T: WasiView> stdin::Host for T {
fn get_stdin(&mut self) -> Result<streams::InputStream, anyhow::Error> {
Ok(self.ctx().stdin)
}
}
impl<T: WasiView> stdout::Host for T {
fn get_stdout(&mut self) -> Result<streams::OutputStream, anyhow::Error> {
Ok(self.ctx().stdout)
}
}
impl<T: WasiView> stderr::Host for T {
fn get_stderr(&mut self) -> Result<streams::OutputStream, anyhow::Error> {
Ok(self.ctx().stderr)
fn initial_cwd(&mut self) -> anyhow::Result<Option<String>> {
// FIXME: expose cwd in builder and save in ctx
Ok(None)
}
}

2
crates/wasi/src/preview2/host/exit.rs

@ -1,4 +1,4 @@
use crate::preview2::{bindings::cli_base::exit, I32Exit, WasiView};
use crate::preview2::{bindings::cli::exit, I32Exit, WasiView};
impl<T: WasiView> exit::Host for T {
fn exit(&mut self, status: Result<(), ()>) -> anyhow::Result<()> {

18
crates/wasi/src/preview2/mod.rs

@ -36,6 +36,7 @@ pub use self::error::I32Exit;
pub use self::filesystem::{DirPerms, FilePerms};
pub use self::poll::{ClosureFuture, HostPollable, MakeFuture, PollableFuture, TablePollableExt};
pub use self::random::{thread_rng, Deterministic};
pub use self::stdio::{stderr, stdin, stdout, IsATTY, Stderr, Stdin, Stdout};
pub use self::stream::{HostInputStream, HostOutputStream, StreamState, TableStreamExt};
pub use self::table::{OccupiedEntry, Table, TableError};
pub use cap_fs_ext::SystemTimeSpec;
@ -122,11 +123,16 @@ pub mod bindings {
import wasi:random/random
import wasi:random/insecure
import wasi:random/insecure-seed
import wasi:cli-base/environment
import wasi:cli-base/exit
import wasi:cli-base/stdin
import wasi:cli-base/stdout
import wasi:cli-base/stderr
import wasi:cli/environment
import wasi:cli/exit
import wasi:cli/stdin
import wasi:cli/stdout
import wasi:cli/stderr
import wasi:cli/terminal-input
import wasi:cli/terminal-output
import wasi:cli/terminal-stdin
import wasi:cli/terminal-stdout
import wasi:cli/terminal-stderr
",
tracing: true,
trappable_error_type: {
@ -142,7 +148,7 @@ pub mod bindings {
});
}
pub use self::_internal_rest::wasi::{cli_base, random};
pub use self::_internal_rest::wasi::{cli, random};
pub mod filesystem {
pub use super::_internal_io::wasi::filesystem::types;
pub use super::_internal_rest::wasi::filesystem::preopens;

215
crates/wasi/src/preview2/preview1.rs

@ -1,10 +1,13 @@
use crate::preview2::bindings::cli_base::{stderr, stdin, stdout};
use crate::preview2::bindings::cli::{
stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin,
terminal_stdout,
};
use crate::preview2::bindings::clocks::{monotonic_clock, wall_clock};
use crate::preview2::bindings::filesystem::{preopens, types as filesystem};
use crate::preview2::bindings::io::streams;
use crate::preview2::filesystem::TableFsExt;
use crate::preview2::host::filesystem::TableReaddirExt;
use crate::preview2::{bindings, TableError, WasiView};
use crate::preview2::{bindings, IsATTY, TableError, WasiView};
use anyhow::{anyhow, bail, Context};
use std::borrow::Borrow;
use std::cell::Cell;
@ -36,9 +39,18 @@ struct File {
#[derive(Clone, Debug)]
enum Descriptor {
Stdin(streams::InputStream),
Stdout(streams::OutputStream),
Stderr(streams::OutputStream),
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)),
File(File),
}
@ -71,30 +83,78 @@ impl DerefMut for Descriptors {
impl Descriptors {
/// Initializes [Self] using `preopens`
fn new(
preopens: &mut (impl preopens::Host + stdin::Host + stdout::Host + stderr::Host + ?Sized),
host: &mut (impl preopens::Host
+ stdin::Host
+ stdout::Host
+ stderr::Host
+ terminal_stdin::Host
+ terminal_stdout::Host
+ terminal_stderr::Host
+ terminal_input::Host
+ terminal_output::Host
+ ?Sized),
) -> Result<Self, types::Error> {
let stdin = preopens
.get_stdin()
.context("failed to call `get-stdin`")
.map_err(types::Error::trap)?;
let stdout = preopens
.get_stdout()
.context("failed to call `get-stdout`")
.map_err(types::Error::trap)?;
let stderr = preopens
.get_stderr()
.context("failed to call `get-stderr`")
.map_err(types::Error::trap)?;
let directories = preopens
let mut descriptors = Self::default();
descriptors.push(Descriptor::Stdin {
input_stream: host
.get_stdin()
.context("failed to call `get-stdin`")
.map_err(types::Error::trap)?,
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)
.context("failed to call `drop-terminal-input`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
descriptors.push(Descriptor::Stdout {
output_stream: host
.get_stdout()
.context("failed to call `get-stdout`")
.map_err(types::Error::trap)?,
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)
.context("failed to call `drop-terminal-output`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
descriptors.push(Descriptor::Stderr {
output_stream: host
.get_stderr()
.context("failed to call `get-stderr`")
.map_err(types::Error::trap)?,
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)
.context("failed to call `drop-terminal-output`")
.map_err(types::Error::trap)?;
IsATTY::Yes
} else {
IsATTY::No
},
})?;
for dir in host
.get_directories()
.context("failed to call `get-directories`")
.map_err(types::Error::trap)?;
let mut descriptors = Self::default();
descriptors.push(Descriptor::Stdin(stdin))?;
descriptors.push(Descriptor::Stdout(stdout))?;
descriptors.push(Descriptor::Stderr(stderr))?;
for dir in directories {
.map_err(types::Error::trap)?
{
descriptors.push(Descriptor::PreopenDirectory(dir))?;
}
Ok(descriptors)
@ -232,7 +292,9 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
Some(Descriptor::File(file @ File { fd, .. })) if self.view.table().is_file(*fd) => {
Ok(file)
}
Some(Descriptor::Stdin(..) | Descriptor::Stdout(..) | Descriptor::Stderr(..)) => {
Some(
Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. },
) => {
// NOTE: legacy implementation returns SPIPE here
Err(types::Errno::Spipe.into())
}
@ -245,8 +307,10 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
match self.get_descriptor(fd)? {
Descriptor::File(File { fd, .. }) => Ok(*fd),
Descriptor::PreopenDirectory((fd, _)) => Ok(*fd),
Descriptor::Stdin(stream) => Ok(*stream),
Descriptor::Stdout(stream) | Descriptor::Stderr(stream) => Ok(*stream),
Descriptor::Stdin { input_stream, .. } => Ok(*input_stream),
Descriptor::Stdout { output_stream, .. } | Descriptor::Stderr { output_stream, .. } => {
Ok(*output_stream)
}
}
}
@ -270,7 +334,16 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
}
trait WasiPreview1ViewExt:
WasiPreview1View + preopens::Host + stdin::Host + stdout::Host + stderr::Host
WasiPreview1View
+ preopens::Host
+ stdin::Host
+ stdout::Host
+ stderr::Host
+ terminal_input::Host
+ terminal_output::Host
+ terminal_stdin::Host
+ terminal_stdout::Host
+ terminal_stderr::Host
{
/// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
/// and returns [`Transaction`] on success
@ -319,8 +392,8 @@ impl<T: WasiPreview1View + preopens::Host> WasiPreview1ViewExt for T {}
pub fn add_to_linker<
T: WasiPreview1View
+ bindings::cli_base::environment::Host
+ bindings::cli_base::exit::Host
+ bindings::cli::environment::Host
+ bindings::cli::exit::Host
+ bindings::filesystem::types::Host
+ bindings::filesystem::preopens::Host
+ bindings::sync_io::poll::poll::Host
@ -453,6 +526,15 @@ impl TryFrom<filesystem::DescriptorType> for types::Filetype {
}
}
impl From<IsATTY> for types::Filetype {
fn from(isatty: IsATTY) -> Self {
match isatty {
IsATTY::Yes => types::Filetype::CharacterDevice,
IsATTY::No => types::Filetype::Unknown,
}
}
}
impl From<filesystem::ErrorCode> for types::Errno {
fn from(code: filesystem::ErrorCode) -> Self {
match code {
@ -634,8 +716,8 @@ fn first_non_empty_iovec<'a>(
// stored in the WasiPreview1Adapter struct.
impl<
T: WasiPreview1View
+ bindings::cli_base::environment::Host
+ bindings::cli_base::exit::Host
+ bindings::cli::environment::Host
+ bindings::cli::exit::Host
+ bindings::filesystem::preopens::Host
+ bindings::filesystem::types::Host
+ bindings::poll::poll::Host
@ -806,11 +888,13 @@ impl<
.ok_or(types::Errno::Badf)?
.clone();
match desc {
Descriptor::Stdin(stream) => streams::Host::drop_input_stream(self, stream)
.await
.context("failed to call `drop-input-stream`"),
Descriptor::Stdout(stream) | Descriptor::Stderr(stream) => {
streams::Host::drop_output_stream(self, stream)
Descriptor::Stdin { input_stream, .. } => {
streams::Host::drop_input_stream(self, input_stream)
.await
.context("failed to call `drop-input-stream`")
}
Descriptor::Stdout { output_stream, .. } | Descriptor::Stderr { output_stream, .. } => {
streams::Host::drop_output_stream(self, output_stream)
.await
.context("failed to call `drop-output-stream`")
}
@ -839,19 +923,19 @@ impl<
#[instrument(skip(self))]
async fn fd_fdstat_get(&mut self, fd: types::Fd) -> Result<types::Fdstat, types::Error> {
let (fd, blocking, append) = match self.transact()?.get_descriptor(fd)? {
Descriptor::Stdin(..) => {
Descriptor::Stdin { isatty, .. } => {
let fs_rights_base = types::Rights::FD_READ;
return Ok(types::Fdstat {
fs_filetype: types::Filetype::CharacterDevice,
fs_filetype: (*isatty).into(),
fs_flags: types::Fdflags::empty(),
fs_rights_base,
fs_rights_inheriting: fs_rights_base,
});
}
Descriptor::Stdout(..) | Descriptor::Stderr(..) => {
Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => {
let fs_rights_base = types::Rights::FD_WRITE;
return Ok(types::Fdstat {
fs_filetype: types::Filetype::CharacterDevice,
fs_filetype: (*isatty).into(),
fs_flags: types::Fdflags::empty(),
fs_rights_base,
fs_rights_inheriting: fs_rights_base,
@ -996,18 +1080,18 @@ impl<
async fn fd_filestat_get(&mut self, fd: types::Fd) -> Result<types::Filestat, types::Error> {
let desc = self.transact()?.get_descriptor(fd)?.clone();
match desc {
Descriptor::Stdin(..) | Descriptor::Stdout(..) | Descriptor::Stderr(..) => {
Ok(types::Filestat {
dev: 0,
ino: 0,
filetype: types::Filetype::CharacterDevice,
nlink: 0,
size: 0,
atim: 0,
mtim: 0,
ctim: 0,
})
}
Descriptor::Stdin { isatty, .. }
| Descriptor::Stdout { isatty, .. }
| Descriptor::Stderr { isatty, .. } => Ok(types::Filestat {
dev: 0,
ino: 0,
filetype: isatty.into(),
nlink: 0,
size: 0,
atim: 0,
mtim: 0,
ctim: 0,
}),
Descriptor::PreopenDirectory((fd, _)) | Descriptor::File(File { fd, .. }) => {
let filesystem::DescriptorStat {
type_,
@ -1129,14 +1213,17 @@ impl<
(buf, read, state)
}
Descriptor::Stdin(stream) => {
Descriptor::Stdin { input_stream, .. } => {
let Some(buf) = first_non_empty_iovec(iovs)? else {
return Ok(0)
};
let (read, state) =
streams::Host::read(self, stream, buf.len().try_into().unwrap_or(u64::MAX))
.await
.map_err(|_| types::Errno::Io)?;
let (read, state) = streams::Host::read(
self,
input_stream,
buf.len().try_into().unwrap_or(u64::MAX),
)
.await
.map_err(|_| types::Errno::Io)?;
(buf, read, state)
}
_ => return Err(types::Errno::Badf.into()),
@ -1184,7 +1271,7 @@ impl<
(buf, read, state)
}
Descriptor::Stdin(..) => {
Descriptor::Stdin { .. } => {
// NOTE: legacy implementation returns SPIPE here
return Err(types::Errno::Spipe.into());
}
@ -1249,11 +1336,11 @@ impl<
}
n
}
Descriptor::Stdout(stream) | Descriptor::Stderr(stream) => {
Descriptor::Stdout { output_stream, .. } | Descriptor::Stderr { output_stream, .. } => {
let Some(buf) = first_non_empty_ciovec(ciovs)? else {
return Ok(0)
};
let (n, _stat) = streams::Host::blocking_write(self, stream, buf)
let (n, _stat) = streams::Host::blocking_write(self, output_stream, buf)
.await
.map_err(|_| types::Errno::Io)?;
n
@ -1291,7 +1378,7 @@ impl<
}
.map_err(|_| types::Errno::Io)?
}
Descriptor::Stdout(..) | Descriptor::Stderr(..) => {
Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => {
// NOTE: legacy implementation returns SPIPE here
return Err(types::Errno::Spipe.into());
}

123
crates/wasi/src/preview2/stdio.rs

@ -1,4 +1,13 @@
use crate::preview2::bindings::cli::{
stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin,
terminal_stdout,
};
use crate::preview2::bindings::io::streams;
use crate::preview2::pipe::AsyncWriteStream;
use crate::preview2::{HostOutputStream, StreamState, WasiView};
use anyhow::Error;
use bytes::Bytes;
use is_terminal::IsTerminal;
#[cfg(unix)]
mod unix;
@ -10,15 +19,121 @@ mod worker_thread_stdin;
#[cfg(windows)]
pub use self::worker_thread_stdin::{stdin, Stdin};
pub type Stdout = AsyncWriteStream;
pub struct Stdout(AsyncWriteStream);
pub fn stdout() -> Stdout {
AsyncWriteStream::new(tokio::io::stdout())
Stdout(AsyncWriteStream::new(tokio::io::stdout()))
}
pub type Stderr = AsyncWriteStream;
impl IsTerminal for Stdout {
fn is_terminal(&self) -> bool {
std::io::stdout().is_terminal()
}
}
#[async_trait::async_trait]
impl HostOutputStream for Stdout {
fn write(&mut self, bytes: Bytes) -> Result<(usize, StreamState), Error> {
self.0.write(bytes)
}
async fn ready(&mut self) -> Result<(), Error> {
self.0.ready().await
}
}
pub struct Stderr(AsyncWriteStream);
pub fn stderr() -> Stderr {
AsyncWriteStream::new(tokio::io::stderr())
Stderr(AsyncWriteStream::new(tokio::io::stderr()))
}
impl IsTerminal for Stderr {
fn is_terminal(&self) -> bool {
std::io::stderr().is_terminal()
}
}
#[async_trait::async_trait]
impl HostOutputStream for Stderr {
fn write(&mut self, bytes: Bytes) -> Result<(usize, StreamState), Error> {
self.0.write(bytes)
}
async fn ready(&mut self) -> Result<(), Error> {
self.0.ready().await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsATTY {
Yes,
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<T: WasiView> stdin::Host for T {
fn get_stdin(&mut self) -> Result<streams::InputStream, anyhow::Error> {
Ok(self.ctx().stdin.input_stream)
}
}
impl<T: WasiView> stdout::Host for T {
fn get_stdout(&mut self) -> Result<streams::OutputStream, anyhow::Error> {
Ok(self.ctx().stdout.output_stream)
}
}
impl<T: WasiView> stderr::Host for T {
fn get_stderr(&mut self) -> Result<streams::OutputStream, anyhow::Error> {
Ok(self.ctx().stderr.output_stream)
}
}
struct HostTerminalInput;
struct HostTerminalOutput;
impl<T: WasiView> terminal_input::Host for T {
fn drop_terminal_input(&mut self, r: terminal_input::TerminalInput) -> anyhow::Result<()> {
self.table_mut().delete::<HostTerminalInput>(r)?;
Ok(())
}
}
impl<T: WasiView> terminal_output::Host for T {
fn drop_terminal_output(&mut self, r: terminal_output::TerminalOutput) -> anyhow::Result<()> {
self.table_mut().delete::<HostTerminalOutput>(r)?;
Ok(())
}
}
impl<T: WasiView> terminal_stdin::Host for T {
fn get_terminal_stdin(&mut self) -> anyhow::Result<Option<terminal_input::TerminalInput>> {
if let IsATTY::Yes = self.ctx().stdin.isatty {
Ok(Some(self.table_mut().push(Box::new(HostTerminalInput))?))
} else {
Ok(None)
}
}
}
impl<T: WasiView> terminal_stdout::Host for T {
fn get_terminal_stdout(&mut self) -> anyhow::Result<Option<terminal_output::TerminalOutput>> {
if let IsATTY::Yes = self.ctx().stdout.isatty {
Ok(Some(self.table_mut().push(Box::new(HostTerminalOutput))?))
} else {
Ok(None)
}
}
}
impl<T: WasiView> terminal_stderr::Host for T {
fn get_terminal_stderr(&mut self) -> anyhow::Result<Option<terminal_output::TerminalOutput>> {
if let IsATTY::Yes = self.ctx().stderr.isatty {
Ok(Some(self.table_mut().push(Box::new(HostTerminalOutput))?))
} else {
Ok(None)
}
}
}
#[cfg(all(unix, test))]

6
crates/wasi/src/preview2/stdio/unix.rs

@ -65,6 +65,12 @@ pub fn stdin() -> Stdin {
handle
}
impl is_terminal::IsTerminal for Stdin {
fn is_terminal(&self) -> bool {
std::io::stdin().is_terminal()
}
}
#[async_trait::async_trait]
impl crate::preview2::HostInputStream for Stdin {
fn read(&mut self, size: usize) -> Result<(Bytes, StreamState), Error> {

6
crates/wasi/src/preview2/stdio/worker_thread_stdin.rs

@ -91,6 +91,12 @@ pub fn stdin() -> Stdin {
Stdin
}
impl is_terminal::IsTerminal for Stdin {
fn is_terminal(&self) -> bool {
std::io::stdin().is_terminal()
}
}
#[async_trait::async_trait]
impl HostInputStream for Stdin {
fn read(&mut self, size: usize) -> Result<(Bytes, StreamState), Error> {

20
crates/wasi/wit/deps/preview/command.wit → crates/wasi/wit/deps/cli/command.wit

@ -1,3 +1,5 @@
package wasi:cli
world command {
import wasi:clocks/wall-clock
import wasi:clocks/monotonic-clock
@ -16,11 +18,15 @@ world command {
import wasi:random/insecure-seed
import wasi:poll/poll
import wasi:io/streams
import wasi:cli-base/environment
import wasi:cli-base/exit
import wasi:cli-base/stdin
import wasi:cli-base/stdout
import wasi:cli-base/stderr
export run: func() -> result
import environment
import exit
import stdin
import stdout
import stderr
import terminal-input
import terminal-output
import terminal-stdin
import terminal-stdout
import terminal-stderr
export run
}

6
crates/wasi/wit/deps/wasi-cli-base/environment.wit → crates/wasi/wit/deps/cli/environment.wit

@ -1,5 +1,3 @@
package wasi:cli-base
interface environment {
/// Get the POSIX-style environment variables.
///
@ -13,4 +11,8 @@ interface environment {
/// Get the POSIX-style arguments to the program.
get-arguments: func() -> list<string>
/// Return a path that programs should use as their initial current working
/// directory, interpreting `.` as shorthand for this.
initial-cwd: func() -> option<string>
}

4
crates/wasi/wit/deps/cli/exit.wit

@ -0,0 +1,4 @@
interface exit {
/// Exit the current instance and any linked instances.
exit: func(status: result)
}

4
crates/wasi/wit/deps/cli/run.wit

@ -0,0 +1,4 @@
interface run {
/// Run the program.
run: func() -> result
}

0
crates/wasi/wit/deps/wasi-cli-base/stdio.wit → crates/wasi/wit/deps/cli/stdio.wit

59
crates/wasi/wit/deps/cli/terminal.wit

@ -0,0 +1,59 @@
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
// 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
// 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
/// link-time authority.
interface terminal-stdin {
use terminal-input.{terminal-input}
/// If stdin is connected to a terminal, return a `terminal-input` handle
/// allowing further interaction with it.
get-terminal-stdin: func() -> option<terminal-input>
}
/// An interface providing an optional `terminal-output` for stdout as a
/// link-time authority.
interface terminal-stdout {
use terminal-output.{terminal-output}
/// If stdout is connected to a terminal, return a `terminal-output` handle
/// allowing further interaction with it.
get-terminal-stdout: func() -> option<terminal-output>
}
/// An interface providing an optional `terminal-output` for stderr as a
/// link-time authority.
interface terminal-stderr {
use terminal-output.{terminal-output}
/// If stderr is connected to a terminal, return a `terminal-output` handle
/// allowing further interaction with it.
get-terminal-stderr: func() -> option<terminal-output>
}

36
crates/wasi/wit/deps/preview/command-extended.wit

@ -1,36 +0,0 @@
package wasi:preview
world command-extended {
import wasi:clocks/wall-clock
import wasi:clocks/monotonic-clock
import wasi:clocks/timezone
import wasi:filesystem/types
import wasi:filesystem/preopens
import wasi:sockets/instance-network
import wasi:sockets/ip-name-lookup
import wasi:sockets/network
import wasi:sockets/tcp-create-socket
import wasi:sockets/tcp
import wasi:sockets/udp-create-socket
import wasi:sockets/udp
import wasi:random/random
import wasi:random/insecure
import wasi:random/insecure-seed
import wasi:poll/poll
import wasi:io/streams
import wasi:cli-base/environment
import wasi:cli-base/exit
import wasi:cli-base/stdin
import wasi:cli-base/stdout
import wasi:cli-base/stderr
// We should replace all others with `include self.command`
// as soon as the unioning of worlds is available:
// https://github.com/WebAssembly/component-model/issues/169
import wasi:logging/handler
import wasi:http/outgoing-handler
export run: func(
args: list<string>,
) -> result
}

9
crates/wasi/wit/deps/preview/proxy.wit

@ -1,9 +0,0 @@
world proxy {
import wasi:random/random
import wasi:random/insecure
import wasi:random/insecure-seed
import wasi:logging/handler
import wasi:http/outgoing-handler
export wasi:http/incoming-handler
}

24
crates/wasi/wit/deps/preview/reactor.wit

@ -1,24 +0,0 @@
world reactor {
import wasi:clocks/wall-clock
import wasi:clocks/monotonic-clock
import wasi:clocks/timezone
import wasi:filesystem/types
import wasi:filesystem/preopens
import wasi:sockets/instance-network
import wasi:sockets/ip-name-lookup
import wasi:sockets/network
import wasi:sockets/tcp-create-socket
import wasi:sockets/tcp
import wasi:sockets/udp-create-socket
import wasi:sockets/udp
import wasi:random/random
import wasi:poll/poll
import wasi:io/streams
import wasi:logging/handler
import wasi:http/outgoing-handler
import wasi:cli-base/environment
import wasi:cli-base/exit
import wasi:cli-base/stdin
import wasi:cli-base/stdout
import wasi:cli-base/stderr
}

4
crates/wasi/wit/deps/wasi-cli-base/exit.wit

@ -1,4 +0,0 @@
interface exit {
/// Exit the curerent instance and any linked instances.
exit: func(status: result)
}

34
crates/wasi/wit/main.wit

@ -1 +1,33 @@
package unused:main
package wasmtime:wasi
// All of the same imports available in the wasi:cli/command world, but no
// export required:
world preview1-adapter-reactor {
import wasi:clocks/wall-clock
import wasi:clocks/monotonic-clock
import wasi:clocks/timezone
import wasi:filesystem/types
import wasi:filesystem/preopens
import wasi:sockets/instance-network
import wasi:sockets/ip-name-lookup
import wasi:sockets/network
import wasi:sockets/tcp-create-socket
import wasi:sockets/tcp
import wasi:sockets/udp-create-socket
import wasi:sockets/udp
import wasi:random/random
import wasi:random/insecure
import wasi:random/insecure-seed
import wasi:poll/poll
import wasi:io/streams
import wasi:cli/environment
import wasi:cli/exit
import wasi:cli/stdin
import wasi:cli/stdout
import wasi:cli/stderr
import wasi:cli/terminal-input
import wasi:cli/terminal-output
import wasi:cli/terminal-stdin
import wasi:cli/terminal-stdout
import wasi:cli/terminal-stderr
}

12
crates/wasi/wit/test.wit

@ -1,11 +1,11 @@
// only used as part of `test-programs`
world test-reactor {
import wasi:cli-base/environment
import wasi:cli/environment
import wasi:io/streams
import wasi:filesystem/types
import wasi:filesystem/preopens
import wasi:cli-base/exit
import wasi:cli/exit
export add-strings: func(s: list<string>) -> u32
export get-strings: func() -> list<string>
@ -21,8 +21,8 @@ world test-reactor {
world test-command {
import wasi:poll/poll
import wasi:io/streams
import wasi:cli-base/environment
import wasi:cli-base/stdin
import wasi:cli-base/stdout
import wasi:cli-base/stderr
import wasi:cli/environment
import wasi:cli/stdin
import wasi:cli/stdout
import wasi:cli/stderr
}

Loading…
Cancel
Save