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(
wasm_store_t* store,
const char* name,
own wasi_config_t* config,
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,
) -> bool {
let linker = &mut (*linker).linker;
(*instance).wasi.add_to_linker(linker).is_ok()
(*instance).add_to_linker(linker).is_ok()
}
#[no_mangle]

View File

@@ -1,14 +1,18 @@
//! The WASI embedding API definitions for Wasmtime.
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::ffi::CStr;
use std::fs::File;
use std::os::raw::{c_char, c_int};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::slice;
use wasi_common::{preopen_dir, WasiCtxBuilder};
use wasmtime::{HostRef, Trap};
use wasmtime_wasi::Wasi;
use wasi_common::{
old::snapshot_0::WasiCtxBuilder as WasiSnapshot0CtxBuilder, preopen_dir,
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> {
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()
}
#[repr(C)]
pub struct wasi_config_t {
builder: WasiCtxBuilder,
pub enum WasiModule {
Snapshot0(WasiSnapshot0),
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]
pub unsafe extern "C" fn wasi_config_new() -> *mut wasi_config_t {
Box::into_raw(Box::new(wasi_config_t {
builder: WasiCtxBuilder::new(),
}))
Box::into_raw(Box::new(wasi_config_t::default()))
}
#[no_mangle]
@@ -47,16 +65,17 @@ pub unsafe extern "C" fn wasi_config_set_argv(
argc: c_int,
argv: *const *const c_char,
) {
(*config).builder.args(
slice::from_raw_parts(argv, argc as usize)
.iter()
.map(|a| slice::from_raw_parts(*a as *const u8, CStr::from_ptr(*a).to_bytes().len())),
);
(*config).args = slice::from_raw_parts(argv, argc as usize)
.iter()
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
.collect();
(*config).inherit_args = false;
}
#[no_mangle]
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]
@@ -69,17 +88,22 @@ pub unsafe extern "C" fn wasi_config_set_env(
let names = slice::from_raw_parts(names, envc as usize);
let values = slice::from_raw_parts(values, envc as usize);
(*config).builder.envs(
names
.iter()
.map(|p| CStr::from_ptr(*p).to_bytes())
.zip(values.iter().map(|p| CStr::from_ptr(*p).to_bytes())),
);
(*config).env = names
.iter()
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
.zip(
values
.iter()
.map(|p| CStr::from_ptr(*p).to_bytes().to_owned()),
)
.collect();
(*config).inherit_env = false;
}
#[no_mangle]
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]
@@ -92,14 +116,16 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file(
None => return false,
};
(*config).builder.stdin(file);
(*config).stdin = Some(file);
(*config).inherit_stdin = false;
true
}
#[no_mangle]
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]
@@ -112,14 +138,16 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file(
None => return false,
};
(*config).builder.stdout(file);
(*config).stdout = Some(file);
(*config).inherit_stdout = false;
true
}
#[no_mangle]
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]
@@ -132,14 +160,16 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file(
None => return false,
};
(*config).builder.stderr(file);
(*config).stderr = Some(file);
(*config).inherit_stderr = false;
true
}
#[no_mangle]
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]
@@ -161,29 +191,110 @@ pub unsafe extern "C" fn wasi_config_preopen_dir(
None => return false,
};
(*config).builder.preopened_dir(dir, guest_path);
(*config).preopens.push((dir, guest_path.to_owned()));
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)]
pub struct wasi_instance_t {
pub wasi: Wasi,
wasi: WasiInstance,
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]
pub unsafe extern "C" fn wasi_instance_new(
store: *mut wasm_store_t,
name: *const c_char,
config: *mut wasi_config_t,
trap: *mut *mut wasm_trap_t,
) -> *mut wasi_instance_t {
let store = &(*store).store.borrow();
let mut config = Box::from_raw(config);
let config = Box::from_raw(config);
match config.builder.build() {
Ok(ctx) => Box::into_raw(Box::new(wasi_instance_t {
wasi: Wasi::new(store, ctx),
let result = match CStr::from_ptr(name).to_str().unwrap_or("") {
"wasi_snapshot_preview1" => create_preview1_instance(store, *config),
"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(),
})),
Err(e) => {
@@ -206,20 +317,32 @@ pub unsafe extern "C" fn wasi_instance_bind_import(
instance: *mut wasi_instance_t,
import: *const wasm_importtype_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)
let func_type = match (*import).ty.ty() {
ExternType::Func(f) => f,
_ => return std::ptr::null_mut(),
};
let module = (*import).ty.module();
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) => {
if export.ty() != func_type {
return std::ptr::null_mut();

View File

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

View File

@@ -9,18 +9,26 @@ namespace Wasmtime
/// <summary>
/// Creates a default <see cref="Wasi"/> instance.
/// </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(
(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;
Handle = Interop.wasi_instance_new(store, config, out trap);
Handle = Interop.wasi_instance_new(store, name, config, out trap);
config.SetHandleAsInvalid();
if (trap != IntPtr.Zero)

View File

@@ -12,10 +12,22 @@ namespace Wasmtime
/// <summary>
/// Constructs a new <see cref="WasiBuilder" />.
/// </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>
/// Adds a command line argument to the builder.
/// </summary>
@@ -271,7 +283,7 @@ namespace Wasmtime
SetStandardError(config);
SetPreopenDirectories(config);
return new Wasi(store.Handle, config);
return new Wasi(store.Handle, config, Name);
}
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]
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;
var memory = instance.Externs.Memories[0];
@@ -43,7 +43,7 @@ namespace Wasmtime.Tests
{"VERY", "COOL"},
};
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value)))
.Build(Fixture.Module.Store);
@@ -67,7 +67,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItInheritsEnvironment()
{
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithInheritedEnvironment()
.Build(Fixture.Module.Store);
@@ -83,7 +83,7 @@ namespace Wasmtime.Tests
[Fact]
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;
var memory = instance.Externs.Memories[0];
@@ -103,7 +103,7 @@ namespace Wasmtime.Tests
"COOL"
};
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithArgs(args)
.Build(Fixture.Module.Store);
@@ -127,7 +127,7 @@ namespace Wasmtime.Tests
[Fact]
public void ItInheritsArguments()
{
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithInheritedArgs()
.Build(Fixture.Module.Store);
@@ -148,7 +148,7 @@ namespace Wasmtime.Tests
using var file = new TempFile();
File.WriteAllText(file.Path, MESSAGE);
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithStandardInput(file.Path)
.Build(Fixture.Module.Store);
@@ -173,7 +173,7 @@ namespace Wasmtime.Tests
using var file = new TempFile();
var builder = new WasiBuilder();
var builder = new WasiBuilder("wasi_snapshot_preview1");
if (fd == 1)
{
builder.WithStandardOutput(file.Path);
@@ -206,7 +206,7 @@ namespace Wasmtime.Tests
using var file = new TempFile();
var wasi = new WasiBuilder()
var wasi = new WasiBuilder("wasi_snapshot_preview1")
.WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo")
.Build(Fixture.Module.Store);

View File

@@ -101,46 +101,46 @@ impl PendingCString {
/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
stdin: PendingEntry,
stdout: PendingEntry,
stderr: PendingEntry,
preopens: Vec<(PathBuf, File)>,
args: Vec<PendingCString>,
env: HashMap<PendingCString, PendingCString>,
stdin: Option<PendingEntry>,
stdout: Option<PendingEntry>,
stderr: Option<PendingEntry>,
preopens: Option<Vec<(PathBuf, File)>>,
args: Option<Vec<PendingCString>>,
env: Option<HashMap<PendingCString, PendingCString>>,
}
impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`.
pub fn new() -> Self {
let stdin = PendingEntry::Thunk(Entry::null);
let stdout = PendingEntry::Thunk(Entry::null);
let stderr = PendingEntry::Thunk(Entry::null);
Self {
stdin,
stdout,
stderr,
preopens: Vec::new(),
args: vec![],
env: HashMap::new(),
stdin: Some(PendingEntry::Thunk(Entry::null)),
stdout: Some(PendingEntry::Thunk(Entry::null)),
stderr: Some(PendingEntry::Thunk(Entry::null)),
preopens: Some(Vec::new()),
args: Some(Vec::new()),
env: Some(HashMap::new()),
}
}
/// Add arguments to the command-line arguments list.
///
/// 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 {
self.args = args
.into_iter()
.map(|arg| arg.as_ref().to_vec().into())
.collect();
pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args
.as_mut()
.unwrap()
.extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
self
}
/// 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.
pub fn arg<S: AsRef<[u8]>>(mut self, arg: S) -> Self {
self.args.push(arg.as_ref().to_vec().into());
pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args
.as_mut()
.unwrap()
.push(arg.as_ref().to_vec().into());
self
}
@@ -148,16 +148,36 @@ impl WasiCtxBuilder {
///
/// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will
/// fail.
pub fn inherit_args(mut self) -> Self {
self.args = env::args_os().map(PendingCString::OsString).collect();
pub fn inherit_args(&mut self) -> &mut Self {
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
}
/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(mut self) -> Self {
self.stdin = PendingEntry::Thunk(Entry::duplicate_stdin);
self.stdout = PendingEntry::Thunk(Entry::duplicate_stdout);
self.stderr = PendingEntry::Thunk(Entry::duplicate_stderr);
pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
self
}
@@ -165,10 +185,10 @@ impl WasiCtxBuilder {
///
/// If any environment variables from the host process contain invalid Unicode (UTF-16 for
/// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail.
pub fn inherit_env(mut self) -> Self {
self.env = std::env::vars_os()
.map(|(k, v)| (k.into(), v.into()))
.collect();
pub fn inherit_env(&mut self) -> &mut Self {
let env = self.env.as_mut().unwrap();
env.clear();
env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into())));
self
}
@@ -176,8 +196,10 @@ impl WasiCtxBuilder {
///
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `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
.as_mut()
.unwrap()
.insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into());
self
}
@@ -187,40 +209,40 @@ impl WasiCtxBuilder {
/// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else
/// `WasiCtxBuilder::build()` will fail.
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
mut self,
&mut self,
envs: impl IntoIterator<Item = T>,
) -> Self {
self.env = envs
.into_iter()
.map(|t| {
let (k, v) = t.borrow();
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
})
.collect();
) -> &mut Self {
self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| {
let (k, v) = t.borrow();
(k.as_ref().to_vec().into(), v.as_ref().to_vec().into())
}));
self
}
/// Provide a File to use as stdin
pub fn stdin(mut self, file: File) -> Self {
self.stdin = PendingEntry::File(file);
pub fn stdin(&mut self, file: File) -> &mut Self {
self.stdin = Some(PendingEntry::File(file));
self
}
/// Provide a File to use as stdout
pub fn stdout(mut self, file: File) -> Self {
self.stdout = PendingEntry::File(file);
pub fn stdout(&mut self, file: File) -> &mut Self {
self.stdout = Some(PendingEntry::File(file));
self
}
/// Provide a File to use as stderr
pub fn stderr(mut self, file: File) -> Self {
self.stderr = PendingEntry::File(file);
pub fn stderr(&mut self, file: File) -> &mut Self {
self.stderr = Some(PendingEntry::File(file));
self
}
/// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(mut self, dir: File, guest_path: P) -> Self {
self.preopens.push((guest_path.as_ref().to_owned(), dir));
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), dir));
self
}
@@ -228,17 +250,21 @@ impl WasiCtxBuilder {
///
/// 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.
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
// contain any NUL bytes, or if conversion from `OsString` fails.
let args = self
.args
.take()
.unwrap()
.into_iter()
.map(|arg| arg.into_utf8_cstring())
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
let env = self
.env
.take()
.unwrap()
.into_iter()
.map(|(k, v)| {
k.into_string().and_then(|mut pair| {
@@ -257,12 +283,12 @@ impl WasiCtxBuilder {
let mut fd_pool = FdPool::new();
let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new();
// 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
.allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending {
match pending.take().unwrap() {
PendingEntry::Thunk(f) => {
entries.insert(fd, f()?);
}
@@ -272,7 +298,7 @@ impl WasiCtxBuilder {
}
}
// 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
// unnecessarily if we have exactly the maximum number of file descriptors.
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 ctor_externs = Vec::new();
let mut ctor_fields = Vec::new();
let mut linker_add = Vec::new();
for module in doc.modules() {
let module_name = module.name.as_str();
for func in module.funcs() {
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 });
get_exports.push(quote! { #name => Some(&self.#name_ident) });
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 params = Vec::new();
@@ -249,6 +254,12 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
_ => 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() {
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() {
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 });
get_exports.push(quote! { #name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone());

View File

@@ -336,13 +336,12 @@ impl ModuleRegistry {
let cx1 = cx1.build()?;
let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new()
.inherit_stdio()
.args(argv)
.envs(vars);
let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new();
cx2.inherit_stdio().args(argv).envs(vars);
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()?;