1#![allow(clippy::missing_safety_doc)]
2
3use std::os::raw::c_char;
4use std::str::FromStr;
5
6use crate::*;
7
8#[repr(C)]
10pub union ValUnion {
11 i32: i32,
12 i64: i64,
13 f32: f32,
14 f64: f64,
15 }
17
18#[repr(C)]
20pub struct ExtismVal {
21 t: ValType,
22 v: ValUnion,
23}
24
25pub struct ExtismFunction(Function);
27
28impl From<Function> for ExtismFunction {
29 fn from(x: Function) -> Self {
30 ExtismFunction(x)
31 }
32}
33
34pub type ExtismFunctionType = extern "C" fn(
36 plugin: *mut Internal,
37 inputs: *const ExtismVal,
38 n_inputs: Size,
39 outputs: *mut ExtismVal,
40 n_outputs: Size,
41 data: *mut std::ffi::c_void,
42);
43
44impl From<&wasmtime::Val> for ExtismVal {
45 fn from(value: &wasmtime::Val) -> Self {
46 match value.ty() {
47 wasmtime::ValType::I32 => ExtismVal {
48 t: ValType::I32,
49 v: ValUnion {
50 i32: value.unwrap_i32(),
51 },
52 },
53 wasmtime::ValType::I64 => ExtismVal {
54 t: ValType::I64,
55 v: ValUnion {
56 i64: value.unwrap_i64(),
57 },
58 },
59 wasmtime::ValType::F32 => ExtismVal {
60 t: ValType::F32,
61 v: ValUnion {
62 f32: value.unwrap_f32(),
63 },
64 },
65 wasmtime::ValType::F64 => ExtismVal {
66 t: ValType::F64,
67 v: ValUnion {
68 f64: value.unwrap_f64(),
69 },
70 },
71 t => todo!("{}", t),
72 }
73 }
74}
75
76#[no_mangle]
78pub unsafe extern "C" fn extism_context_new() -> *mut Context {
79 trace!("Creating new Context");
80 Box::into_raw(Box::new(Context::new()))
81}
82
83#[no_mangle]
85pub unsafe extern "C" fn extism_context_free(ctx: *mut Context) {
86 trace!("Freeing context");
87 if ctx.is_null() {
88 return;
89 }
90 drop(Box::from_raw(ctx))
91}
92
93#[no_mangle]
96pub unsafe extern "C" fn extism_current_plugin_memory(plugin: *mut Internal) -> *mut u8 {
97 if plugin.is_null() {
98 return std::ptr::null_mut();
99 }
100
101 let plugin = &mut *plugin;
102 plugin.memory_ptr()
103}
104
105#[no_mangle]
108pub unsafe extern "C" fn extism_current_plugin_memory_alloc(plugin: *mut Internal, n: Size) -> u64 {
109 if plugin.is_null() {
110 return 0;
111 }
112
113 let plugin = &mut *plugin;
114 plugin.memory_alloc(n as u64).unwrap_or_default()
115}
116
117#[no_mangle]
120pub unsafe extern "C" fn extism_current_plugin_memory_length(
121 plugin: *mut Internal,
122 n: Size,
123) -> Size {
124 if plugin.is_null() {
125 return 0;
126 }
127
128 let plugin = &mut *plugin;
129 plugin.memory_length(n)
130}
131
132#[no_mangle]
135pub unsafe extern "C" fn extism_current_plugin_memory_free(plugin: *mut Internal, ptr: u64) {
136 if plugin.is_null() {
137 return;
138 }
139
140 let plugin = &mut *plugin;
141 plugin.memory_free(ptr);
142}
143
144#[no_mangle]
160pub unsafe extern "C" fn extism_function_new(
161 name: *const std::ffi::c_char,
162 inputs: *const ValType,
163 n_inputs: Size,
164 outputs: *const ValType,
165 n_outputs: Size,
166 func: ExtismFunctionType,
167 user_data: *mut std::ffi::c_void,
168 free_user_data: Option<extern "C" fn(_: *mut std::ffi::c_void)>,
169) -> *mut ExtismFunction {
170 let name = match std::ffi::CStr::from_ptr(name).to_str() {
171 Ok(x) => x.to_string(),
172 Err(_) => {
173 return std::ptr::null_mut();
174 }
175 };
176
177 let inputs = if inputs.is_null() || n_inputs == 0 {
178 &[]
179 } else {
180 std::slice::from_raw_parts(inputs, n_inputs as usize)
181 }
182 .to_vec();
183
184 let output_types = if outputs.is_null() || n_outputs == 0 {
185 &[]
186 } else {
187 std::slice::from_raw_parts(outputs, n_outputs as usize)
188 }
189 .to_vec();
190
191 let user_data = UserData::new_pointer(user_data, free_user_data);
192 let f = Function::new(
193 name,
194 inputs,
195 output_types.clone(),
196 Some(user_data),
197 move |plugin, inputs, outputs, user_data| {
198 let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
199 let mut output_tmp: Vec<_> = output_types
200 .iter()
201 .map(|t| ExtismVal {
202 t: t.clone(),
203 v: ValUnion { i64: 0 },
204 })
205 .collect();
206
207 func(
208 plugin,
209 inputs.as_ptr(),
210 inputs.len() as Size,
211 output_tmp.as_mut_ptr(),
212 output_tmp.len() as Size,
213 user_data.as_ptr(),
214 );
215
216 for (tmp, out) in output_tmp.iter().zip(outputs.iter_mut()) {
217 match tmp.t {
218 ValType::I32 => *out = Val::I32(tmp.v.i32),
219 ValType::I64 => *out = Val::I64(tmp.v.i64),
220 ValType::F32 => *out = Val::F32(tmp.v.f32 as u32),
221 ValType::F64 => *out = Val::F64(tmp.v.f64 as u64),
222 _ => todo!(),
223 }
224 }
225 Ok(())
226 },
227 );
228 Box::into_raw(Box::new(ExtismFunction(f)))
229}
230
231#[no_mangle]
233pub unsafe extern "C" fn extism_function_set_namespace(
234 ptr: *mut ExtismFunction,
235 namespace: *const std::ffi::c_char,
236) {
237 let namespace = std::ffi::CStr::from_ptr(namespace);
238 let f = &mut *ptr;
239 f.0.set_namespace(namespace.to_string_lossy().to_string());
240}
241
242#[no_mangle]
244pub unsafe extern "C" fn extism_function_free(ptr: *mut ExtismFunction) {
245 drop(Box::from_raw(ptr))
246}
247
248#[no_mangle]
256pub unsafe extern "C" fn extism_plugin_new(
257 ctx: *mut Context,
258 wasm: *const u8,
259 wasm_size: Size,
260 functions: *mut *const ExtismFunction,
261 n_functions: Size,
262 with_wasi: bool,
263) -> PluginIndex {
264 trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
265 let ctx = &mut *ctx;
266 let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
267 let mut funcs = vec![];
268
269 if !functions.is_null() {
270 for i in 0..n_functions {
271 unsafe {
272 let f = *functions.add(i as usize);
273 if f.is_null() {
274 continue;
275 }
276 let f = &*f;
277 funcs.push(&f.0);
278 }
279 }
280 }
281 ctx.new_plugin(data, funcs, with_wasi)
282}
283
284#[no_mangle]
291pub unsafe extern "C" fn extism_plugin_update(
292 ctx: *mut Context,
293 index: PluginIndex,
294 wasm: *const u8,
295 wasm_size: Size,
296 functions: *mut *const ExtismFunction,
297 nfunctions: Size,
298 with_wasi: bool,
299) -> bool {
300 trace!("Call to extism_plugin_update with wasm pointer {:?}", wasm);
301 let ctx = &mut *ctx;
302
303 let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
304
305 let mut funcs = vec![];
306
307 if !functions.is_null() {
308 for i in 0..nfunctions {
309 unsafe {
310 let f = *functions.add(i as usize);
311 if f.is_null() {
312 continue;
313 }
314 let f = &*f;
315 funcs.push(&f.0);
316 }
317 }
318 }
319
320 let plugin = match Plugin::new(data, funcs, with_wasi) {
321 Ok(x) => x,
322 Err(e) => {
323 error!("Error creating Plugin: {:?}", e);
324 ctx.set_error(e);
325 return false;
326 }
327 };
328
329 if !ctx.plugins.contains_key(&index) {
330 ctx.set_error("Plugin index does not exist");
331 return false;
332 }
333
334 ctx.plugins.insert(index, plugin);
335
336 debug!("Plugin updated: {index}");
337 true
338}
339
340#[no_mangle]
342pub unsafe extern "C" fn extism_plugin_free(ctx: *mut Context, plugin: PluginIndex) {
343 if plugin < 0 || ctx.is_null() {
344 return;
345 }
346
347 trace!("Freeing plugin {plugin}");
348
349 let ctx = &mut *ctx;
350 ctx.remove(plugin);
351}
352
353pub struct ExtismCancelHandle {
354 pub(crate) epoch_timer_tx: Option<std::sync::mpsc::SyncSender<TimerAction>>,
355 pub id: uuid::Uuid,
356}
357
358#[no_mangle]
360pub unsafe extern "C" fn extism_plugin_cancel_handle(
361 ctx: *mut Context,
362 plugin: PluginIndex,
363) -> *const ExtismCancelHandle {
364 let ctx = &mut *ctx;
365 let mut plugin = match PluginRef::new(ctx, plugin, true) {
366 None => return std::ptr::null_mut(),
367 Some(p) => p,
368 };
369 let plugin = plugin.as_mut();
370 &plugin.cancel_handle as *const _
371}
372
373#[no_mangle]
375pub unsafe extern "C" fn extism_plugin_cancel(handle: *const ExtismCancelHandle) -> bool {
376 let handle = &*handle;
377 if let Some(tx) = &handle.epoch_timer_tx {
378 return tx.send(TimerAction::Cancel { id: handle.id }).is_ok();
379 }
380
381 false
382}
383
384#[no_mangle]
386pub unsafe extern "C" fn extism_context_reset(ctx: *mut Context) {
387 let ctx = &mut *ctx;
388
389 trace!(
390 "Resetting context, plugins cleared: {:?}",
391 ctx.plugins.keys().collect::<Vec<&i32>>()
392 );
393
394 ctx.plugins.clear();
395}
396
397#[no_mangle]
399pub unsafe extern "C" fn extism_plugin_config(
400 ctx: *mut Context,
401 plugin: PluginIndex,
402 json: *const u8,
403 json_size: Size,
404) -> bool {
405 let ctx = &mut *ctx;
406 let mut plugin_ref = match PluginRef::new(ctx, plugin, true) {
407 None => return false,
408 Some(p) => p,
409 };
410 trace!(
411 "Call to extism_plugin_config for {} with json pointer {:?}",
412 plugin_ref.id,
413 json
414 );
415 let plugin = plugin_ref.as_mut();
416
417 let data = std::slice::from_raw_parts(json, json_size as usize);
418 let json: std::collections::BTreeMap<String, Option<String>> =
419 match serde_json::from_slice(data) {
420 Ok(x) => x,
421 Err(e) => {
422 return plugin.error(e, false);
423 }
424 };
425
426 let wasi = &mut plugin.internal_mut().wasi;
427 if let Some(Wasi { ctx, .. }) = wasi {
428 for (k, v) in json.iter() {
429 match v {
430 Some(v) => {
431 let _ = ctx.push_env(k, v);
432 }
433 None => {
434 let _ = ctx.push_env(k, "");
435 }
436 }
437 }
438 }
439
440 let config = &mut plugin.internal_mut().manifest.as_mut().config;
441 for (k, v) in json.into_iter() {
442 match v {
443 Some(v) => {
444 trace!("Config, adding {k}");
445 config.insert(k, v);
446 }
447 None => {
448 trace!("Config, removing {k}");
449 config.remove(&k);
450 }
451 }
452 }
453
454 true
455}
456
457#[no_mangle]
459pub unsafe extern "C" fn extism_plugin_function_exists(
460 ctx: *mut Context,
461 plugin: PluginIndex,
462 func_name: *const c_char,
463) -> bool {
464 let ctx = &mut *ctx;
465 let mut plugin = match PluginRef::new(ctx, plugin, true) {
466 None => return false,
467 Some(p) => p,
468 };
469
470 let name = std::ffi::CStr::from_ptr(func_name);
471 trace!("Call to extism_plugin_function_exists for: {:?}", name);
472
473 let name = match name.to_str() {
474 Ok(x) => x,
475 Err(e) => {
476 return plugin.as_mut().error(e, false);
477 }
478 };
479
480 plugin.as_mut().get_func(name).is_some()
481}
482
483#[no_mangle]
489pub unsafe extern "C" fn extism_plugin_call(
490 ctx: *mut Context,
491 plugin_id: PluginIndex,
492 func_name: *const c_char,
493 data: *const u8,
494 data_len: Size,
495) -> i32 {
496 let ctx = &mut *ctx;
497
498 let name = std::ffi::CStr::from_ptr(func_name);
500 let name = match name.to_str() {
501 Ok(name) => name,
502 Err(e) => return ctx.error(e, -1),
503 };
504 let is_start = name == "_start";
505
506 let mut plugin_ref = match PluginRef::new(ctx, plugin_id, true) {
509 None => return -1,
510 Some(p) => p.start_call(is_start),
511 };
512 let tx = plugin_ref.epoch_timer_tx.clone();
513 let plugin = plugin_ref.as_mut();
514
515 let func = match plugin.get_func(name) {
516 Some(x) => x,
517 None => return plugin.error(format!("Function not found: {name}"), -1),
518 };
519
520 let n_results = func.ty(plugin.store()).results().len();
522 if n_results > 1 {
523 return plugin.error(
524 format!("Function {name} has {n_results} results, expected 0 or 1"),
525 -1,
526 );
527 }
528
529 if let Err(e) = plugin.set_input(data, data_len as usize) {
530 return plugin.error(e, -1);
531 }
532
533 if plugin.has_error() {
534 return -1;
535 }
536
537 if let Err(e) = plugin.start_timer(&tx) {
539 return plugin.error(e, -1);
540 }
541
542 debug!("Calling function: {name} in plugin {plugin_id}");
543
544 let mut results = vec![wasmtime::Val::null(); n_results];
546 let res = func.call(plugin.store_mut(), &[], results.as_mut_slice());
547
548 match res {
549 Ok(()) => (),
550 Err(e) => {
551 plugin.store.set_epoch_deadline(1);
552 if let Some(exit) = e.downcast_ref::<wasmtime_wasi::I32Exit>() {
553 trace!("WASI return code: {}", exit.0);
554 if exit.0 != 0 {
555 return plugin.error(&e, exit.0);
556 }
557 return exit.0;
558 }
559
560 if e.root_cause().to_string() == "timeout" {
561 return plugin.error("timeout", -1);
562 }
563
564 error!("Call: {e:?}");
565 return plugin.error(e.context("Call failed"), -1);
566 }
567 };
568
569 if results.is_empty() {
572 return 0;
573 }
574
575 results[0].unwrap_i32()
577}
578
579pub fn get_context_error(ctx: &Context) -> *const c_char {
580 match &ctx.error {
581 Some(e) => e.as_ptr() as *const _,
582 None => {
583 trace!("Context error is NULL");
584 std::ptr::null()
585 }
586 }
587}
588
589#[no_mangle]
592pub unsafe extern "C" fn extism_error(ctx: *mut Context, plugin: PluginIndex) -> *const c_char {
593 trace!("Call to extism_error for plugin {plugin}");
594
595 let ctx = &mut *ctx;
596
597 if !ctx.plugin_exists(plugin) {
598 return get_context_error(ctx);
599 }
600
601 let mut plugin_ref = match PluginRef::new(ctx, plugin, false) {
602 None => return std::ptr::null(),
603 Some(p) => p,
604 };
605 let plugin = plugin_ref.as_mut();
606 let output = &mut [Val::I64(0)];
607 if let Some(f) = plugin
608 .linker
609 .get(&mut plugin.store, "env", "extism_error_get")
610 {
611 f.into_func()
612 .unwrap()
613 .call(&mut plugin.store, &[], output)
614 .unwrap();
615 }
616 if output[0].unwrap_i64() == 0 {
617 trace!("Error is NULL");
618 return std::ptr::null();
619 }
620
621 plugin.memory_ptr().add(output[0].unwrap_i64() as usize) as *const _
622}
623
624#[no_mangle]
626pub unsafe extern "C" fn extism_plugin_output_length(
627 ctx: *mut Context,
628 plugin: PluginIndex,
629) -> Size {
630 trace!("Call to extism_plugin_output_length for plugin {plugin}");
631
632 let ctx = &mut *ctx;
633 let mut plugin_ref = match PluginRef::new(ctx, plugin, true) {
634 None => return 0,
635 Some(p) => p,
636 };
637 let plugin = plugin_ref.as_mut();
638 let out = &mut [Val::I64(0)];
639 let _ = plugin
640 .linker
641 .get(&mut plugin.store, "env", "extism_output_length")
642 .unwrap()
643 .into_func()
644 .unwrap()
645 .call(&mut plugin.store_mut(), &[], out);
646 let len = out[0].unwrap_i64() as Size;
647 trace!("Output length: {len}");
648 len
649}
650
651#[no_mangle]
653pub unsafe extern "C" fn extism_plugin_output_data(
654 ctx: *mut Context,
655 plugin: PluginIndex,
656) -> *const u8 {
657 trace!("Call to extism_plugin_output_data for plugin {plugin}");
658
659 let ctx = &mut *ctx;
660 let mut plugin_ref = match PluginRef::new(ctx, plugin, true) {
661 None => return std::ptr::null(),
662 Some(p) => p,
663 };
664 let plugin = plugin_ref.as_mut();
665 let ptr = plugin.memory_ptr();
666 let out = &mut [Val::I64(0)];
667 let mut store = &mut *(plugin.store_mut() as *mut Store<_>);
668 plugin
669 .linker
670 .get(&mut store, "env", "extism_output_offset")
671 .unwrap()
672 .into_func()
673 .unwrap()
674 .call(&mut store, &[], out)
675 .unwrap();
676
677 let offs = out[0].unwrap_i64() as usize;
678 trace!("Output offset: {}", offs);
679 ptr.add(offs)
680}
681
682#[no_mangle]
684pub unsafe extern "C" fn extism_log_file(
685 filename: *const c_char,
686 log_level: *const c_char,
687) -> bool {
688 use log::LevelFilter;
689 use log4rs::append::console::ConsoleAppender;
690 use log4rs::append::file::FileAppender;
691 use log4rs::config::{Appender, Config, Logger, Root};
692 use log4rs::encode::pattern::PatternEncoder;
693
694 let file = if !filename.is_null() {
695 let file = std::ffi::CStr::from_ptr(filename);
696 match file.to_str() {
697 Ok(x) => x,
698 Err(_) => {
699 return false;
700 }
701 }
702 } else {
703 "stderr"
704 };
705
706 let level = if !log_level.is_null() {
707 let level = std::ffi::CStr::from_ptr(log_level);
708 match level.to_str() {
709 Ok(x) => x,
710 Err(_) => {
711 return false;
712 }
713 }
714 } else {
715 "error"
716 };
717
718 let level = match LevelFilter::from_str(level) {
719 Ok(x) => x,
720 Err(_) => {
721 return false;
722 }
723 };
724
725 let encoder = Box::new(PatternEncoder::new("{t} {l} {d} - {m}\n"));
726
727 let logfile: Box<dyn log4rs::append::Append> =
728 if file == "-" || file == "stdout" || file == "stderr" {
729 let target = if file == "-" || file == "stdout" {
730 log4rs::append::console::Target::Stdout
731 } else {
732 log4rs::append::console::Target::Stderr
733 };
734 let console = ConsoleAppender::builder().target(target).encoder(encoder);
735 Box::new(console.build())
736 } else {
737 match FileAppender::builder().encoder(encoder).build(file) {
738 Ok(x) => Box::new(x),
739 Err(_) => {
740 return false;
741 }
742 }
743 };
744
745 let config = match Config::builder()
746 .appender(Appender::builder().build("logfile", logfile))
747 .logger(
748 Logger::builder()
749 .appender("logfile")
750 .build("extism_runtime", level),
751 )
752 .build(Root::builder().build(LevelFilter::Off))
753 {
754 Ok(x) => x,
755 Err(_) => {
756 return false;
757 }
758 };
759
760 if log4rs::init_config(config).is_err() {
761 return false;
762 }
763 true
764}
765
766const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
767
768#[no_mangle]
770pub unsafe extern "C" fn extism_version() -> *const c_char {
771 VERSION.as_ptr() as *const _
772}