mirror of
https://github.com/extism/extism.git
synced 2026-01-12 07:18:02 -05:00
Compare commits
15 Commits
userdata
...
wasi-socke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2affe334c9 | ||
|
|
3f54892a39 | ||
|
|
ecf18a2d81 | ||
|
|
9bc1fc73f2 | ||
|
|
2bf391f236 | ||
|
|
5da0eb38ec | ||
|
|
7cb6c53910 | ||
|
|
0882f35300 | ||
|
|
5d9c8c5d05 | ||
|
|
75e92c40a0 | ||
|
|
5373f7d88d | ||
|
|
c5a23a31d8 | ||
|
|
7206e2b362 | ||
|
|
20f551f019 | ||
|
|
9aa817def7 |
3
.github/workflows/kernel.yml
vendored
3
.github/workflows/kernel.yml
vendored
@@ -21,6 +21,9 @@ jobs:
|
||||
target: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: install wasm-tools
|
||||
uses: bytecodealliance/actions/wasm-tools/setup@v1
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install wabt --yes
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -17,6 +17,9 @@ done
|
||||
|
||||
cargo build --package extism-runtime-kernel --bin extism-runtime --release --target wasm32-unknown-unknown $CARGO_FLAGS
|
||||
cp target/wasm32-unknown-unknown/release/extism-runtime.wasm .
|
||||
wasm-strip extism-runtime.wasm
|
||||
mv extism-runtime.wasm ../runtime/src/extism-runtime.wasm
|
||||
|
||||
wasm-tools parse extism-context.wat -o extism-context.wasm
|
||||
wasm-merge --enable-reference-types ./extism-runtime.wasm runtime extism-context.wasm context -o ../runtime/src/extism-runtime.wasm
|
||||
rm extism-context.wasm
|
||||
rm extism-runtime.wasm
|
||||
wasm-strip ../runtime/src/extism-runtime.wasm
|
||||
|
||||
3
kernel/extism-context.wat
Normal file
3
kernel/extism-context.wat
Normal file
@@ -0,0 +1,3 @@
|
||||
(module
|
||||
(global (export "extism_context") (mut externref) (ref.null extern))
|
||||
)
|
||||
@@ -14,7 +14,9 @@ Create a new plugin.
|
||||
- `functions`: is an array of `ExtismFunction*`
|
||||
- `n_functions`: is the number of functions
|
||||
- `with_wasi`: enables/disables WASI
|
||||
- `errmsg`: error message during plugin creation
|
||||
- `errmsg`: error message during plugin creation, this should be freed with
|
||||
`extism_plugin_new_error_free`
|
||||
|
||||
|
||||
```c
|
||||
ExtismPlugin extism_plugin_new(const uint8_t *wasm,
|
||||
@@ -24,6 +26,17 @@ ExtismPlugin extism_plugin_new(const uint8_t *wasm,
|
||||
bool with_wasi,
|
||||
char **errmsg);
|
||||
```
|
||||
---
|
||||
|
||||
### `extism_plugin_new_error_free`
|
||||
|
||||
Frees the error message returned when creating a plugin
|
||||
|
||||
```c
|
||||
void extism_plugin_new_error_free(char *err);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_free`
|
||||
|
||||
@@ -33,6 +46,8 @@ Remove a plugin from the registry and free associated memory.
|
||||
void extism_plugin_free(ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_config`
|
||||
|
||||
Update plugin config values, this will merge with the existing values.
|
||||
@@ -43,6 +58,8 @@ bool extism_plugin_config(ExtismPlugin *plugin,
|
||||
ExtismSize json_size);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_function_exists`
|
||||
|
||||
Returns true if `func_name` exists.
|
||||
@@ -52,6 +69,8 @@ bool extism_plugin_function_exists(ExtismPlugin *plugin,
|
||||
const char *func_name);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_call`
|
||||
|
||||
Call a function.
|
||||
@@ -59,6 +78,8 @@ Call a function.
|
||||
- `data`: is the input data
|
||||
- `data_len`: is the length of `data`
|
||||
|
||||
Returns `0` when the call is successful.
|
||||
|
||||
```c
|
||||
int32_t extism_plugin_call(ExtismPlugin *plugin,
|
||||
const char *func_name,
|
||||
@@ -66,6 +87,28 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
|
||||
ExtismSize data_len);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_call_with_host_context`
|
||||
|
||||
Call a function with additional host context that can be accessed from inside host functions.
|
||||
- `func_name`: is the function to call
|
||||
- `data`: is the input data
|
||||
- `data_len`: is the length of `data`
|
||||
- `host_ctx`: an opaque pointer that can be accessed in host functions
|
||||
|
||||
Returns `0` when the call is successful.
|
||||
|
||||
```c
|
||||
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len,
|
||||
void *host_ctx);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_error`
|
||||
|
||||
Get the error associated with a `Plugin`
|
||||
@@ -74,6 +117,8 @@ Get the error associated with a `Plugin`
|
||||
const char *extism_plugin_error(ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_output_length`
|
||||
|
||||
Get the length of a plugin's output data.
|
||||
@@ -82,6 +127,8 @@ Get the length of a plugin's output data.
|
||||
ExtismSize extism_plugin_output_length(ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_output_data`
|
||||
|
||||
Get the plugin's output data.
|
||||
@@ -90,6 +137,8 @@ Get the plugin's output data.
|
||||
const uint8_t *extism_plugin_output_data(ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_reset`
|
||||
|
||||
Reset the Extism runtime, this will invalidate all allocated memory.
|
||||
@@ -98,6 +147,8 @@ Reset the Extism runtime, this will invalidate all allocated memory.
|
||||
bool extism_plugin_reset(ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_log_file`
|
||||
|
||||
Set log file and level.
|
||||
@@ -106,6 +157,8 @@ Set log file and level.
|
||||
bool extism_log_file(const char *filename, const char *log_level);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_log_custom`
|
||||
|
||||
Enable a custom log handler, this will buffer logs until `extism_log_drain`
|
||||
@@ -115,6 +168,8 @@ is called Log level should be one of: info, error, trace, debug, warn
|
||||
bool extism_log_custom(const char *log_level);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_log_drain`
|
||||
|
||||
Calls the provided callback function for each buffered log line.
|
||||
@@ -124,6 +179,8 @@ This is only needed when `extism_log_custom` is used.
|
||||
void extism_log_drain(void (*handler)(const char *, uintptr_t));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_version`
|
||||
|
||||
Get the Extism version string.
|
||||
@@ -132,6 +189,8 @@ Get the Extism version string.
|
||||
const char *extism_version(void);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_current_plugin_memory`
|
||||
|
||||
Returns a pointer to the memory of the currently running plugin
|
||||
@@ -140,6 +199,19 @@ Returns a pointer to the memory of the currently running plugin
|
||||
uint8_t *extism_current_plugin_memory(ExtismCurrentPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_current_plugin_host_context`
|
||||
|
||||
Get access to the host context, passed in using `extism_plugin_call_with_host_context`
|
||||
|
||||
```c
|
||||
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### `extism_current_plugin_memory_alloc`
|
||||
|
||||
Allocate a memory block in the currently running plugin
|
||||
@@ -148,6 +220,8 @@ Allocate a memory block in the currently running plugin
|
||||
uint64_t extism_current_plugin_memory_alloc(ExtismCurrentPlugin *plugin, ExtismSize n);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_current_plugin_memory_length`
|
||||
|
||||
Get the length of an allocated block
|
||||
@@ -156,6 +230,8 @@ Get the length of an allocated block
|
||||
ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, ExtismSize n);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_current_plugin_memory_free`
|
||||
|
||||
Free an allocated memory block
|
||||
@@ -164,6 +240,8 @@ Free an allocated memory block
|
||||
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, uint64_t ptr);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_function_new`
|
||||
Create a new host function
|
||||
- `name`: function name, this should be valid UTF-8
|
||||
@@ -190,6 +268,8 @@ ExtismFunction *extism_function_new(const char *name,
|
||||
void (*free_user_data)(void *_));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_function_set_namespace`
|
||||
|
||||
Set the namespace of an `ExtismFunction`
|
||||
@@ -198,6 +278,8 @@ Set the namespace of an `ExtismFunction`
|
||||
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_function_free`
|
||||
|
||||
Free an `ExtismFunction`
|
||||
@@ -206,6 +288,8 @@ Free an `ExtismFunction`
|
||||
void extism_function_free(ExtismFunction *ptr);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_cancel_handle`
|
||||
|
||||
Get handle for plugin cancellation
|
||||
@@ -214,6 +298,8 @@ Get handle for plugin cancellation
|
||||
const ExtismCancelHandle *extism_plugin_cancel_handle(const ExtismPlugin *plugin);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `extism_plugin_cancel`
|
||||
|
||||
Cancel a running plugin from another thread
|
||||
@@ -222,12 +308,14 @@ Cancel a running plugin from another thread
|
||||
bool extism_plugin_cancel(const ExtismCancelHandle *handle);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type definitions:
|
||||
|
||||
### `ExtismPlugin`
|
||||
|
||||
```c
|
||||
typedef int32_t ExtismPlugin;
|
||||
typedef struct ExtismPlugin ExtismPlugin;
|
||||
```
|
||||
|
||||
### `ExtismSize`
|
||||
@@ -259,3 +347,5 @@ typedef struct ExtismCurrentPlugin ExtismCurrentPlugin;
|
||||
```c
|
||||
typedef struct ExtismCancelHandle ExtismCancelHandle;
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ pub type ManifestMemory = MemoryOptions;
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryOptions {
|
||||
/// The max number of WebAssembly pages that should be allocated
|
||||
#[serde(alias = "max")]
|
||||
pub max_pages: Option<u32>,
|
||||
|
||||
/// The maximum number of bytes allowed in an HTTP response
|
||||
@@ -62,7 +61,6 @@ pub struct HttpRequest {
|
||||
|
||||
/// Request headers
|
||||
#[serde(default)]
|
||||
#[serde(alias = "header")]
|
||||
pub headers: std::collections::BTreeMap<String, String>,
|
||||
|
||||
/// Request method
|
||||
|
||||
@@ -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 = ">= 20.0.0, < 22.0.0"
|
||||
wasmtime-wasi = ">= 20.0.0, < 22.0.0"
|
||||
anyhow = "1"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ fn main() {
|
||||
#define EXTISM_SUCCESS 0
|
||||
|
||||
/** An alias for I64 to signify an Extism pointer */
|
||||
#define PTR I64
|
||||
#define EXTISM_PTR ExtismValType_I64
|
||||
";
|
||||
if let Ok(x) = cbindgen::Builder::new()
|
||||
.with_crate(".")
|
||||
|
||||
@@ -2,6 +2,8 @@ use extism::*;
|
||||
|
||||
fn main() {
|
||||
let manifest = Manifest::new([
|
||||
// upper.wat provides an export called `host_reflect` that takes a string
|
||||
// and returns the same string uppercased
|
||||
Wasm::File {
|
||||
// See https://github.com/extism/plugins/blob/main/upper.wat
|
||||
path: "../wasm/upper.wasm".into(),
|
||||
@@ -10,6 +12,9 @@ fn main() {
|
||||
hash: None,
|
||||
},
|
||||
},
|
||||
// reflect expects host_reflect to be imported: https://github.com/extism/plugins/blob/e5578bbbdd87f9936a0a8d36df629768b2eff6bb/reflect/src/lib.rs#L5
|
||||
// Extism will link the export from upper.wat to the import of reflect.rs at runtime so it
|
||||
// can call it
|
||||
Wasm::File {
|
||||
// See https://github.com/extism/plugins/tree/main/reflect
|
||||
path: "../wasm/reflect.wasm".into(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#define EXTISM_SUCCESS 0
|
||||
|
||||
/** An alias for I64 to signify an Extism pointer */
|
||||
#define PTR I64
|
||||
#define EXTISM_PTR ExtismValType_I64
|
||||
|
||||
|
||||
/**
|
||||
@@ -20,31 +20,31 @@ typedef enum {
|
||||
/**
|
||||
* Signed 32 bit integer.
|
||||
*/
|
||||
I32,
|
||||
ExtismValType_I32,
|
||||
/**
|
||||
* Signed 64 bit integer.
|
||||
*/
|
||||
I64,
|
||||
ExtismValType_I64,
|
||||
/**
|
||||
* Floating point 32 bit integer.
|
||||
*/
|
||||
F32,
|
||||
ExtismValType_F32,
|
||||
/**
|
||||
* Floating point 64 bit integer.
|
||||
*/
|
||||
F64,
|
||||
ExtismValType_F64,
|
||||
/**
|
||||
* A 128 bit number.
|
||||
*/
|
||||
V128,
|
||||
ExtismValType_V128,
|
||||
/**
|
||||
* A reference to a Wasm function.
|
||||
*/
|
||||
FuncRef,
|
||||
ExtismValType_FuncRef,
|
||||
/**
|
||||
* A reference to opaque data in the Wasm instance.
|
||||
*/
|
||||
ExternRef,
|
||||
ExtismValType_ExternRef,
|
||||
} ExtismValType;
|
||||
|
||||
/**
|
||||
@@ -113,6 +113,12 @@ extern "C" {
|
||||
*/
|
||||
const uint8_t *extism_plugin_id(ExtismPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Get the current plugin's associated host context data. Returns null if call was made without
|
||||
* host context.
|
||||
*/
|
||||
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the memory of the currently running plugin
|
||||
* NOTE: this should only be called from host functions.
|
||||
@@ -231,6 +237,20 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len);
|
||||
|
||||
/**
|
||||
* Call a function with host context.
|
||||
*
|
||||
* `func_name`: is the function to call
|
||||
* `data`: is the input data
|
||||
* `data_len`: is the length of `data`
|
||||
* `host_context`: a pointer to context data that will be available in host functions
|
||||
*/
|
||||
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len,
|
||||
void *host_context);
|
||||
|
||||
/**
|
||||
* Get the error associated with a `Plugin`
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct CurrentPlugin {
|
||||
pub(crate) available_pages: Option<u32>,
|
||||
pub(crate) memory_limiter: Option<MemoryLimiter>,
|
||||
pub(crate) id: uuid::Uuid,
|
||||
pub(crate) start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
unsafe impl Send for CurrentPlugin {}
|
||||
@@ -62,6 +63,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 {
|
||||
@@ -181,6 +187,23 @@ impl CurrentPlugin {
|
||||
anyhow::bail!("{} unable to locate extism memory", self.id)
|
||||
}
|
||||
|
||||
pub fn host_context<T: Clone + 'static>(&mut self) -> Result<T, Error> {
|
||||
let (linker, mut store) = self.linker_and_store();
|
||||
let Some(Extern::Global(xs)) = linker.get(&mut store, EXTISM_ENV_MODULE, "extism_context")
|
||||
else {
|
||||
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
|
||||
};
|
||||
|
||||
let Val::ExternRef(Some(xs)) = xs.get(&mut store) else {
|
||||
anyhow::bail!("expected extism_context to be an externref value",)
|
||||
};
|
||||
|
||||
match xs.data(&mut store)?.downcast_ref::<T>().cloned() {
|
||||
Some(xs) => Ok(xs.clone()),
|
||||
None => anyhow::bail!("could not downcast extism_context",),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory_alloc(&mut self, n: u64) -> Result<MemoryHandle, Error> {
|
||||
if n == 0 {
|
||||
return Ok(MemoryHandle {
|
||||
@@ -288,25 +311,51 @@ impl CurrentPlugin {
|
||||
id: uuid::Uuid,
|
||||
) -> 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)?;
|
||||
}
|
||||
|
||||
// Disable sockets/DNS lookup
|
||||
ctx.allow_ip_name_lookup(true)
|
||||
.allow_tcp(true)
|
||||
.allow_udp(true)
|
||||
.allow_blocking_current_thread(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)?;
|
||||
ctx.preopened_dir(
|
||||
k,
|
||||
v.to_string_lossy(),
|
||||
wasmtime_wasi::DirPerms::READ | wasmtime_wasi::DirPerms::MUTATE,
|
||||
wasmtime_wasi::FilePerms::READ | wasmtime_wasi::FilePerms::WRITE,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
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_p1(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -331,6 +380,7 @@ impl CurrentPlugin {
|
||||
available_pages,
|
||||
memory_limiter,
|
||||
id,
|
||||
start_time: std::time::Instant::now(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -441,6 +491,18 @@ impl CurrentPlugin {
|
||||
let length = self.memory_length(offs).unwrap_or_default();
|
||||
(offs, length)
|
||||
}
|
||||
|
||||
/// Returns the remaining time before a plugin will timeout, or
|
||||
/// `None` if no timeout is configured in the manifest
|
||||
pub fn time_remaining(&self) -> Option<std::time::Duration> {
|
||||
if let Some(x) = &self.manifest.timeout_ms {
|
||||
let elapsed = &self.start_time.elapsed().as_millis();
|
||||
let ms_left = x.saturating_sub(*elapsed as u64);
|
||||
return Some(std::time::Duration::from_millis(ms_left));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Internal for CurrentPlugin {
|
||||
@@ -452,14 +514,6 @@ impl Internal for CurrentPlugin {
|
||||
unsafe { &mut *self.store }
|
||||
}
|
||||
|
||||
fn linker(&self) -> &Linker<CurrentPlugin> {
|
||||
unsafe { &*self.linker }
|
||||
}
|
||||
|
||||
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
|
||||
unsafe { &mut *self.linker }
|
||||
}
|
||||
|
||||
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
|
||||
unsafe { (&mut *self.linker, &mut *self.store) }
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -4,6 +4,7 @@ use wasmtime::Caller;
|
||||
use crate::{error, trace, CurrentPlugin, Error};
|
||||
|
||||
/// An enumeration of all possible value types in WebAssembly.
|
||||
/// cbindgen:prefix-with-name
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub enum ValType {
|
||||
@@ -37,8 +38,13 @@ impl From<wasmtime::ValType> for ValType {
|
||||
F32 => ValType::F32,
|
||||
F64 => ValType::F64,
|
||||
V128 => ValType::V128,
|
||||
FuncRef => ValType::FuncRef,
|
||||
ExternRef => ValType::ExternRef,
|
||||
Ref(t) => {
|
||||
if t.heap_type().is_func() {
|
||||
ValType::FuncRef
|
||||
} else {
|
||||
ValType::ExternRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +58,8 @@ impl From<ValType> for wasmtime::ValType {
|
||||
F32 => wasmtime::ValType::F32,
|
||||
F64 => wasmtime::ValType::F64,
|
||||
V128 => wasmtime::ValType::V128,
|
||||
FuncRef => wasmtime::ValType::FuncRef,
|
||||
ExternRef => wasmtime::ValType::ExternRef,
|
||||
FuncRef => wasmtime::ValType::FUNCREF,
|
||||
ExternRef => wasmtime::ValType::EXTERNREF,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +77,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 +89,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 +109,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 +134,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 +158,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 {}
|
||||
|
||||
@@ -167,8 +176,8 @@ pub struct Function {
|
||||
/// Module name
|
||||
pub(crate) namespace: Option<String>,
|
||||
|
||||
/// Function type
|
||||
pub(crate) ty: wasmtime::FuncType,
|
||||
pub(crate) params: Vec<ValType>,
|
||||
pub(crate) results: Vec<ValType>,
|
||||
|
||||
/// Function handle
|
||||
pub(crate) f: Arc<FunctionInner>,
|
||||
@@ -179,10 +188,10 @@ 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>,
|
||||
params: impl IntoIterator<Item = ValType>,
|
||||
results: impl IntoIterator<Item = ValType>,
|
||||
user_data: UserData<T>,
|
||||
f: F,
|
||||
) -> Function
|
||||
@@ -194,13 +203,13 @@ impl Function {
|
||||
{
|
||||
let data = user_data.clone();
|
||||
let name = name.into();
|
||||
let args = args.into_iter().map(wasmtime::ValType::from);
|
||||
let returns = returns.into_iter().map(wasmtime::ValType::from);
|
||||
let ty = wasmtime::FuncType::new(args, returns);
|
||||
trace!("Creating function {name}: type={ty:?}");
|
||||
let params = params.into_iter().collect();
|
||||
let results = results.into_iter().collect();
|
||||
trace!("Creating function {name}: params={params:?}, results={results:?}");
|
||||
Function {
|
||||
name,
|
||||
ty,
|
||||
params,
|
||||
results,
|
||||
f: Arc::new(
|
||||
move |mut caller: Caller<_>, inp: &[Val], outp: &mut [Val]| {
|
||||
let x = data.clone();
|
||||
@@ -210,13 +219,27 @@ 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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ty(&self, engine: &wasmtime::Engine) -> wasmtime::FuncType {
|
||||
wasmtime::FuncType::new(
|
||||
engine,
|
||||
self.params
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(wasmtime::ValType::from)
|
||||
.collect::<Vec<_>>(),
|
||||
self.results
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(wasmtime::ValType::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Host function name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
@@ -240,9 +263,14 @@ impl Function {
|
||||
self
|
||||
}
|
||||
|
||||
/// Get function type
|
||||
pub fn ty(&self) -> &wasmtime::FuncType {
|
||||
&self.ty
|
||||
/// Get param types
|
||||
pub fn params(&self) -> &[ValType] {
|
||||
&self.params
|
||||
}
|
||||
|
||||
/// Get result types
|
||||
pub fn results(&self) -> &[ValType] {
|
||||
&self.results
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::*;
|
||||
/// WASI context
|
||||
pub struct Wasi {
|
||||
/// wasi
|
||||
pub ctx: wasmtime_wasi::WasiCtx,
|
||||
pub ctx: wasmtime_wasi::preview1::WasiP1Ctx,
|
||||
}
|
||||
|
||||
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values
|
||||
@@ -12,10 +12,6 @@ pub(crate) trait Internal {
|
||||
|
||||
fn store_mut(&mut self) -> &mut Store<CurrentPlugin>;
|
||||
|
||||
fn linker(&self) -> &Linker<CurrentPlugin>;
|
||||
|
||||
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin>;
|
||||
|
||||
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>);
|
||||
|
||||
fn current_plugin(&self) -> &CurrentPlugin {
|
||||
|
||||
@@ -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)?))
|
||||
|
||||
@@ -217,6 +217,11 @@ pub(crate) fn http_request(
|
||||
r = r.set(k, v);
|
||||
}
|
||||
|
||||
// Set HTTP timeout to respect the manifest timeout
|
||||
if let Some(remaining) = data.time_remaining() {
|
||||
r = r.timeout(remaining);
|
||||
}
|
||||
|
||||
let res = if body_offset > 0 {
|
||||
let handle = match data.memory_handle(body_offset) {
|
||||
Some(h) => h,
|
||||
@@ -236,6 +241,12 @@ pub(crate) fn http_request(
|
||||
Some(res.into_reader())
|
||||
}
|
||||
Err(e) => {
|
||||
// Catch timeout and return
|
||||
if let Some(d) = data.time_remaining() {
|
||||
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
|
||||
anyhow::bail!("timeout");
|
||||
}
|
||||
}
|
||||
let msg = e.to_string();
|
||||
if let Some(res) = e.into_response() {
|
||||
data.http_status = res.status();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
path::PathBuf,
|
||||
};
|
||||
@@ -100,14 +101,6 @@ impl Internal for Plugin {
|
||||
&mut self.store
|
||||
}
|
||||
|
||||
fn linker(&self) -> &Linker<CurrentPlugin> {
|
||||
&self.linker
|
||||
}
|
||||
|
||||
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
|
||||
&mut self.linker
|
||||
}
|
||||
|
||||
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
|
||||
(&mut self.linker, &mut self.store)
|
||||
}
|
||||
@@ -193,6 +186,12 @@ fn add_module<T: 'static>(
|
||||
}
|
||||
|
||||
for import in module.imports() {
|
||||
let module = import.module();
|
||||
|
||||
if module == EXTISM_ENV_MODULE && !matches!(import.ty(), ExternType::Func(_)) {
|
||||
anyhow::bail!("linked modules cannot access non-function exports of extism kernel");
|
||||
}
|
||||
|
||||
if !linked.contains(import.module()) {
|
||||
if let Some(m) = modules.get(import.module()) {
|
||||
add_module(
|
||||
@@ -213,6 +212,73 @@ fn add_module<T: 'static>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn relink(
|
||||
engine: &Engine,
|
||||
mut store: &mut Store<CurrentPlugin>,
|
||||
imports: &[Function],
|
||||
modules: &BTreeMap<String, Module>,
|
||||
with_wasi: bool,
|
||||
) -> Result<(InstancePre<CurrentPlugin>, Linker<CurrentPlugin>), Error> {
|
||||
let mut linker = Linker::new(engine);
|
||||
// Define PDK functions
|
||||
macro_rules! add_funcs {
|
||||
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
|
||||
$(
|
||||
let t = FuncType::new(&engine, [$($args),*], [$($($r),*)?]);
|
||||
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Add builtins
|
||||
use wasmtime::ValType::*;
|
||||
add_funcs!(
|
||||
config_get(I64) -> I64;
|
||||
var_get(I64) -> I64;
|
||||
var_set(I64, I64);
|
||||
http_request(I64, I64) -> I64;
|
||||
http_status_code() -> I32;
|
||||
log_warn(I64);
|
||||
log_info(I64);
|
||||
log_debug(I64);
|
||||
log_error(I64);
|
||||
);
|
||||
|
||||
let mut linked = BTreeSet::new();
|
||||
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
|
||||
linked.insert(EXTISM_ENV_MODULE.to_string());
|
||||
|
||||
// If wasi is enabled then add it to the linker
|
||||
if with_wasi {
|
||||
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |x: &mut CurrentPlugin| {
|
||||
&mut x.wasi.as_mut().unwrap().ctx
|
||||
})?;
|
||||
}
|
||||
|
||||
for f in imports {
|
||||
let name = f.name();
|
||||
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
|
||||
unsafe {
|
||||
linker.func_new(ns, name, f.ty(engine).clone(), &*(f.f.as_ref() as *const _))?;
|
||||
}
|
||||
}
|
||||
|
||||
for (name, module) in modules.iter() {
|
||||
add_module(
|
||||
store,
|
||||
&mut linker,
|
||||
&mut linked,
|
||||
modules,
|
||||
name.clone(),
|
||||
module,
|
||||
)?;
|
||||
}
|
||||
|
||||
let main = &modules[MAIN_KEY];
|
||||
let instance_pre = linker.instantiate_pre(main)?;
|
||||
Ok((instance_pre, linker))
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
/// Create a new plugin from a Manifest or WebAssembly module, and host functions. The `with_wasi`
|
||||
/// parameter determines whether or not the module should be executed with WASI enabled.
|
||||
@@ -239,7 +305,8 @@ impl Plugin {
|
||||
.coredump_on_trap(debug_options.coredump.is_some())
|
||||
.profiler(debug_options.profiling_strategy)
|
||||
.wasm_tail_call(true)
|
||||
.wasm_function_references(true);
|
||||
.wasm_function_references(true)
|
||||
.wasm_gc(true);
|
||||
|
||||
match cache_dir {
|
||||
Some(None) => (),
|
||||
@@ -275,64 +342,9 @@ impl Plugin {
|
||||
);
|
||||
store.set_epoch_deadline(1);
|
||||
|
||||
let mut linker = Linker::new(&engine);
|
||||
let mut imports: Vec<_> = imports.into_iter().collect();
|
||||
// Define PDK functions
|
||||
macro_rules! add_funcs {
|
||||
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
|
||||
$(
|
||||
let t = FuncType::new([$($args),*], [$($($r),*)?]);
|
||||
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
|
||||
)*
|
||||
};
|
||||
}
|
||||
let imports: Vec<Function> = imports.into_iter().collect();
|
||||
let (instance_pre, linker) = relink(&engine, &mut store, &imports, &modules, with_wasi)?;
|
||||
|
||||
// Add builtins
|
||||
use wasmtime::ValType::*;
|
||||
add_funcs!(
|
||||
config_get(I64) -> I64;
|
||||
var_get(I64) -> I64;
|
||||
var_set(I64, I64);
|
||||
http_request(I64, I64) -> I64;
|
||||
http_status_code() -> I32;
|
||||
log_warn(I64);
|
||||
log_info(I64);
|
||||
log_debug(I64);
|
||||
log_error(I64);
|
||||
);
|
||||
|
||||
let mut linked = BTreeSet::new();
|
||||
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
|
||||
linked.insert(EXTISM_ENV_MODULE.to_string());
|
||||
|
||||
// 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
|
||||
})?;
|
||||
}
|
||||
|
||||
for f in &mut imports {
|
||||
let name = f.name();
|
||||
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
|
||||
unsafe {
|
||||
linker.func_new(ns, name, f.ty().clone(), &*(f.f.as_ref() as *const _))?;
|
||||
}
|
||||
}
|
||||
|
||||
for (name, module) in modules.iter() {
|
||||
add_module(
|
||||
&mut store,
|
||||
&mut linker,
|
||||
&mut linked,
|
||||
&modules,
|
||||
name.clone(),
|
||||
module,
|
||||
)?;
|
||||
}
|
||||
|
||||
let main = &modules[MAIN_KEY];
|
||||
let instance_pre = linker.instantiate_pre(main)?;
|
||||
let timer_tx = Timer::tx();
|
||||
let mut plugin = Plugin {
|
||||
modules,
|
||||
@@ -370,6 +382,7 @@ impl Plugin {
|
||||
if self.store_needs_reset {
|
||||
let engine = self.store.engine().clone();
|
||||
let internal = self.current_plugin_mut();
|
||||
let with_wasi = internal.wasi.is_some();
|
||||
self.store = Store::new(
|
||||
&engine,
|
||||
CurrentPlugin::new(
|
||||
@@ -380,6 +393,16 @@ impl Plugin {
|
||||
)?,
|
||||
);
|
||||
self.store.set_epoch_deadline(1);
|
||||
|
||||
let (instance_pre, linker) = relink(
|
||||
&engine,
|
||||
&mut self.store,
|
||||
&self._functions,
|
||||
&self.modules,
|
||||
with_wasi,
|
||||
)?;
|
||||
self.linker = linker;
|
||||
self.instance_pre = instance_pre;
|
||||
let store = &mut self.store as *mut _;
|
||||
let linker = &mut self.linker as *mut _;
|
||||
let current_plugin = self.current_plugin_mut();
|
||||
@@ -390,14 +413,7 @@ impl Plugin {
|
||||
.limiter(|internal| internal.memory_limiter.as_mut().unwrap());
|
||||
}
|
||||
|
||||
let main = &self.modules[MAIN_KEY];
|
||||
for (name, module) in self.modules.iter() {
|
||||
if name != MAIN_KEY {
|
||||
self.linker.module(&mut self.store, name, module)?;
|
||||
}
|
||||
}
|
||||
self.instantiations = 0;
|
||||
self.instance_pre = self.linker.instantiate_pre(main)?;
|
||||
**instance_lock = None;
|
||||
self.store_needs_reset = false;
|
||||
}
|
||||
@@ -450,7 +466,7 @@ impl Plugin {
|
||||
if let Some(f) = x.func() {
|
||||
let (params, mut results) = (f.params(), f.results());
|
||||
match (params.len(), results.len()) {
|
||||
(0, 1) => results.next() == Some(wasmtime::ValType::I32),
|
||||
(0, 1) => matches!(results.next(), Some(wasmtime::ValType::I32)),
|
||||
(0, 0) => true,
|
||||
_ => false,
|
||||
}
|
||||
@@ -462,7 +478,12 @@ impl Plugin {
|
||||
}
|
||||
|
||||
// Store input in memory and re-initialize `Internal` pointer
|
||||
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
|
||||
pub(crate) fn set_input(
|
||||
&mut self,
|
||||
input: *const u8,
|
||||
mut len: usize,
|
||||
host_context: Option<Rooted<ExternRef>>,
|
||||
) -> Result<(), Error> {
|
||||
self.output = Output::default();
|
||||
self.clear_error()?;
|
||||
let id = self.id.to_string();
|
||||
@@ -496,6 +517,13 @@ impl Plugin {
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(Extern::Global(ctxt)) =
|
||||
self.linker
|
||||
.get(&mut self.store, EXTISM_ENV_MODULE, "extism_context")
|
||||
{
|
||||
ctxt.set(&mut self.store, Val::ExternRef(host_context))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -588,7 +616,7 @@ impl Plugin {
|
||||
if let Some(reactor_init) = reactor_init {
|
||||
reactor_init.call(&mut store, &[], &mut [])?;
|
||||
}
|
||||
let mut results = vec![Val::null(); init.ty(&store).results().len()];
|
||||
let mut results = vec![Val::I32(0); init.ty(&store).results().len()];
|
||||
init.call(
|
||||
&mut store,
|
||||
&[Val::I32(0), Val::I32(0)],
|
||||
@@ -673,6 +701,7 @@ impl Plugin {
|
||||
lock: &mut std::sync::MutexGuard<Option<Instance>>,
|
||||
name: impl AsRef<str>,
|
||||
input: impl AsRef<[u8]>,
|
||||
host_context: Option<Rooted<ExternRef>>,
|
||||
) -> Result<i32, (Error, i32)> {
|
||||
let name = name.as_ref();
|
||||
let input = input.as_ref();
|
||||
@@ -686,7 +715,7 @@ impl Plugin {
|
||||
|
||||
self.instantiate(lock).map_err(|e| (e, -1))?;
|
||||
|
||||
self.set_input(input.as_ptr(), input.len())
|
||||
self.set_input(input.as_ptr(), input.len(), host_context)
|
||||
.map_err(|x| (x, -1))?;
|
||||
|
||||
let func = match self.get_func(lock, name) {
|
||||
@@ -717,9 +746,10 @@ impl Plugin {
|
||||
.expect("Timer should start");
|
||||
self.store.epoch_deadline_trap();
|
||||
self.store.set_epoch_deadline(1);
|
||||
self.current_plugin_mut().start_time = std::time::Instant::now();
|
||||
|
||||
// Call the function
|
||||
let mut results = vec![wasmtime::Val::null(); n_results];
|
||||
let mut results = vec![wasmtime::Val::I32(0); n_results];
|
||||
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
|
||||
|
||||
// Stop timer
|
||||
@@ -804,13 +834,7 @@ 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)
|
||||
});
|
||||
let wasi_exit_code = e.downcast_ref::<wasmtime_wasi::I32Exit>().map(|e| e.0);
|
||||
if let Some(exit_code) = wasi_exit_code {
|
||||
debug!(
|
||||
plugin = self.id.to_string(),
|
||||
@@ -873,7 +897,27 @@ impl Plugin {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes()?;
|
||||
self.raw_call(&mut lock, name, data)
|
||||
self.raw_call(&mut lock, name, data, None)
|
||||
.map_err(|e| e.0)
|
||||
.and_then(move |_| self.output())
|
||||
}
|
||||
|
||||
pub fn call_with_host_context<'a, 'b, T, U, C>(
|
||||
&'b mut self,
|
||||
name: impl AsRef<str>,
|
||||
input: T,
|
||||
host_context: C,
|
||||
) -> Result<U, Error>
|
||||
where
|
||||
T: ToBytes<'a>,
|
||||
U: FromBytes<'b>,
|
||||
C: Any + Send + Sync + 'static,
|
||||
{
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes()?;
|
||||
let ctx = ExternRef::new(&mut self.store, host_context)?;
|
||||
self.raw_call(&mut lock, name, data, Some(ctx))
|
||||
.map_err(|e| e.0)
|
||||
.and_then(move |_| self.output())
|
||||
}
|
||||
@@ -892,7 +936,7 @@ impl Plugin {
|
||||
let lock = self.instance.clone();
|
||||
let mut lock = lock.lock().unwrap();
|
||||
let data = input.to_bytes().map_err(|e| (e, -1))?;
|
||||
self.raw_call(&mut lock, name, data)
|
||||
self.raw_call(&mut lock, name, data, None)
|
||||
.and_then(move |_| self.output().map_err(|e| (e, -1)))
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -41,9 +41,9 @@ pub type ExtismFunctionType = extern "C" fn(
|
||||
/// Log drain callback
|
||||
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
|
||||
|
||||
impl From<&wasmtime::Val> for ExtismVal {
|
||||
fn from(value: &wasmtime::Val) -> Self {
|
||||
match value.ty() {
|
||||
impl ExtismVal {
|
||||
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
|
||||
match value.ty(ctx) {
|
||||
wasmtime::ValType::I32 => ExtismVal {
|
||||
t: ValType::I32,
|
||||
v: ValUnion {
|
||||
@@ -84,6 +84,24 @@ pub unsafe extern "C" fn extism_plugin_id(plugin: *mut Plugin) -> *const u8 {
|
||||
plugin.id.as_bytes().as_ptr()
|
||||
}
|
||||
|
||||
/// Get the current plugin's associated host context data. Returns null if call was made without
|
||||
/// host context.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_current_plugin_host_context(
|
||||
plugin: *mut CurrentPlugin,
|
||||
) -> *mut std::ffi::c_void {
|
||||
if plugin.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
|
||||
ptr
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a pointer to the memory of the currently running plugin
|
||||
/// NOTE: this should only be called from host functions.
|
||||
#[no_mangle]
|
||||
@@ -200,7 +218,11 @@ pub unsafe extern "C" fn extism_function_new(
|
||||
output_types.clone(),
|
||||
user_data,
|
||||
move |plugin, inputs, outputs, user_data| {
|
||||
let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
|
||||
let store = &*plugin.store;
|
||||
let inputs: Vec<_> = inputs
|
||||
.iter()
|
||||
.map(|x| ExtismVal::from_val(x, store))
|
||||
.collect();
|
||||
let mut output_tmp: Vec<_> = output_types
|
||||
.iter()
|
||||
.map(|t| ExtismVal {
|
||||
@@ -389,20 +411,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() {
|
||||
@@ -464,6 +472,31 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
func_name: *const c_char,
|
||||
data: *const u8,
|
||||
data_len: Size,
|
||||
) -> i32 {
|
||||
extism_plugin_call_with_host_context(plugin, func_name, data, data_len, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
struct CVoidContainer(*mut std::ffi::c_void);
|
||||
|
||||
// "You break it, you buy it."
|
||||
unsafe impl Send for CVoidContainer {}
|
||||
unsafe impl Sync for CVoidContainer {}
|
||||
|
||||
/// Call a function with host context.
|
||||
///
|
||||
/// `func_name`: is the function to call
|
||||
/// `data`: is the input data
|
||||
/// `data_len`: is the length of `data`
|
||||
/// `host_context`: a pointer to context data that will be available in host functions
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_call_with_host_context(
|
||||
plugin: *mut Plugin,
|
||||
func_name: *const c_char,
|
||||
data: *const u8,
|
||||
data_len: Size,
|
||||
host_context: *mut std::ffi::c_void,
|
||||
) -> i32 {
|
||||
if plugin.is_null() {
|
||||
return -1;
|
||||
@@ -485,8 +518,11 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
name
|
||||
);
|
||||
let input = std::slice::from_raw_parts(data, data_len as usize);
|
||||
let res = plugin.raw_call(&mut lock, name, input);
|
||||
|
||||
let r = match ExternRef::new(&mut plugin.store, CVoidContainer(host_context)) {
|
||||
Err(e) => return plugin.return_error(&mut lock, e, -1),
|
||||
Ok(x) => x,
|
||||
};
|
||||
let res = plugin.raw_call(&mut lock, name, input, Some(r));
|
||||
match res {
|
||||
Err((e, rc)) => plugin.return_error(&mut lock, e, rc),
|
||||
Ok(x) => x,
|
||||
@@ -671,7 +707,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);
|
||||
|
||||
@@ -240,6 +240,34 @@ fn test_timeout() {
|
||||
assert!(err == "timeout");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_timeout() {
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
[PTR],
|
||||
[PTR],
|
||||
UserData::default(),
|
||||
hello_world,
|
||||
);
|
||||
|
||||
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_HTTP)])
|
||||
.with_timeout(std::time::Duration::from_millis(1))
|
||||
.with_allowed_host("www.extism.org");
|
||||
let mut plugin = Plugin::new(manifest, [f], true).unwrap();
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let output: Result<&[u8], Error> =
|
||||
plugin.call("http_request", r#"{"url": "https://www.extism.org"}"#);
|
||||
let end = std::time::Instant::now();
|
||||
let time = end - start;
|
||||
let err = output.unwrap_err().root_cause().to_string();
|
||||
println!(
|
||||
"Timed out plugin ran for {:?}, with error: {:?}",
|
||||
time, &err
|
||||
);
|
||||
assert!(err == "timeout");
|
||||
}
|
||||
|
||||
typed_plugin!(pub TestTypedPluginGenerics {
|
||||
count_vowels<T: FromBytes<'a>>(&str) -> T
|
||||
});
|
||||
@@ -307,6 +335,42 @@ fn test_toml_manifest() {
|
||||
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_with_host_context() {
|
||||
#[derive(Clone)]
|
||||
struct Foo {
|
||||
message: String,
|
||||
}
|
||||
|
||||
let f = Function::new(
|
||||
"host_reflect",
|
||||
[PTR],
|
||||
[PTR],
|
||||
UserData::default(),
|
||||
|current_plugin, _val, ret, _user_data: UserData<()>| {
|
||||
let foo = current_plugin.host_context::<Foo>()?;
|
||||
let hnd = current_plugin.memory_new(foo.message)?;
|
||||
ret[0] = current_plugin.memory_to_val(hnd);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
let mut plugin = Plugin::new(WASM_REFLECT, [f], true).unwrap();
|
||||
|
||||
let message = "hello world";
|
||||
let output: String = plugin
|
||||
.call_with_host_context(
|
||||
"reflect",
|
||||
"anything, really",
|
||||
Foo {
|
||||
message: message.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzz_reflect_plugin() {
|
||||
// assert!(set_log_file("stdout", Some(log::Level::Trace)));
|
||||
@@ -453,7 +517,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 +534,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],
|
||||
|
||||
Reference in New Issue
Block a user