Support WASI snapshot0 in the C API.

This commit adds support for snapshot0 in the WASI C API.

A name parameter was added to `wasi_instance_new` to accept which WASI module
is being instantiated.

Additionally, the C# API now supports constructing a WASI instance based on the
WASI module name.

Fixes #1221.
This commit is contained in:
Peter Huene
2020-03-20 16:47:25 -07:00
parent 853d5f304d
commit 0d5d63fdb1
12 changed files with 613 additions and 120 deletions

View File

@@ -52,6 +52,7 @@ WASI_DECLARE_OWN(instance)
WASI_API_EXTERN own wasi_instance_t* wasi_instance_new( WASI_API_EXTERN own wasi_instance_t* wasi_instance_new(
wasm_store_t* store, wasm_store_t* store,
const char* name,
own wasi_config_t* config, own wasi_config_t* config,
own wasm_trap_t** trap own wasm_trap_t** trap
); );

View File

@@ -185,7 +185,7 @@ pub unsafe extern "C" fn wasmtime_linker_define_wasi(
instance: *const wasi_instance_t, instance: *const wasi_instance_t,
) -> bool { ) -> bool {
let linker = &mut (*linker).linker; let linker = &mut (*linker).linker;
(*instance).wasi.add_to_linker(linker).is_ok() (*instance).add_to_linker(linker).is_ok()
} }
#[no_mangle] #[no_mangle]

View File

