Compare commits

..

7 Commits

Author SHA1 Message Date
zach
452b6e3631 chore: update to latest wasmtime 2024-04-15 16:02:50 -07:00
zach
a12ec6bc62 fix: require wasmtime 17 or greater 2024-04-15 16:02:50 -07:00
zach
1216753066 feat: use wasi preview2, support wasi tcp/udp for allowed_hosts 2024-04-15 16:02:50 -07:00
Roland Fredenhagen
c5a23a31d8 feat: add id() to CurrentPlugin (#705)
fixes  #704
2024-04-15 09:03:48 -07:00
Steve Manuel
7206e2b362 chore: add perl-sdk to README list 2024-04-10 22:43:27 -06:00
Steve Manuel
20f551f019 chore: fix broken links in crate readme (#701) 2024-04-04 10:23:46 -06:00
zach
9aa817def7 cleanup: add paths to errors, clippy (#700) 2024-03-27 14:54:03 -07:00
12 changed files with 107 additions and 58 deletions

View File

@@ -51,6 +51,7 @@ started:
| Java SDK | <img alt="Java SDK" src="https://extism.org/img/sdk-languages/java-android.svg" width="50px"/> | https://github.com/extism/java-sdk | [Sonatype](https://central.sonatype.com/artifact/org.extism.sdk/extism) |
| .NET SDK | <img alt=".NET SDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-sdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Sdk) |
| OCaml SDK | <img alt="OCaml SDK" src="https://extism.org/img/sdk-languages/ocaml.svg" width="50px"/> | https://github.com/extism/ocaml-sdk | [opam](https://opam.ocaml.org/packages/extism/) |
| Perl SDK | <img alt="Perl SDK" src="https://extism.org/img/sdk-languages/perl.svg" width="50px"/> | https://github.com/extism/perl-sdk | N/A |
| PHP SDK | <img alt="PHP SDK" src="https://extism.org/img/sdk-languages/php.svg" width="50px"/> | https://github.com/extism/php-sdk | [Packagist](https://packagist.org/packages/extism/extism) |
| Python SDK | <img alt="Python SDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-sdk | [PyPi](https://pypi.org/project/extism/) |
| Ruby SDK | <img alt="Ruby SDK" src="https://extism.org/img/sdk-languages/ruby.svg" width="50px"/> | https://github.com/extism/ruby-sdk | [RubyGems](https://rubygems.org/gems/extism) |

View File

@@ -9,8 +9,8 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 14.0.0, < 18.0.0"
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
wasmtime = ">= 18.0.0, < 19.0.0"
wasmtime-wasi = ">= 18.0.0, < 19.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"

View File

@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
```toml
[dependencies]
extism = "1.0.0"
extism = "1.2.0"
```
## Environment variables
@@ -201,7 +201,7 @@ fn main() {
}
```
> *Note*: In order to write host functions you should get familiar with the methods on the [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.UserData.html) types.
> *Note*: In order to write host functions you should get familiar with the methods on the [CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [UserData](https://docs.rs/extism/latest/extism/enum.UserData.html) types.
Now we can invoke the event:

View File

@@ -1,7 +1,7 @@
use extism::*;
// pretend this is redis or something :)
type KVStore = std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<String, Vec<u8>>>>;
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;
// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
// variable name and type for the `UserData` parameter

View File

@@ -1,3 +1,6 @@
use wasmtime::component::ResourceTable;
use wasmtime_wasi::preview2::{preview1::WasiPreview1Adapter, DirPerms, FilePerms};
use crate::*;
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
@@ -18,6 +21,7 @@ pub struct CurrentPlugin {
}
unsafe impl Send for CurrentPlugin {}
unsafe impl Sync for CurrentPlugin {}
pub(crate) struct MemoryLimiter {
bytes_left: usize,
@@ -62,6 +66,11 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
}
impl CurrentPlugin {
/// Gets `Plugin`'s ID
pub fn id(&self) -> uuid::Uuid {
self.id
}
/// Get a `MemoryHandle` from a memory offset
pub fn memory_handle(&mut self, offs: u64) -> Option<MemoryHandle> {
if offs == 0 {
@@ -289,24 +298,57 @@ impl CurrentPlugin {
) -> Result<Self, Error> {
let wasi = if wasi {
let auth = wasmtime_wasi::ambient_authority();
let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
for (k, v) in manifest.config.iter() {
ctx.env(k, v)?;
}
let mut ctx = wasmtime_wasi::preview2::WasiCtxBuilder::new();
ctx.allow_ip_name_lookup(true);
ctx.allow_tcp(true);
ctx.allow_udp(true);
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
ctx.preopened_dir(d, v)?;
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth).map_err(|err| {
Error::msg(format!(
"Unable to preopen directory \"{}\": {}",
k.display(),
err.kind()
))
})?;
ctx.preopened_dir(
d,
DirPerms::READ | DirPerms::MUTATE,
FilePerms::READ | FilePerms::WRITE,
v.to_string_lossy(),
);
}
}
if let Some(h) = &manifest.allowed_hosts {
let h = h.clone();
ctx.socket_addr_check(move |addr, _kind| {
for host in h.iter() {
let addrs = std::net::ToSocketAddrs::to_socket_addrs(&host);
if let Ok(addrs) = addrs {
for a in addrs.into_iter() {
if addr == &a {
return true;
}
}
}
}
false
});
}
// Enable WASI output, typically used for debugging purposes
if std::env::var("EXTISM_ENABLE_WASI_OUTPUT").is_ok() {
ctx.inherit_stdout().inherit_stderr();
}
Some(Wasi { ctx: ctx.build() })
Some(Wasi {
ctx: ctx.build(),
preview2_table: ResourceTable::new(),
preview1_adapter: WasiPreview1Adapter::new(),
})
} else {
None
};

View File

@@ -71,7 +71,9 @@ pub struct CPtr {
/// UserDataHandle is an untyped version of `UserData` that is stored inside `Function` to keep a live reference.
#[derive(Clone)]
pub(crate) enum UserDataHandle {
#[allow(dead_code)]
C(Arc<CPtr>),
#[allow(dead_code)]
Rust(Arc<std::sync::Mutex<dyn std::any::Any>>),
}
@@ -81,18 +83,18 @@ pub(crate) enum UserDataHandle {
/// using `UserData::get`. The `C` data is stored as a pointer and cleanup function and isn't usable from Rust. The cleanup function
/// will be called when the inner `CPtr` is dropped.
#[derive(Debug)]
pub enum UserData<T: Sync + Clone + Sized> {
pub enum UserData<T: Sized> {
C(Arc<CPtr>),
Rust(T),
Rust(Arc<std::sync::Mutex<T>>),
}
impl<T: Default + Sync + Clone> Default for UserData<T> {
impl<T: Default> Default for UserData<T> {
fn default() -> Self {
UserData::new(T::default())
}
}
impl<T: Sync + Clone> Clone for UserData<T> {
impl<T> Clone for UserData<T> {
fn clone(&self) -> Self {
match self {
UserData::C(ptr) => UserData::C(ptr.clone()),
@@ -101,7 +103,7 @@ impl<T: Sync + Clone> Clone for UserData<T> {
}
}
impl<T: Sync + Clone> UserData<T> {
impl<T> UserData<T> {
/// Create a new `UserData` from an existing pointer and free function, this is used
/// by the C API to wrap C pointers into user data
pub(crate) fn new_pointer(
@@ -126,11 +128,12 @@ impl<T: Sync + Clone> UserData<T> {
///
/// This will wrap the provided value in a reference-counted mutex
pub fn new(x: T) -> Self {
UserData::Rust(x)
let data = Arc::new(std::sync::Mutex::new(x));
UserData::Rust(data)
}
/// Get a copy of the inner value
pub fn get(&self) -> Result<T, Error> {
pub fn get(&self) -> Result<Arc<std::sync::Mutex<T>>, Error> {
match self {
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
UserData::Rust(data) => Ok(data.clone()),
@@ -149,8 +152,8 @@ impl Drop for CPtr {
}
}
unsafe impl<T: Sync + Clone> Send for UserData<T> {}
unsafe impl<T: Sync + Clone> Sync for UserData<T> {}
unsafe impl<T> Send for UserData<T> {}
unsafe impl<T> Sync for UserData<T> {}
unsafe impl Send for CPtr {}
unsafe impl Sync for CPtr {}
@@ -179,7 +182,7 @@ pub struct Function {
impl Function {
/// Create a new host function
pub fn new<T: 'static + Sync + Clone, F>(
pub fn new<T: 'static, F>(
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
returns: impl IntoIterator<Item = ValType>,
@@ -210,9 +213,7 @@ impl Function {
namespace: None,
_user_data: match &user_data {
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
UserData::Rust(x) => {
UserDataHandle::Rust(std::sync::Arc::new(std::sync::Mutex::new(x.clone())))
}
UserData::Rust(x) => UserDataHandle::Rust(x.clone()),
},
}
}

View File

@@ -3,7 +3,29 @@ use crate::*;
/// WASI context
pub struct Wasi {
/// wasi
pub ctx: wasmtime_wasi::WasiCtx,
pub ctx: wasmtime_wasi::preview2::WasiCtx,
pub preview2_table: wasmtime::component::ResourceTable,
pub preview1_adapter: wasmtime_wasi::preview2::preview1::WasiPreview1Adapter,
}
impl wasmtime_wasi::preview2::WasiView for CurrentPlugin {
fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
&mut self.wasi.as_mut().unwrap().preview2_table
}
fn ctx(&mut self) -> &mut wasmtime_wasi::preview2::WasiCtx {
&mut self.wasi.as_mut().unwrap().ctx
}
}
impl wasmtime_wasi::preview2::preview1::WasiPreview1View for CurrentPlugin {
fn adapter(&self) -> &wasmtime_wasi::preview2::preview1::WasiPreview1Adapter {
&self.wasi.as_ref().unwrap().preview1_adapter
}
fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview2::preview1::WasiPreview1Adapter {
&mut self.wasi.as_mut().unwrap().preview1_adapter
}
}
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values

View File

@@ -47,9 +47,13 @@ fn to_module(engine: &Engine, wasm: &extism_manifest::Wasm) -> Result<(String, M
let name = meta.name.as_deref().unwrap_or(MAIN_KEY).to_string();
// Load file
let mut buf = Vec::new();
let mut file = std::fs::File::open(path)?;
file.read_to_end(&mut buf)?;
let buf = std::fs::read(path).map_err(|err| {
Error::msg(format!(
"Unable to load Wasm file \"{}\": {}",
path.display(),
err.kind()
))
})?;
check_hash(&meta.hash, &buf)?;
Ok((name, Module::new(engine, buf)?))

View File

@@ -307,9 +307,7 @@ impl Plugin {
// If wasi is enabled then add it to the linker
if with_wasi {
wasmtime_wasi::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
wasmtime_wasi::preview2::preview1::add_to_linker_sync(&mut linker)?;
}
for f in &mut imports {
@@ -805,12 +803,8 @@ impl Plugin {
}
let wasi_exit_code = e
.downcast_ref::<wasmtime_wasi::I32Exit>()
.map(|e| e.0)
.or_else(|| {
e.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0)
});
.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0);
if let Some(exit_code) = wasi_exit_code {
debug!(
plugin = self.id.to_string(),

View File

@@ -60,7 +60,7 @@ impl<'a> PluginBuilder<'a> {
}
/// Add a single host function
pub fn with_function<T: Sync + Clone + 'static, F>(
pub fn with_function<T: 'static, F>(
mut self,
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
@@ -80,7 +80,7 @@ impl<'a> PluginBuilder<'a> {
}
/// Add a single host function in a specific namespace
pub fn with_function_in_namespace<T: Sync + Clone + 'static, F>(
pub fn with_function_in_namespace<T: 'static, F>(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,

View File

@@ -389,20 +389,6 @@ pub unsafe extern "C" fn extism_plugin_config(
}
};
let wasi = &mut plugin.current_plugin_mut().wasi;
if let Some(Wasi { ctx, .. }) = wasi {
for (k, v) in json.iter() {
match v {
Some(v) => {
let _ = ctx.push_env(k, v);
}
None => {
let _ = ctx.push_env(k, "");
}
}
}
}
let id = plugin.id;
let config = &mut plugin.current_plugin_mut().manifest.config;
for (k, v) in json.into_iter() {
@@ -671,7 +657,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
/// Calls the provided callback function for each buffered log line.
/// This is only needed when `extism_log_custom` is used.
pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
if let Some(buf) = &mut LOG_BUFFER {
if let Some(buf) = LOG_BUFFER.as_mut() {
if let Ok(mut buf) = buf.buffer.lock() {
for (line, len) in buf.drain(..) {
handler(line.as_ptr(), len as u64);

View File

@@ -453,7 +453,7 @@ fn hello_world_user_data(
_plugin: &mut CurrentPlugin,
inputs: &[Val],
outputs: &mut [Val],
user_data: UserData<std::sync::Arc<std::sync::Mutex<std::fs::File>>>,
user_data: UserData<std::fs::File>,
) -> Result<(), Error> {
let data = user_data.get()?;
let mut data = data.lock().unwrap();
@@ -470,8 +470,7 @@ fn test_userdata() {
if path.exists() {
std::fs::remove_file(&path).unwrap();
}
let file =
std::sync::Arc::new(std::sync::Mutex::new(std::fs::File::create(&path).unwrap()));
let file = std::fs::File::create(&path).unwrap();
let f = Function::new(
"hello_world",
[PTR],