Skip to content

Commit

Permalink
Backport 8066 to release-20.0.0 (#8351)
Browse files Browse the repository at this point in the history
* wasmtime-c-api: switch from wasi-common to wasmtime-wasi (#8066)

* wasmtime-c-api: switch from using wasi-common to wasmtime-wasi

* Fix WasiP1Ctx references, and stop eagerly opening dirs for preopens

* Add OutputFile to skip async writes in stdout/stderr

---------

Co-authored-by: Trevor Elliott <telliott@fastly.com>

* Add release notes for 8066

---------

Co-authored-by: Pat Hickey <phickey@fastly.com>
  • Loading branch information
elliottt and Pat Hickey committed Apr 12, 2024
1 parent deb0577 commit a072a22
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 87 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Unreleased.
the wasi-cli APIs. `-S common` is still accepted for now, and will be deprecated
in the future.

* C API bindings now depend on `wasmtime-wasi` instead of `wasi-common`, and the
`wasi_config_preopen_socket` function is no longer available as a result.
[#8066](https://github.com/bytecodealliance/wasmtime/pull/8066)

--------------------------------------------------------------------------------

## 19.0.0
Expand Down
5 changes: 3 additions & 2 deletions crates/c-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ wat = { workspace = true, optional = true }

# Optional dependencies for the `wasi` feature
cap-std = { workspace = true, optional = true }
wasi-common = { workspace = true, optional = true, features = ["sync"] }
tokio = { workspace = true, optional = true, features = ["fs"] }
wasmtime-wasi = { workspace = true, optional = true, features = ["preview1"] }

# Optional dependencies for the `async` feature
futures = { workspace = true, optional = true }
Expand All @@ -42,7 +43,7 @@ async = ['wasmtime/async', 'futures']
profiling = ["wasmtime/profiling"]
cache = ["wasmtime/cache"]
parallel-compilation = ['wasmtime/parallel-compilation']
wasi = ['cap-std', 'wasi-common']
wasi = ['cap-std', 'wasmtime-wasi', 'tokio']
logging = ['dep:env_logger']
disable-logging = ["log/max_level_off", "tracing/max_level_off"]
coredump = ["wasmtime/coredump"]
Expand Down
2 changes: 1 addition & 1 deletion crates/c-api/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub extern "C" fn wasmtime_error_message(error: &wasmtime_error_t, message: &mut
#[no_mangle]
pub extern "C" fn wasmtime_error_exit_status(raw: &wasmtime_error_t, status: &mut i32) -> bool {
#[cfg(feature = "wasi")]
if let Some(exit) = raw.error.downcast_ref::<wasi_common::I32Exit>() {
if let Some(exit) = raw.error.downcast_ref::<wasmtime_wasi::I32Exit>() {
*status = exit.0;
return true;
}
Expand Down
6 changes: 2 additions & 4 deletions crates/c-api/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,8 @@ pub extern "C" fn wasmtime_linker_define_wasi(
linker: &mut wasmtime_linker_t,
) -> Option<Box<wasmtime_error_t>> {
handle_result(
wasi_common::sync::add_to_linker(&mut linker.linker, |cx| {
cx.wasi.as_mut().expect(
"failed to define WASI on linker; did you set a WASI configuration in the store?",
)
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker.linker, |ctx| {
ctx.wasi.as_mut().expect("wasi context must be populated")
}),
|_linker| (),
)
Expand Down
2 changes: 1 addition & 1 deletion crates/c-api/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub type CStoreContextMut<'a> = StoreContextMut<'a, StoreData>;
pub struct StoreData {
foreign: crate::ForeignData,
#[cfg(feature = "wasi")]
pub(crate) wasi: Option<wasi_common::WasiCtx>,
pub(crate) wasi: Option<wasmtime_wasi::preview1::WasiP1Ctx>,

/// Temporary storage for usage during a wasm->host call to store values
/// in a slice we pass to the C API.
Expand Down
105 changes: 29 additions & 76 deletions crates/c-api/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

use crate::wasm_byte_vec_t;
use anyhow::Result;
use cap_std::ambient_authority;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::slice;
use wasi_common::{
pipe::ReadPipe,
sync::{Dir, TcpListener, WasiCtxBuilder},
WasiCtx,
};
use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder};

unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
CStr::from_ptr(path).to_str().map(Path::new).ok()
Expand All @@ -39,8 +33,7 @@ pub struct wasi_config_t {
stdin: WasiConfigReadPipe,
stdout: WasiConfigWritePipe,
stderr: WasiConfigWritePipe,
preopen_dirs: Vec<(Dir, PathBuf)>,
preopen_sockets: HashMap<u32, TcpListener>,
preopen_dirs: Vec<(PathBuf, String)>,
inherit_args: bool,
inherit_env: bool,
}
Expand All @@ -67,20 +60,20 @@ pub enum WasiConfigWritePipe {
wasmtime_c_api_macros::declare_own!(wasi_config_t);

impl wasi_config_t {
pub fn into_wasi_ctx(self) -> Result<WasiCtx> {
pub fn into_wasi_ctx(self) -> Result<WasiP1Ctx> {
let mut builder = WasiCtxBuilder::new();
if self.inherit_args {
builder.inherit_args()?;
builder.inherit_args();
} else if !self.args.is_empty() {
let args = self
.args
.into_iter()
.map(|bytes| Ok(String::from_utf8(bytes)?))
.collect::<Result<Vec<String>>>()?;
builder.args(&args)?;
builder.args(&args);
}
if self.inherit_env {
builder.inherit_env()?;
builder.inherit_env();
} else if !self.env.is_empty() {
let env = self
.env
Expand All @@ -91,21 +84,23 @@ impl wasi_config_t {
Ok((k, v))
})
.collect::<Result<Vec<(String, String)>>>()?;
builder.envs(&env)?;
builder.envs(&env);
}
match self.stdin {
WasiConfigReadPipe::None => {}
WasiConfigReadPipe::Inherit => {
builder.inherit_stdin();
}
WasiConfigReadPipe::File(file) => {
let file = cap_std::fs::File::from_std(file);
let file = wasi_common::sync::file::File::from_cap_std(file);
builder.stdin(Box::new(file));
let file = tokio::fs::File::from_std(file);
let stdin_stream = wasmtime_wasi::AsyncStdinStream::new(
wasmtime_wasi::pipe::AsyncReadStream::new(file),
);
builder.stdin(stdin_stream);
}
WasiConfigReadPipe::Bytes(binary) => {
let binary = ReadPipe::from(binary);
builder.stdin(Box::new(binary));
let binary = wasmtime_wasi::pipe::MemoryInputPipe::new(binary);
builder.stdin(binary);
}
};
match self.stdout {
Expand All @@ -114,9 +109,7 @@ impl wasi_config_t {
builder.inherit_stdout();
}
WasiConfigWritePipe::File(file) => {
let file = cap_std::fs::File::from_std(file);
let file = wasi_common::sync::file::File::from_cap_std(file);
builder.stdout(Box::new(file));
builder.stdout(wasmtime_wasi::OutputFile::new(file));
}
};
match self.stderr {
Expand All @@ -125,18 +118,18 @@ impl wasi_config_t {
builder.inherit_stderr();
}
WasiConfigWritePipe::File(file) => {
let file = cap_std::fs::File::from_std(file);
let file = wasi_common::sync::file::File::from_cap_std(file);
builder.stderr(Box::new(file));
builder.stderr(wasmtime_wasi::OutputFile::new(file));
}
};
for (dir, path) in self.preopen_dirs {
builder.preopened_dir(dir, path)?;
for (host_path, guest_path) in self.preopen_dirs {
builder.preopened_dir(
host_path,
guest_path,
wasmtime_wasi::DirPerms::all(),
wasmtime_wasi::FilePerms::all(),
)?;
}
for (fd_num, listener) in self.preopen_sockets {
builder.preopened_socket(fd_num, listener)?;
}
Ok(builder.build())
Ok(builder.build_p1())
}
}

Expand Down Expand Up @@ -268,59 +261,19 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
path: *const c_char,
guest_path: *const c_char,
) -> bool {
let guest_path = match cstr_to_path(guest_path) {
let guest_path = match cstr_to_str(guest_path) {
Some(p) => p,
None => return false,
};

let dir = match cstr_to_path(path) {
Some(p) => match Dir::open_ambient_dir(p, ambient_authority()) {
Ok(d) => d,
Err(_) => return false,
},
None => return false,
};

(*config).preopen_dirs.push((dir, guest_path.to_owned()));

true
}

#[no_mangle]
pub unsafe extern "C" fn wasi_config_preopen_socket(
config: &mut wasi_config_t,
fd_num: u32,
host_port: *const c_char,
) -> bool {
const VAR: &str = "WASMTIME_WASI_CONFIG_PREOPEN_SOCKET_ALLOW";
if std::env::var(VAR).is_err() {
panic!(
"wasmtime c-api: wasi_config_preopen_socket will be deprecated in the \
wasmtime 20.0.0 release. set {VAR} to enable temporarily"
);
}

let address = match cstr_to_str(host_port) {
Some(s) => s,
let host_path = match cstr_to_path(path) {
Some(p) => p,
None => return false,
};
let listener = match std::net::TcpListener::bind(address) {
Ok(listener) => listener,
Err(_) => return false,
};

if let Err(_) = listener.set_nonblocking(true) {
return false;
}

// Caller cannot call in more than once with the same FD number so return an error.
if (*config).preopen_sockets.contains_key(&fd_num) {
return false;
}

(*config)
.preopen_sockets
.insert(fd_num, TcpListener::from_std(listener));
.preopen_dirs
.push((host_path.to_owned(), guest_path.to_owned()));

true
}
4 changes: 2 additions & 2 deletions crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ pub use self::network::{Network, SocketAddrUse, SocketError, SocketResult};
pub use self::poll::{subscribe, ClosureFuture, MakeFuture, Pollable, PollableFuture, Subscribe};
pub use self::random::{thread_rng, Deterministic};
pub use self::stdio::{
stderr, stdin, stdout, AsyncStdinStream, AsyncStdoutStream, IsATTY, Stderr, Stdin, StdinStream,
Stdout, StdoutStream,
stderr, stdin, stdout, AsyncStdinStream, AsyncStdoutStream, IsATTY, OutputFile, Stderr, Stdin,
StdinStream, Stdout, StdoutStream,
};
pub use self::stream::{
HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, StreamResult,
Expand Down
56 changes: 56 additions & 0 deletions crates/wasi/src/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,62 @@ impl StdoutStream for pipe::ClosedOutputStream {
}
}

/// This implementation will yield output streams that block on writes, and
/// output directly to a file. If truly async output is required, [`AsyncStdoutStream`]
/// should be used instead.
pub struct OutputFile {
file: Arc<std::fs::File>,
}

impl OutputFile {
pub fn new(file: std::fs::File) -> Self {
Self {
file: Arc::new(file),
}
}
}

impl StdoutStream for OutputFile {
fn stream(&self) -> Box<dyn HostOutputStream> {
Box::new(OutputFileStream {
file: Arc::clone(&self.file),
})
}

fn isatty(&self) -> bool {
false
}
}

struct OutputFileStream {
file: Arc<std::fs::File>,
}

#[async_trait::async_trait]
impl Subscribe for OutputFileStream {
async fn ready(&mut self) {}
}

impl HostOutputStream for OutputFileStream {
fn write(&mut self, bytes: Bytes) -> StreamResult<()> {
use std::io::Write;
self.file
.write_all(&bytes)
.map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e)))
}

fn flush(&mut self) -> StreamResult<()> {
use std::io::Write;
self.file
.flush()
.map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e)))
}

fn check_write(&mut self) -> StreamResult<usize> {
Ok(1024 * 1024)
}
}

/// This implementation will yield output streams that block on writes, as they
/// inherit the implementation directly from the rust std library. A different
/// implementation of [`StdoutStream`] will be necessary if truly async output
Expand Down

0 comments on commit a072a22

Please sign in to comment.