diff --git a/Cargo.lock b/Cargo.lock index a211324257..f5bcb273c8 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index 27f08de6de..d45779629f 100644 --- a/Cargo.toml +++ b/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 } diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 37a97b40b3..bd9de15696 100644 --- a/crates/test-programs/Cargo.toml +++ b/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 } 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 1ccc710643..ebd8071957 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,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; diff --git a/crates/test-programs/src/lib.rs b/crates/test-programs/src/lib.rs index 960fd9a7a0..11eca7f286 100644 --- a/crates/test-programs/src/lib.rs +++ b/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()) +} diff --git a/crates/test-programs/tests/command.rs b/crates/test-programs/tests/command.rs index 7b5877e9da..5743804203 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, 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::() @@ -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::() @@ -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() diff --git a/crates/test-programs/tests/reactor.rs b/crates/test-programs/tests/reactor.rs index b9e4c56ca2..7dff956b61 100644 --- a/crates/test-programs/tests/reactor.rs +++ b/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); diff --git a/crates/test-programs/tests/wasi-cap-std-sync.rs b/crates/test-programs/tests/wasi-cap-std-sync.rs index f373b33dff..6b7143f3b5 100644 --- a/crates/test-programs/tests/wasi-cap-std-sync.rs +++ b/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() 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 700cfd2345..064620b0da 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, 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() } diff --git a/crates/test-programs/tests/wasi-preview2-components-sync.rs b/crates/test-programs/tests/wasi-preview2-components-sync.rs index b618b9f403..08b60217be 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, 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() } diff --git a/crates/test-programs/tests/wasi-preview2-components.rs b/crates/test-programs/tests/wasi-preview2-components.rs index c74cae9002..77a3e71ff6 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, 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() } diff --git a/crates/test-programs/tests/wasi-tokio.rs b/crates/test-programs/tests/wasi-tokio.rs index bd673704cc..9f8390c917 100644 --- a/crates/test-programs/tests/wasi-tokio.rs +++ b/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() } diff --git a/crates/test-programs/wasi-tests/src/bin/isatty.rs b/crates/test-programs/wasi-tests/src/bin/regular_file_isatty.rs similarity index 92% rename from crates/test-programs/wasi-tests/src/bin/isatty.rs rename to crates/test-programs/wasi-tests/src/bin/regular_file_isatty.rs index c8bb3a3979..6a8c821bec 100644 --- a/crates/test-programs/wasi-tests/src/bin/isatty.rs +++ b/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) } } diff --git a/crates/test-programs/wasi-tests/src/bin/stdio_isatty.rs b/crates/test-programs/wasi-tests/src/bin/stdio_isatty.rs new file mode 100644 index 0000000000..7b0f37708e --- /dev/null +++ b/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() } +} diff --git a/crates/test-programs/wasi-tests/src/bin/stdio_not_isatty.rs b/crates/test-programs/wasi-tests/src/bin/stdio_not_isatty.rs new file mode 100644 index 0000000000..c67d3cf3bf --- /dev/null +++ b/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() } +} diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml index 64fb8efb37..f0fe058eac 100644 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ b/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"] } diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs index e8412eb016..81d8f28107 100644 --- a/crates/wasi-preview1-component-adapter/src/descriptors.rs +++ b/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(); diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 67179891cf..595ffe6156 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/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); diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index ab6655a79a..462dccbaee 100644 --- a/crates/wasi/Cargo.toml +++ b/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', diff --git a/crates/wasi/src/preview2/command.rs b/crates/wasi/src/preview2/command.rs index e213f3e4bf..d44b4026a4 100644 --- a/crates/wasi/src/preview2/command.rs +++ b/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(l: &mut wasmtime::component::Linker) -> 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(()) } } diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs index 10408fbf2d..252307c80f 100644 --- a/crates/wasi/src/preview2/ctx.rs +++ b/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, - stdout: Box, - stderr: Box, + stdin: (Box, IsATTY), + stdout: (Box, IsATTY), + stderr: (Box, IsATTY), env: Vec<(String, String)>, args: Vec, preopens: Vec<(Dir, String)>, @@ -56,9 +57,9 @@ impl WasiCtxBuilder { let insecure_random_seed = cap_rand::thread_rng(cap_rand::ambient_authority()).gen::(); 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::>>()?; 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, 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, } diff --git a/crates/wasi/src/preview2/host/env.rs b/crates/wasi/src/preview2/host/env.rs index edb4d118d9..e765dbd817 100644 --- a/crates/wasi/src/preview2/host/env.rs +++ b/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 environment::Host for T { @@ -9,22 +8,8 @@ impl environment::Host for T { fn get_arguments(&mut self) -> anyhow::Result> { Ok(self.ctx().args.clone()) } -} - -impl stdin::Host for T { - fn get_stdin(&mut self) -> Result { - Ok(self.ctx().stdin) - } -} - -impl stdout::Host for T { - fn get_stdout(&mut self) -> Result { - Ok(self.ctx().stdout) - } -} - -impl stderr::Host for T { - fn get_stderr(&mut self) -> Result { - Ok(self.ctx().stderr) + fn initial_cwd(&mut self) -> anyhow::Result> { + // FIXME: expose cwd in builder and save in ctx + Ok(None) } } diff --git a/crates/wasi/src/preview2/host/exit.rs b/crates/wasi/src/preview2/host/exit.rs index 24acff103b..d95866a9a9 100644 --- a/crates/wasi/src/preview2/host/exit.rs +++ b/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 exit::Host for T { fn exit(&mut self, status: Result<(), ()>) -> anyhow::Result<()> { diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs index f86cec58f3..f0144562ee 100644 --- a/crates/wasi/src/preview2/mod.rs +++ b/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; diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs index 2478a197c1..a2f957aa18 100644 --- a/crates/wasi/src/preview2/preview1.rs +++ b/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 { - 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 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 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 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 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 for types::Filetype { } } +impl From for types::Filetype { + fn from(isatty: IsATTY) -> Self { + match isatty { + IsATTY::Yes => types::Filetype::CharacterDevice, + IsATTY::No => types::Filetype::Unknown, + } + } +} + impl From 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 { 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 { 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()); } diff --git a/crates/wasi/src/preview2/stdio.rs b/crates/wasi/src/preview2/stdio.rs index 81083d2e0b..947a4885f5 100644 --- a/crates/wasi/src/preview2/stdio.rs +++ b/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 stdin::Host for T { + fn get_stdin(&mut self) -> Result { + Ok(self.ctx().stdin.input_stream) + } +} + +impl stdout::Host for T { + fn get_stdout(&mut self) -> Result { + Ok(self.ctx().stdout.output_stream) + } +} + +impl stderr::Host for T { + fn get_stderr(&mut self) -> Result { + Ok(self.ctx().stderr.output_stream) + } +} + +struct HostTerminalInput; +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)?; + Ok(()) + } +} +impl terminal_output::Host for T { + fn drop_terminal_output(&mut self, r: terminal_output::TerminalOutput) -> anyhow::Result<()> { + self.table_mut().delete::(r)?; + 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))?)) + } 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))?)) + } 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))?)) + } else { + Ok(None) + } + } } #[cfg(all(unix, test))] diff --git a/crates/wasi/src/preview2/stdio/unix.rs b/crates/wasi/src/preview2/stdio/unix.rs index b624b874e8..a27cf381db 100644 --- a/crates/wasi/src/preview2/stdio/unix.rs +++ b/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> { diff --git a/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs b/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs index 116c747ca5..c5be39b6df 100644 --- a/crates/wasi/src/preview2/stdio/worker_thread_stdin.rs +++ b/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> { diff --git a/crates/wasi/wit/deps/preview/command.wit b/crates/wasi/wit/deps/cli/command.wit similarity index 70% rename from crates/wasi/wit/deps/preview/command.wit rename to crates/wasi/wit/deps/cli/command.wit index dcd06d7957..c29b4a61f8 100644 --- a/crates/wasi/wit/deps/preview/command.wit +++ b/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 } diff --git a/crates/wasi/wit/deps/wasi-cli-base/environment.wit b/crates/wasi/wit/deps/cli/environment.wit similarity index 75% rename from crates/wasi/wit/deps/wasi-cli-base/environment.wit rename to crates/wasi/wit/deps/cli/environment.wit index 4c97c85d1a..36790fe714 100644 --- a/crates/wasi/wit/deps/wasi-cli-base/environment.wit +++ b/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 + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option } diff --git a/crates/wasi/wit/deps/cli/exit.wit b/crates/wasi/wit/deps/cli/exit.wit new file mode 100644 index 0000000000..4831d50789 --- /dev/null +++ b/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) +} diff --git a/crates/wasi/wit/deps/cli/run.wit b/crates/wasi/wit/deps/cli/run.wit new file mode 100644 index 0000000000..45a1ca533f --- /dev/null +++ b/crates/wasi/wit/deps/cli/run.wit @@ -0,0 +1,4 @@ +interface run { + /// Run the program. + run: func() -> result +} diff --git a/crates/wasi/wit/deps/wasi-cli-base/stdio.wit b/crates/wasi/wit/deps/cli/stdio.wit similarity index 100% rename from crates/wasi/wit/deps/wasi-cli-base/stdio.wit rename to crates/wasi/wit/deps/cli/stdio.wit diff --git a/crates/wasi/wit/deps/cli/terminal.wit b/crates/wasi/wit/deps/cli/terminal.wit new file mode 100644 index 0000000000..f32e744374 --- /dev/null +++ b/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 +} + +/// 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 +} + +/// 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 +} diff --git a/crates/wasi/wit/deps/preview/command-extended.wit b/crates/wasi/wit/deps/preview/command-extended.wit deleted file mode 100644 index 8fe716038e..0000000000 --- a/crates/wasi/wit/deps/preview/command-extended.wit +++ /dev/null @@ -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, - ) -> result -} diff --git a/crates/wasi/wit/deps/preview/proxy.wit b/crates/wasi/wit/deps/preview/proxy.wit deleted file mode 100644 index a616daa1c2..0000000000 --- a/crates/wasi/wit/deps/preview/proxy.wit +++ /dev/null @@ -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 -} diff --git a/crates/wasi/wit/deps/preview/reactor.wit b/crates/wasi/wit/deps/preview/reactor.wit deleted file mode 100644 index aff49ea4d1..0000000000 --- a/crates/wasi/wit/deps/preview/reactor.wit +++ /dev/null @@ -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 -} diff --git a/crates/wasi/wit/deps/wasi-cli-base/exit.wit b/crates/wasi/wit/deps/wasi-cli-base/exit.wit deleted file mode 100644 index 66835aa702..0000000000 --- a/crates/wasi/wit/deps/wasi-cli-base/exit.wit +++ /dev/null @@ -1,4 +0,0 @@ -interface exit { - /// Exit the curerent instance and any linked instances. - exit: func(status: result) -} diff --git a/crates/wasi/wit/main.wit b/crates/wasi/wit/main.wit index 264dfee826..753770ad22 100644 --- a/crates/wasi/wit/main.wit +++ b/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 +} diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit index 09498f62cf..447304cba3 100644 --- a/crates/wasi/wit/test.wit +++ b/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) -> u32 export get-strings: func() -> list @@ -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 }