Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions crates/test-programs/src/bin/p3_cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use test_programs::p3::wasi::cli::{
environment, stderr, stdin, stdout, terminal_stderr, terminal_stdin, terminal_stdout,
};
use test_programs::p3::wit_stream;
use wit_bindgen::StreamResult;

struct Component;

test_programs::p3::export!(Component);

impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
async fn run() -> Result<(), ()> {
assert_eq!(environment::get_arguments(), ["p3_cli.component", "."]);
assert_eq!(environment::get_environment().len(), 1);
assert_eq!(environment::initial_cwd(), None);

assert!(terminal_stdin::get_terminal_stdin().is_none());
assert!(terminal_stdout::get_terminal_stdout().is_none());
assert!(terminal_stderr::get_terminal_stderr().is_none());

let mut stdin = stdin::get_stdin();
assert!(stdin.next().await.is_none());

let (mut stdout_tx, stdout_rx) = wit_stream::new();
stdout::set_stdout(stdout_rx);
let (res, buf) = stdout_tx.write(b"hello stdout\n".into()).await;
assert_eq!(res, StreamResult::Complete(13));
assert_eq!(buf.into_vec(), []);

let (mut stderr_tx, stderr_rx) = wit_stream::new();
stderr::set_stderr(stderr_rx);
let (res, buf) = stderr_tx.write(b"hello stderr\n".into()).await;
assert_eq!(res, StreamResult::Complete(13));
assert_eq!(buf.into_vec(), []);

Ok(())
}
}

fn main() {}
1 change: 1 addition & 0 deletions crates/wasi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ preview1 = [
]
p3 = [
"wasmtime/component-model-async",
"wasmtime/component-model-async-bytes",
]

[[test]]
Expand Down
95 changes: 95 additions & 0 deletions crates/wasi/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::rc::Rc;
use std::sync::Arc;

#[derive(Default)]
pub struct WasiCliCtx<I, O> {
pub environment: Vec<(String, String)>,
pub arguments: Vec<String>,
pub initial_cwd: Option<String>,
pub stdin: I,
pub stdout: O,
pub stderr: O,
}

pub trait IsTerminal {
/// Returns whether this stream is backed by a TTY.
fn is_terminal(&self) -> bool;
}
Comment on lines +14 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use std::io::IsTerminal instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is "no" given the litany of implementations we have, but in such a case could documentation be added that this is basically a mirror of what's in libstd? Also is it worth trying to add a blanket impl<T: std::IsTerminal> wasi::IsTermainal for T impl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot use the std::io::IsTerminal, because it's sealed: https://github.com/rust-lang/rust/blob/f8f6997469237299c1d60814c7b9828602a1f8e4/library/std/src/io/stdio.rs#L1197

Unfortunately, largely because of that, blanket implementations for T also do not appear to be possible.

For example:

error[E0119]: conflicting implementations of trait `cli::IsTerminal` for type `tokio::io::Stderr`
   --> crates/wasi/src/cli.rs:126:1
    |