@@ -1,14 +1,18 @@
//! The WASI embedding API definitions for Wasmtime. //! The WASI embedding API definitions for Wasmtime.
use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType}; use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost, ExternType};
use anyhow::Result;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::CStr; use std::ffi::CStr;
use std::fs::File; use std::fs::File;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::path::Path; use std::path::{Path, PathBuf};
use std::slice; use std::slice;
use wasi_common::{preopen_dir, WasiCtxBuilder}; use wasi_common::{
use wasmtime::{HostRef, Trap}; old::snapshot_0::WasiCtxBuilder as WasiSnapshot0CtxBuilder, preopen_dir,
use wasmtime_wasi::Wasi; WasiCtxBuilder as WasiPreview1CtxBuilder,
};
use wasmtime::{HostRef, Linker, Store, Trap};
use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi as WasiPreview1};
unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> { unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
CStr::from_ptr(path).to_str().map(Path::new).ok() CStr::from_ptr(path).to_str().map(Path::new).ok()
@@ -22,18 +26,32 @@ unsafe fn create_file(path: *const c_char) -> Option<File> {
File::create(cstr_to_path(path)?).ok() File::create(cstr_to_path(path)?).ok()
} }
#[repr(C)] pub enum WasiModule {
pub struct wasi_config_t { Snapshot0(WasiSnapshot0),
builder: WasiCtxBuilder, Preview1(WasiPreview1),
} }
impl wasi_config_t {} impl WasiModule {}
#[repr(C)]
#[derive(Default)]
pub struct wasi_config_t {
args: Vec<Vec<u8>>,
env: Vec<(Vec<u8>, Vec<u8>)>,
stdin: Option<File>,
stdout: Option<File>,
stderr: Option<File>,
preopens: Vec<(File, PathBuf)>,
inherit_args: bool,
inherit_env: bool,
inherit_stdin: bool,
inherit_stdout: bool,
inherit_stderr: bool,
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t { pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t {
Box::into_raw(Box::new(wasi_config_t { Box::into_raw(Box::new(wasi_config_t::default()))
builder: WasiCtxBuilder::new(),
}))
} }
#[no_mangle] #[no_mangle]
@@ -47,16 +65,17 @@ pub unsafe extern "C" fn wasi_config_set_argv(
argc: c_int, argc: c_int,
argv: *const *const c_char, argv: *const *const c_char,
) { ) {
(*config).builder.args( (*config).args = slice::from_raw_parts(argv, argc as usize)
slice::from_raw_parts(argv, argc as usize) .iter()
.iter() .map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
.map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())), .collect();
); (*config).inherit_args = false;
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) { pub unsafe extern "C" fn wasi_config_inherit_argv(config: *mut wasi_config_t) {
(*config).builder.inherit_args(); (*config).args.clear();
(*config).inherit_args = true;
} }
#[no_mangle] #[no_mangle]
@@ -69,17 +88,22 @@ pub unsafe extern "C" fn wasi_config_set_env(
let names = slice::from_raw_parts(names, envc as usize); let names = slice::from_raw_parts(names, envc as usize);
let values = slice::from_raw_parts(values, envc as usize); let values = slice::from_raw_parts(values, envc as usize);
(*config).builder.envs( (*config).env = names
names .iter()
.iter() .map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
.map(|p| CStr::from_ptr(*p).to_bytes()) .zip(
.zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())), values
); .iter()
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned()),
)
.collect();
(*config).inherit_env = false;
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) { pub unsafe extern "C" fn wasi_config_inherit_env(config: *mut wasi_config_t) {
(*config).builder.inherit_env(); (*config).env.clear();
(*config).inherit_env = true;
} }
#[no_mangle] #[no_mangle]
@@ -92,14 +116,16 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file(
None => return false, None => return false,
}; };
(*config).builder.stdin(file); (*config).stdin = Some(file);
(*config).inherit_stdin = false;
true true
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) { pub unsafe extern "C" fn wasi_config_inherit_stdin(config: *mut wasi_config_t) {
(*config).builder.inherit_stdin(); (*config).stdin = None;
(*config).inherit_stdin = true;
} }
#[no_mangle] #[no_mangle]
@@ -112,14 +138,16 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file(
None => return false, None => return false,
}; };
(*config).builder.stdout(file); (*config).stdout = Some(file);
(*config).inherit_stdout = false;
true true
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) { pub unsafe extern "C" fn wasi_config_inherit_stdout(config: *mut wasi_config_t) {
(*config).builder.inherit_stdout(); (*config).stdout = None;
(*config).inherit_stdout = true;
} }
#[no_mangle] #[no_mangle]
@@ -132,14 +160,16 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file(
None => return false, None => return false,
}; };
(*config).builder.stderr(file); (*config).stderr = Some(file);
(*config).inherit_stderr = false;
true true
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) { pub unsafe extern "C" fn wasi_config_inherit_stderr(config: *mut wasi_config_t) {
(*config).builder.inherit_stderr(); (*config).stderr = None;
(*config).inherit_stderr = true;
} }
#[no_mangle] #[no_mangle]
@@ -161,29 +191,110 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
None => return false, None => return false,
}; };
(*config).builder.preopened_dir(dir, guest_path); (*config).preopens.push((dir, guest_path.to_owned()));
true true
} }
enum WasiInstance {
Preview1(WasiPreview1),
Snapshot0(WasiSnapshot0),
}
macro_rules! config_to_builder {
($builder:ident, $config:ident) => {{
let mut builder = $builder::new();
if $config.inherit_args {
builder.inherit_args();
} else if !$config.args.is_empty() {
builder.args($config.args);
}
if $config.inherit_env {
builder.inherit_env();
} else if !$config.env.is_empty() {
builder.envs($config.env);
}
if $config.inherit_stdin {
builder.inherit_stdin();
} else if let Some(file) = $config.stdin {
builder.stdin(file);
}
if $config.inherit_stdout {
builder.inherit_stdout();
} else if let Some(file) = $config.stdout {
builder.stdout(file);
}
if $config.inherit_stderr {
builder.inherit_stderr();
} else if let Some(file) = $config.stderr {
builder.stderr(file);
}
for preopen in $config.preopens {
builder.preopened_dir(preopen.0, preopen.1);
}
builder
}};
}
fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
Ok(WasiInstance::Snapshot0(WasiSnapshot0::new(
store,
config_to_builder!(WasiSnapshot0CtxBuilder, config)
.build()
.map_err(|e| e.to_string())?,
)))
}
fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
Ok(WasiInstance::Preview1(WasiPreview1::new(
store,
config_to_builder!(WasiPreview1CtxBuilder, config)
.build()
.map_err(|e| e.to_string())?,
)))
}
#[repr(C)] #[repr(C)]
pub struct wasi_instance_t { pub struct wasi_instance_t {
pub wasi: Wasi, wasi: WasiInstance,
export_cache: HashMap<String, Box<wasm_extern_t>>, export_cache: HashMap<String, Box<wasm_extern_t>>,
} }
impl wasi_instance_t {
pub fn add_to_linker(&self, linker: &mut Linker) -> Result<()> {
match &self.wasi {
WasiInstance::Snapshot0(w) => w.add_to_linker(linker),
WasiInstance::Preview1(w) => w.add_to_linker(linker),
}
}
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasi_instance_new( pub unsafe extern "C" fn wasi_instance_new(
store: *mut wasm_store_t, store: *mut wasm_store_t,
name: *const c_char,
config: *mut wasi_config_t, config: *mut wasi_config_t,
trap: *mut *mut wasm_trap_t, trap: *mut *mut wasm_trap_t,
) -> *mut wasi_instance_t { ) -> *mut wasi_instance_t {
let store = &(*store).store.borrow(); let store = &(*store).store.borrow();
let mut config = Box::from_raw(config); let config = Box::from_raw(config);
match config.builder.build() { let result = match CStr::from_ptr(name).to_str().unwrap_or("") {
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t { "wasi_snapshot_preview1" => create_preview1_instance(store, *config),
wasi: Wasi::new(store, ctx), "wasi_unstable" => create_snapshot0_instance(store, *config),
_ => Err("unsupported WASI version".into()),
};
match result {
Ok(wasi) => Box::into_raw(Box::new(wasi_instance_t {
wasi,
export_cache: HashMap::new(), export_cache: HashMap::new(),
})), })),
Err(e) => { Err(e) => {
@@ -206,20 +317,32 @@ pub unsafe extern "C" fn wasi_instance_bind_import(
instance: *mut wasi_instance_t, instance: *mut wasi_instance_t,
import: *const wasm_importtype_t, import: *const wasm_importtype_t,
) -> *const wasm_extern_t { ) -> *const wasm_extern_t {
// TODO: support previous versions?
if (*import).ty.module() != "wasi_snapshot_preview1" {
return std::ptr::null_mut();
}
// The import should be a function (WASI only exports functions) // The import should be a function (WASI only exports functions)
let func_type = match (*import).ty.ty() { let func_type = match (*import).ty.ty() {
ExternType::Func(f) => f, ExternType::Func(f) => f,
_ => return std::ptr::null_mut(), _ => return std::ptr::null_mut(),
}; };
let module = (*import).ty.module();
let name = (*import).ty.name(); let name = (*import).ty.name();
match (*instance).wasi.get_export(name) { let import = match &(*instance).wasi {
WasiInstance::Preview1(wasi) => {
if module != "wasi_snapshot_preview1" {
return std::ptr::null();
}
wasi.get_export(name)
}
WasiInstance::Snapshot0(wasi) => {
if module != "wasi_unstable" {
return std::ptr::null();
}
wasi.get_export(name)
}
};
match import {
Some(export) => { Some(export) => {
if export.ty() != func_type { if export.ty() != func_type {
return std::ptr::null_mut(); return std::ptr::null_mut();

View File

@@ -936,6 +936,7 @@ namespace Wasmtime
[DllImport(LibraryName)] [DllImport(LibraryName)]
public static extern WasiInstanceHandle wasi_instance_new( public static extern WasiInstanceHandle wasi_instance_new(
StoreHandle store, StoreHandle store,
[MarshalAs(UnmanagedType.LPUTF8Str)] string name,
WasiConfigHandle config, WasiConfigHandle config,
out IntPtr trap out IntPtr trap
); );

View File

@@ -9,18 +9,26 @@ namespace Wasmtime
/// <summary> /// <summary>
/// Creates a default <see cref="Wasi"/> instance. /// Creates a default <see cref="Wasi"/> instance.
/// </summary> /// </summary>
public Wasi(Store store) : /// <param name="store">The store to use for the new WASI instance.</param>
/// <param name="name">The name of the WASI module to create.</param>
public Wasi(Store store, string name) :
this( this(
(store ?? throw new ArgumentNullException(nameof(store))).Handle, (store ?? throw new ArgumentNullException(nameof(store))).Handle,
Interop.wasi_config_new() Interop.wasi_config_new(),
name
) )
{ {
} }
internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config) internal Wasi(Interop.StoreHandle store, Interop.WasiConfigHandle config, string name)
{ {
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
}
IntPtr trap; IntPtr trap;
Handle = Interop.wasi_instance_new(store, config, out trap); Handle = Interop.wasi_instance_new(store, name, config, out trap);
config.SetHandleAsInvalid(); config.SetHandleAsInvalid();
if (trap != IntPtr.Zero) if (trap != IntPtr.Zero)

View File

@@ -12,10 +12,22 @@ namespace Wasmtime
/// <summary> /// <summary>
/// Constructs a new <see cref="WasiBuilder" />. /// Constructs a new <see cref="WasiBuilder" />.
/// </summary> /// </summary>
public WasiBuilder() public WasiBuilder(string name)
{ {
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name cannot be null or empty.", nameof(name));
}
Name = name;
} }
/// <summary>
/// Gets the name of the WASI module being built.
/// </summary>
/// <value>The name of the WASI module.</value>
public string Name { get; private set; }
/// <summary> /// <summary>
/// Adds a command line argument to the builder. /// Adds a command line argument to the builder.
/// </summary> /// </summary>
@@ -271,7 +283,7 @@ namespace Wasmtime
SetStandardError(config); SetStandardError(config);
SetPreopenDirectories(config); SetPreopenDirectories(config);
return new Wasi(store.Handle, config); return new Wasi(store.Handle, config, Name);
} }
private unsafe void SetConfigArgs(Interop.WasiConfigHandle config) private unsafe void SetConfigArgs(Interop.WasiConfigHandle config)

View File

@@ -0,0 +1,66 @@
(module
(type $t0 (func (param i32 i32) (result i32)))
(type $t1 (func (param i32 i32 i32 i32) (result i32)))
(type $t2 (func (param i32) (result i32)))
(type $t3 (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
(import "wasi_unstable" "environ_sizes_get" (func $wasi_unstable.environ_sizes_get (type $t0)))
(import "wasi_unstable" "environ_get" (func $wasi_unstable.environ_get (type $t0)))
(import "wasi_unstable" "args_sizes_get" (func $wasi_unstable.args_sizes_get (type $t0)))
(import "wasi_unstable" "args_get" (func $wasi_unstable.args_get (type $t0)))
(import "wasi_unstable" "fd_write" (func $wasi_unstable.fd_write (type $t1)))
(import "wasi_unstable" "fd_read" (func $wasi_unstable.fd_read (type $t1)))
(import "wasi_unstable" "fd_close" (func $wasi_unstable.fd_close (type $t2)))
(import "wasi_unstable" "path_open" (func $wasi_unstable.path_open (type $t3)))
(memory $memory 1)
(export "memory" (memory 0))
(func $call_environ_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
local.get $p0
local.get $p1
call $wasi_unstable.environ_sizes_get)
(func $call_environ_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
local.get $p0
local.get $p1
call $wasi_unstable.environ_get)
(func $call_args_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
local.get $p0
local.get $p1
call $wasi_unstable.args_sizes_get)
(func $call_args_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
local.get $p0
local.get $p1
call $wasi_unstable.args_get)
(func $call_fd_write (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32)
local.get $p0
local.get $p1
local.get $p2
local.get $p3
call $wasi_unstable.fd_write)
(func $call_fd_read (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32)
local.get $p0
local.get $p1
local.get $p2
local.get $p3
call $wasi_unstable.fd_read)
(func $call_fd_close (type $t2) (param $p0 i32) (result i32)
local.get $p0
call $wasi_unstable.fd_close)
(func $call_path_open (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i64) (param $p6 i64) (param $p7 i32) (param $p8 i32) (result i32)
local.get $p0
local.get $p1
local.get $p2
local.get $p3
local.get $p4
local.get $p5
local.get $p6
local.get $p7
local.get $p8
call $wasi_unstable.path_open)
(export "call_environ_sizes_get" (func $call_environ_sizes_get))
(export "call_environ_get" (func $call_environ_get))
(export "call_args_sizes_get" (func $call_args_sizes_get))
(export "call_args_get" (func $call_args_get))
(export "call_fd_write" (func $call_fd_write))
(export "call_fd_read" (func $call_fd_read))
(export "call_fd_close" (func $call_fd_close))
(export "call_path_open" (func $call_path_open))
)

View File

@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using FluentAssertions;
using Xunit;
namespace Wasmtime.Tests
{
public class WasiSnapshot0Fixture : ModuleFixture
{
protected override string ModuleFileName => "WasiSnapshot0.wat";
}
public class WasiSnapshot0Tests : IClassFixture<WasiSnapshot0Fixture>
{
public WasiSnapshot0Tests(WasiSnapshot0Fixture fixture)
{
Fixture = fixture;
}
private WasiSnapshot0Fixture Fixture { get; set; }
[Fact]
public void ItHasNoEnvironmentByDefault()
{
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable"));
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
Assert.Equal(0, memory.ReadInt32(0));
Assert.Equal(0, memory.ReadInt32(4));
}
[Fact]
public void ItHasSpecifiedEnvironment()
{
var env = new Dictionary<string, string>() {
{"FOO", "BAR"},
{"WASM", "IS"},
{"VERY", "COOL"},
};
var wasi = new WasiBuilder("wasi_unstable")
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)))
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
Assert.Equal(env.Count, memory.ReadInt32(0));
Assert.Equal(env.Sum(kvp => kvp.Key.Length + kvp.Value.Length + 2), memory.ReadInt32(4));
Assert.Equal(0, inst.call_environ_get(0, 4 * env.Count));
for (int i = 0; i < env.Count; ++i)
{
var kvp = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)).Split("=");
Assert.Equal(env[kvp[0]], kvp[1]);
}
}
[Fact]
public void ItInheritsEnvironment()
{
var wasi = new WasiBuilder("wasi_unstable")
.WithInheritedEnvironment()
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_environ_sizes_get(0, 4));
Assert.Equal(Environment.GetEnvironmentVariables().Keys.Count, memory.ReadInt32(0));
}
[Fact]
public void ItHasNoArgumentsByDefault()
{
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_unstable"));
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
Assert.Equal(0, memory.ReadInt32(0));
Assert.Equal(0, memory.ReadInt32(4));
}
[Fact]
public void ItHasSpecifiedArguments()
{
var args = new List<string>() {
"WASM",
"IS",
"VERY",
"COOL"
};
var wasi = new WasiBuilder("wasi_unstable")
.WithArgs(args)
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
Assert.Equal(args.Count, memory.ReadInt32(0));
Assert.Equal(args.Sum(a => a.Length + 1), memory.ReadInt32(4));
Assert.Equal(0, inst.call_args_get(0, 4 * args.Count));
for (int i = 0; i < args.Count; ++i)
{
var arg = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4));
Assert.Equal(args[i], arg);
}
}
[Fact]
public void ItInheritsArguments()
{
var wasi = new WasiBuilder("wasi_unstable")
.WithInheritedArgs()
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
Assert.Equal(0, inst.call_args_sizes_get(0, 4));
Assert.Equal(Environment.GetCommandLineArgs().Length, memory.ReadInt32(0));
}
[Fact]
public void ItSetsStdIn()
{
const string MESSAGE = "WASM IS VERY COOL";
using var file = new TempFile();
File.WriteAllText(file.Path, MESSAGE);
var wasi = new WasiBuilder("wasi_unstable")
.WithStandardInput(file.Path)
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
memory.WriteInt32(0, 8);
memory.WriteInt32(4, MESSAGE.Length);
Assert.Equal(0, inst.call_fd_read(0, 0, 1, 32));
Assert.Equal(MESSAGE.Length, memory.ReadInt32(32));
Assert.Equal(MESSAGE, memory.ReadString(8, MESSAGE.Length));
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void ItSetsStdOutAndStdErr(int fd)
{
const string MESSAGE = "WASM IS VERY COOL";
using var file = new TempFile();
var builder = new WasiBuilder("wasi_unstable");
if (fd == 1)
{
builder.WithStandardOutput(file.Path);
}
else if (fd == 2)
{
builder.WithStandardError(file.Path);
}
var wasi = builder.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
memory.WriteInt32(0, 8);
memory.WriteInt32(4, MESSAGE.Length);
memory.WriteString(8, MESSAGE);
Assert.Equal(0, inst.call_fd_write(fd, 0, 1, 32));
Assert.Equal(MESSAGE.Length, memory.ReadInt32(32));
Assert.Equal(0, inst.call_fd_close(fd));
Assert.Equal(MESSAGE, File.ReadAllText(file.Path));
}
[Fact]
public void ItSetsPreopenDirectories()
{
const string MESSAGE = "WASM IS VERY COOL";
using var file = new TempFile();
var wasi = new WasiBuilder("wasi_unstable")
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo")
.Build(Fixture.Module.Store);
using var instance = Fixture.Module.Instantiate(wasi);
dynamic inst = instance;
var memory = instance.Externs.Memories[0];
var fileName = Path.GetFileName(file.Path);
memory.WriteString(0, fileName);
Assert.Equal(0, inst.call_path_open(
3,
0,
0,
fileName.Length,
0,
0x40 /* RIGHTS_FD_WRITE */,
0,
0,
64
)
);
var fileFd = (int) memory.ReadInt32(64);
Assert.True(fileFd > 3);
memory.WriteInt32(0, 8);
memory.WriteInt32(4, MESSAGE.Length);
memory.WriteString(8, MESSAGE);
Assert.Equal(0, inst.call_fd_write(fileFd, 0, 1, 64));
Assert.Equal(MESSAGE.Length, memory.ReadInt32(64));
Assert.Equal(0, inst.call_fd_close(fileFd));
Assert.Equal(MESSAGE, File.ReadAllText(file.Path));
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItHasNoEnvironmentByDefault() public void ItHasNoEnvironmentByDefault()
{ {
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1"));
dynamic inst = instance; dynamic inst = instance;
var memory = instance.Externs.Memories[0]; var memory = instance.Externs.Memories[0];
@@ -43,7 +43,7 @@ namespace Wasmtime.Tests
{"VERY", "COOL"}, {"VERY", "COOL"},
}; };
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)))
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);
@@ -67,7 +67,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItInheritsEnvironment() public void ItInheritsEnvironment()
{ {
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithInheritedEnvironment() .WithInheritedEnvironment()
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);
@@ -83,7 +83,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItHasNoArgumentsByDefault() public void ItHasNoArgumentsByDefault()
{ {
using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store, "wasi_snapshot_preview1"));
dynamic inst = instance; dynamic inst = instance;
var memory = instance.Externs.Memories[0]; var memory = instance.Externs.Memories[0];
@@ -103,7 +103,7 @@ namespace Wasmtime.Tests
"COOL" "COOL"
}; };
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithArgs(args) .WithArgs(args)
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);
@@ -127,7 +127,7 @@ namespace Wasmtime.Tests
[Fact] [Fact]
public void ItInheritsArguments() public void ItInheritsArguments()
{ {
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithInheritedArgs() .WithInheritedArgs()
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);
@@ -148,7 +148,7 @@ namespace Wasmtime.Tests
using var file = new TempFile(); using var file = new TempFile();
File.WriteAllText(file.Path, MESSAGE); File.WriteAllText(file.Path, MESSAGE);
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithStandardInput(file.Path) .WithStandardInput(file.Path)
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);
@@ -173,7 +173,7 @@ namespace Wasmtime.Tests
using var file = new TempFile(); using var file = new TempFile();
var builder = new WasiBuilder(); var builder = new WasiBuilder("wasi_snapshot_preview1");
if (fd == 1) if (fd == 1)
{ {
builder.WithStandardOutput(file.Path); builder.WithStandardOutput(file.Path);
@@ -206,7 +206,7 @@ namespace Wasmtime.Tests
using var file = new TempFile(); using var file = new TempFile();
var wasi = new WasiBuilder() var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo")
.Build(Fixture.Module.Store); .Build(Fixture.Module.Store);

View File

@@ -101,46 +101,46 @@ impl PendingCString {
/// A builder allowing customizable construction of `WasiCtx` instances. /// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder { pub struct WasiCtxBuilder {
stdin: PendingEntry, stdin: Option<PendingEntry>,
stdout: PendingEntry, stdout: Option<PendingEntry>,
stderr: PendingEntry, stderr: Option<PendingEntry>,
preopens: Vec<(PathBuf, File)>, preopens: Option<Vec<(PathBuf, File)>>,
args: Vec<PendingCString>, args: Option<Vec<PendingCString>>,
env: HashMap<PendingCString, PendingCString>, env: Option<HashMap<PendingCString, PendingCString>>,
} }
impl WasiCtxBuilder { impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`. /// Builder for a new `WasiCtx`.
pub fn new() -> Self { pub fn new() -> Self {
let stdin = PendingEntry::Thunk(Entry::null);
let stdout = PendingEntry::Thunk(Entry::null);
let stderr = PendingEntry::Thunk(Entry::null);
Self { Self {
stdin, stdin: Some(PendingEntry::Thunk(Entry::null)),
stdout, stdout: Some(PendingEntry::Thunk(Entry::null)),
stderr, stderr: Some(PendingEntry::Thunk(Entry::null)),
preopens: Vec::new(), preopens: Some(Vec::new()),
args: vec![], args: Some(Vec::new()),
env: HashMap::new(), env: Some(HashMap::new()),
} }
} }
/// Add arguments to the command-line arguments list. /// Add arguments to the command-line arguments list.
/// ///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
pub fn args<S: AsRef<[u8]>>(mut self, args: impl IntoIterator<Item = S>) -> Self { pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args = args self.args
.into_iter() .as_mut()
.map(|arg| arg.as_ref().to_vec().into()) .unwrap()
.collect(); .extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
self self
} }
/// Add an argument to the command-line arguments list. /// Add an argument to the command-line arguments list.
/// ///
/// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail.
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self { pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args.push(arg.as_ref().to_vec().into()); self.args
.as_mut()
.unwrap()
.push(arg.as_ref().to_vec().into());
self self
} }
@@ -148,16 +148,36 @@ impl WasiCtxBuilder {
/// ///
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
/// fail. /// fail.
pub fn inherit_args(mut self) -> Self { pub fn inherit_args(&mut self) -> &mut Self {
self.args = env::args_os().map(PendingCString::OsString).collect(); let args = self.args.as_mut().unwrap();
args.clear();
args.extend(env::args_os().map(PendingCString::OsString));
self
}
/// Inherit stdin from the host process.
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
self
}
/// Inherit stdout from the host process.
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
self self
} }
/// Inherit the stdin, stdout, and stderr streams from the host process. /// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(mut self) -> Self { pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = PendingEntry::Thunk(Entry::duplicate_stdin); self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
self.stdout = PendingEntry::Thunk(Entry::duplicate_stdout); self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
self.stderr = PendingEntry::Thunk(Entry::duplicate_stderr); self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
self self
} }
@@ -165,10 +185,10 @@ impl WasiCtxBuilder {
/// ///
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for /// If any environment variables from the host process contain invalid Unicode (UTF-16 for
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail. /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail.
pub fn inherit_env(mut self) -> Self { pub fn inherit_env(&mut self) -> &mut Self {
self.env = std::env::vars_os() let env = self.env.as_mut().unwrap();
.map(|(k, v)| (k.into(), v.into())) env.clear();
.collect(); env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
self self
} }
@@ -176,8 +196,10 @@ impl WasiCtxBuilder {
/// ///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail. /// `WasiCtxBuilder::build()` will fail.
pub fn env<S: AsRef<[u8]>>(mut self, k: S, v: S) -> Self { pub fn env<S: AsRef<[u8]>>(&mut self, k: S, v: S) -> &mut Self {
self.env self.env
.as_mut()
.unwrap()
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into()); .insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
self self
} }
@@ -187,40 +209,40 @@ impl WasiCtxBuilder {
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail. /// `WasiCtxBuilder::build()` will fail.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>( pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
mut self, &mut self,
envs: impl IntoIterator<Item = T>, envs: impl IntoIterator<Item = T>,
) -> Self { ) -> &mut Self {
self.env = envs self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
.into_iter() let (k, v) = t.borrow();
.map(|t| { (k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
let (k, v) = t.borrow(); }));
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
})
.collect();
self self
} }
/// Provide a File to use as stdin /// Provide a File to use as stdin
pub fn stdin(mut self, file: File) -> Self { pub fn stdin(&mut self, file: File) -> &mut Self {
self.stdin = PendingEntry::File(file); self.stdin = Some(PendingEntry::File(file));
self self
} }
/// Provide a File to use as stdout /// Provide a File to use as stdout
pub fn stdout(mut self, file: File) -> Self { pub fn stdout(&mut self, file: File) -> &mut Self {
self.stdout = PendingEntry::File(file); self.stdout = Some(PendingEntry::File(file));
self self
} }
/// Provide a File to use as stderr /// Provide a File to use as stderr
pub fn stderr(mut self, file: File) -> Self { pub fn stderr(&mut self, file: File) -> &mut Self {
self.stderr = PendingEntry::File(file); self.stderr = Some(PendingEntry::File(file));
self self
} }
/// Add a preopened directory. /// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self { pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
self.preopens.push((guest_path.as_ref().to_owned(), dir)); self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), dir));
self self
} }
@@ -228,17 +250,21 @@ impl WasiCtxBuilder {
/// ///
/// If any of the arguments or environment variables in this builder cannot be converted into /// If any of the arguments or environment variables in this builder cannot be converted into
/// `CString`s, either due to NUL bytes or Unicode conversions, this returns an error. /// `CString`s, either due to NUL bytes or Unicode conversions, this returns an error.
pub fn build(self) -> WasiCtxBuilderResult<WasiCtx> { pub fn build(&mut self) -> WasiCtxBuilderResult<WasiCtx> {
// Process arguments and environment variables into `CString`s, failing quickly if they // Process arguments and environment variables into `CString`s, failing quickly if they
// contain any NUL bytes, or if conversion from `OsString` fails. // contain any NUL bytes, or if conversion from `OsString` fails.
let args = self let args = self
.args .args
.take()
.unwrap()
.into_iter() .into_iter()
.map(|arg| arg.into_utf8_cstring()) .map(|arg| arg.into_utf8_cstring())
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?; .collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
let env = self let env = self
.env .env
.take()
.unwrap()
.into_iter() .into_iter()
.map(|(k, v)| { .map(|(k, v)| {
k.into_string().and_then(|mut pair| { k.into_string().and_then(|mut pair| {
@@ -257,12 +283,12 @@ impl WasiCtxBuilder {
let mut fd_pool = FdPool::new(); let mut fd_pool = FdPool::new();
let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new(); let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new();
// Populate the non-preopen fds. // Populate the non-preopen fds.
for pending in vec![self.stdin, self.stdout, self.stderr] { for pending in &mut [&mut self.stdin, &mut self.stdout, &mut self.stderr] {
let fd = fd_pool let fd = fd_pool
.allocate() .allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending); log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending { match pending.take().unwrap() {
PendingEntry::Thunk(f) => { PendingEntry::Thunk(f) => {
entries.insert(fd, f()?); entries.insert(fd, f()?);
} }
@@ -272,7 +298,7 @@ impl WasiCtxBuilder {
} }
} }
// Then add the preopen fds. // Then add the preopen fds.
for (guest_path, dir) in self.preopens { for (guest_path, dir) in self.preopens.take().unwrap() {
// We do the increment at the beginning of the loop body, so that we don't overflow // We do the increment at the beginning of the loop body, so that we don't overflow
// unnecessarily if we have exactly the maximum number of file descriptors. // unnecessarily if we have exactly the maximum number of file descriptors.
let preopen_fd = fd_pool let preopen_fd = fd_pool

View File

@@ -33,14 +33,19 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
let mut get_exports = Vec::new(); let mut get_exports = Vec::new();
let mut ctor_externs = Vec::new(); let mut ctor_externs = Vec::new();
let mut ctor_fields = Vec::new(); let mut ctor_fields = Vec::new();
let mut linker_add = Vec::new();
for module in doc.modules() { for module in doc.modules() {
let module_name = module.name.as_str();
for func in module.funcs() { for func in module.funcs() {
let name = func.name.as_str(); let name = func.name.as_str();
let name_ident = Ident::new(func.name.as_str(), Span::call_site()); let name_ident = Ident::new(name, Span::call_site());
fields.push(quote! { pub #name_ident: wasmtime::Func }); fields.push(quote! { pub #name_ident: wasmtime::Func });
get_exports.push(quote! { #name => Some(&self.#name_ident) }); get_exports.push(quote! { #name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone()); ctor_fields.push(name_ident.clone());
linker_add.push(quote! {
linker.define(#module_name, #name, self.#name_ident.clone())?;
});
let mut shim_arg_decls = Vec::new(); let mut shim_arg_decls = Vec::new();
let mut params = Vec::new(); let mut params = Vec::new();
@@ -249,6 +254,12 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
_ => None, _ => None,
} }
} }
/// Adds all wasi items to the specified `Linker`.
pub fn add_to_linker(&self, linker: &mut wasmtime::Linker) -> anyhow::Result<()> {
#(#linker_add)*
Ok(())
}
} }
} }
} }
@@ -270,10 +281,10 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
for module in doc.modules() { for module in doc.modules() {
let module_name = module.name.as_str(); let module_name = module.name.as_str();
let module_id = Ident::new(module.name.as_str(), Span::call_site()); let module_id = Ident::new(module_name, Span::call_site());
for func in module.funcs() { for func in module.funcs() {
let name = func.name.as_str(); let name = func.name.as_str();
let name_ident = Ident::new(func.name.as_str(), Span::call_site()); let name_ident = Ident::new(name, Span::call_site());
fields.push(quote! { pub #name_ident: wasmtime::Func }); fields.push(quote! { pub #name_ident: wasmtime::Func });
get_exports.push(quote! { #name => Some(&self.#name_ident) }); get_exports.push(quote! { #name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone()); ctor_fields.push(name_ident.clone());

View File

@@ -336,13 +336,12 @@ impl ModuleRegistry {
let cx1 = cx1.build()?; let cx1 = cx1.build()?;
let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new() let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new();
.inherit_stdio()
.args(argv) cx2.inherit_stdio().args(argv).envs(vars);
.envs(vars);
for (name, file) in preopen_dirs { for (name, file) in preopen_dirs {
cx2 = cx2.preopened_dir(file.try_clone()?, name); cx2.preopened_dir(file.try_clone()?, name);
} }
let cx2 = cx2.build()?; let cx2 = cx2.build()?;