66  | impl<T: std::io::IsTerminal> IsTerminal for T {
    | --------------------------------------------- first implementation here
...
126 | impl IsTerminal for tokio::io::Stderr {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `tokio::io::Stderr`
    |
    = note: upstream crates may add a new impl of trait `std::sealed::Sealed` for type `tokio::io::Stderr` in future versions
    = note: upstream crates may add a new impl of trait `std::io::IsTerminal` for type `tokio::io::Stderr` in future versions

tokio::io::Stderr cannot implement std::io::IsTerminal, because it's sealed and we cannot implement wasmtime_wasi::cli::IsTerminal for it if we provide a blanket implementation for std::io::IsTerminal


impl<T: ?Sized + IsTerminal> IsTerminal for &T {
fn is_terminal(&self) -> bool {
T::is_terminal(self)
}
}

impl<T: ?Sized + IsTerminal> IsTerminal for &mut T {
fn is_terminal(&self) -> bool {
T::is_terminal(self)
}
}

impl<T: ?Sized + IsTerminal> IsTerminal for Box<T> {
fn is_terminal(&self) -> bool {
T::is_terminal(self)
}
}

impl<T: ?Sized + IsTerminal> IsTerminal for Rc<T> {
fn is_terminal(&self) -> bool {
T::is_terminal(self)
}
}

impl<T: ?Sized + IsTerminal> IsTerminal for Arc<T> {
fn is_terminal(&self) -> bool {
T::is_terminal(self)
}
}

impl IsTerminal for tokio::io::Empty {
fn is_terminal(&self) -> bool {
false
}
}

impl IsTerminal for std::io::Empty {
fn is_terminal(&self) -> bool {
false
}
}

impl IsTerminal for tokio::io::Stdin {
fn is_terminal(&self) -> bool {
std::io::stdin().is_terminal()
}
}

impl IsTerminal for std::io::Stdin {
fn is_terminal(&self) -> bool {
std::io::IsTerminal::is_terminal(self)
}
}

impl IsTerminal for tokio::io::Stdout {
fn is_terminal(&self) -> bool {
std::io::stdout().is_terminal()
}
}

impl IsTerminal for std::io::Stdout {
fn is_terminal(&self) -> bool {
std::io::IsTerminal::is_terminal(self)
}
}

impl IsTerminal for tokio::io::Stderr {
fn is_terminal(&self) -> bool {
std::io::stderr().is_terminal()
}
}

impl IsTerminal for std::io::Stderr {
fn is_terminal(&self) -> bool {
std::io::IsTerminal::is_terminal(self)
}
}
17 changes: 7 additions & 10 deletions crates/wasi/src/clocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@ use cap_std::time::{Duration, Instant, SystemClock};
use cap_std::{AmbientAuthority, ambient_authority};
use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _};

#[repr(transparent)]
pub struct WasiClocksImpl<T>(pub T);

impl<T: WasiClocksView> WasiClocksView for &mut T {
fn clocks(&mut self) -> &WasiClocksCtx {
(**self).clocks()
fn clocks(&mut self) -> &mut WasiClocksCtx {
T::clocks(self)
}
}

impl<T: WasiClocksView> WasiClocksView for WasiClocksImpl<T> {
fn clocks(&mut self) -> &WasiClocksCtx {
self.0.clocks()
impl<T: WasiClocksView> WasiClocksView for Box<T> {
fn clocks(&mut self) -> &mut WasiClocksCtx {
T::clocks(self)
}
}

impl WasiClocksView for WasiClocksCtx {
fn clocks(&mut self) -> &WasiClocksCtx {
fn clocks(&mut self) -> &mut WasiClocksCtx {
self
}
}

pub trait WasiClocksView: Send {
fn clocks(&mut self) -> &WasiClocksCtx;
fn clocks(&mut self) -> &mut WasiClocksCtx;
}

pub struct WasiClocksCtx {
Expand Down
51 changes: 40 additions & 11 deletions crates/wasi/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::cli::WasiCliCtx;
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
use crate::net::{SocketAddrCheck, SocketAddrUse};
use crate::random::WasiRandomCtx;
Expand All @@ -16,17 +17,17 @@ use std::sync::Arc;
/// [p2::WasiCtxBuilder](crate::p2::WasiCtxBuilder)
///
/// [`Store`]: wasmtime::Store
pub(crate) struct WasiCtxBuilder {
pub(crate) env: Vec<(String, String)>,
pub(crate) args: Vec<String>,
#[derive(Default)]
pub(crate) struct WasiCtxBuilder<I, O> {
pub(crate) random: WasiRandomCtx,
pub(crate) clocks: WasiClocksCtx,
pub(crate) cli: WasiCliCtx<I, O>,
pub(crate) socket_addr_check: SocketAddrCheck,
pub(crate) allowed_network_uses: AllowedNetworkUses,
pub(crate) allow_blocking_current_thread: bool,
}

impl WasiCtxBuilder {
impl<I, O> WasiCtxBuilder<I, O> {
/// Creates a builder for a new context with default parameters set.
///
/// The current defaults are:
Expand All @@ -44,20 +45,45 @@ impl WasiCtxBuilder {
///
/// These defaults can all be updated via the various builder configuration
/// methods below.
pub(crate) fn new() -> Self {
pub(crate) fn new(stdin: I, stdout: O, stderr: O) -> Self {
let random = WasiRandomCtx::default();
let clocks = WasiClocksCtx::default();
let cli = WasiCliCtx {
environment: Vec::default(),
arguments: Vec::default(),
initial_cwd: None,
stdin,
stdout,
stderr,
};
Self {
env: Vec::new(),
args: Vec::new(),
random,
clocks,
cli,
socket_addr_check: SocketAddrCheck::default(),
allowed_network_uses: AllowedNetworkUses::default(),
allow_blocking_current_thread: false,
}
}

/// Provides a custom implementation of stdin to use.
pub fn stdin(&mut self, stdin: I) -> &mut Self {
self.cli.stdin = stdin;
self
}

/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
pub fn stdout(&mut self, stdout: O) -> &mut Self {
self.cli.stdout = stdout;
self
}

/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
pub fn stderr(&mut self, stderr: O) -> &mut Self {
self.cli.stderr = stderr;
self
}

/// Configures whether or not blocking operations made through this
/// `WasiCtx` are allowed to block the current thread.
///
Expand Down Expand Up @@ -97,7 +123,7 @@ impl WasiCtxBuilder {
/// At this time environment variables are not deduplicated and if the same
/// key is set twice then the guest will see two entries for the same key.
pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
self.env.extend(
self.cli.environment.extend(
env.iter()
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),
);
Expand All @@ -109,7 +135,8 @@ impl WasiCtxBuilder {
/// At this time environment variables are not deduplicated and if the same
/// key is set twice then the guest will see two entries for the same key.
pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
self.env
self.cli
.environment
.push((k.as_ref().to_owned(), v.as_ref().to_owned()));
self
}
Expand All @@ -125,13 +152,15 @@ impl WasiCtxBuilder {

/// Appends a list of arguments to the argument array to pass to wasm.
pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
self.cli
.arguments
.extend(args.iter().map(|a| a.as_ref().to_owned()));
self
}

/// Appends a single argument to get passed to wasm.
pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
self.args.push(arg.as_ref().to_owned());
self.cli.arguments.push(arg.as_ref().to_owned());
self
}

Expand Down
1 change: 1 addition & 0 deletions crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//!
//! For WASIp3, see [`p3`]. WASIp3 support is experimental, unstable and incomplete.

pub mod cli;
pub mod clocks;
mod ctx;
mod error;
Expand Down
35 changes: 19 additions & 16 deletions crates/wasi/src/p2/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::cli::WasiCliCtx;
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
use crate::ctx::AllowedNetworkUses;
use crate::net::{SocketAddrCheck, SocketAddrUse};
Expand Down Expand Up @@ -36,10 +37,7 @@ use std::pin::Pin;
///
/// [`Store`]: wasmtime::Store
pub struct WasiCtxBuilder {
common: crate::WasiCtxBuilder,
stdin: Box<dyn StdinStream>,
stdout: Box<dyn StdoutStream>,
stderr: Box<dyn StdoutStream>,
common: crate::WasiCtxBuilder<Box<dyn StdinStream>, Box<dyn StdoutStream>>,
preopens: Vec<(Dir, String)>,
built: bool,
}
Expand All @@ -64,10 +62,11 @@ impl WasiCtxBuilder {
/// methods below.
pub fn new() -> Self {
Self {
common: crate::WasiCtxBuilder::new(),
stdin: Box::new(pipe::ClosedInputStream),
stdout: Box::new(pipe::SinkOutputStream),
stderr: Box::new(pipe::SinkOutputStream),
common: crate::WasiCtxBuilder::new(
Box::new(pipe::ClosedInputStream),
Box::new(pipe::SinkOutputStream),
Box::new(pipe::SinkOutputStream),
),
preopens: Vec::new(),
built: false,
}
Expand All @@ -88,19 +87,19 @@ impl WasiCtxBuilder {
/// Note that inheriting the process's stdin can also be done through
/// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin).
pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {
self.stdin = Box::new(stdin);
self.common.stdin(Box::new(stdin));
self
}

/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {
self.stdout = Box::new(stdout);
self.common.stdout(Box::new(stdout));
self
}

/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {
self.stderr = Box::new(stderr);
self.common.stderr(Box::new(stderr));
self
}

Expand Down Expand Up @@ -436,8 +435,6 @@ impl WasiCtxBuilder {
let Self {
common:
crate::WasiCtxBuilder {
env,
args,
random:
WasiRandomCtx {
random,
Expand All @@ -449,13 +446,19 @@ impl WasiCtxBuilder {
wall_clock,
monotonic_clock,
},
cli:
WasiCliCtx {
environment: env,
arguments: args,
initial_cwd: _,
stdin,
stdout,
stderr,
},
socket_addr_check,
allowed_network_uses,
allow_blocking_current_thread,
},
stdin,
stdout,
stderr,
preopens,
built: _,
} = mem::replace(self, Self::new());
Expand Down
Loading