extism/
sdk.rs

1#![allow(clippy::missing_safety_doc)]
2
3use std::{os::raw::c_char, ptr::null_mut};
4
5use crate::*;
6
7pub type ExtismMemoryHandle = u64;
8pub type Size = u64;
9pub struct ExtismFunction(std::cell::Cell<Option<Function>>);
10
11/// The return code used to specify a successful plugin call
12pub static EXTISM_SUCCESS: i32 = 0;
13
14fn make_error_msg(s: String) -> Vec<u8> {
15    let mut s = s.into_bytes();
16    s.push(0);
17    s
18}
19
20/// A union type for host function argument/return values
21#[repr(C)]
22pub union ValUnion {
23    i32: i32,
24    i64: i64,
25    f32: f32,
26    f64: f64,
27    // TODO: v128, ExternRef, FuncRef
28}
29
30/// `ExtismVal` holds the type and value of a function argument/return
31#[repr(C)]
32pub struct ExtismVal {
33    t: ValType,
34    v: ValUnion,
35}
36
37/// Host function signature
38pub type ExtismFunctionType = extern "C" fn(
39    plugin: *mut CurrentPlugin,
40    inputs: *const ExtismVal,
41    n_inputs: Size,
42    outputs: *mut ExtismVal,
43    n_outputs: Size,
44    data: *mut std::ffi::c_void,
45);
46
47/// Log drain callback
48pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
49
50impl ExtismVal {
51    fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Result<Self, Error> {
52        match value.ty(ctx)? {
53            wasmtime::ValType::I32 => Ok(ExtismVal {
54                t: ValType::I32,
55                v: ValUnion {
56                    i32: value.unwrap_i32(),
57                },
58            }),
59            wasmtime::ValType::I64 => Ok(ExtismVal {
60                t: ValType::I64,
61                v: ValUnion {
62                    i64: value.unwrap_i64(),
63                },
64            }),
65            wasmtime::ValType::F32 => Ok(ExtismVal {
66                t: ValType::F32,
67                v: ValUnion {
68                    f32: value.unwrap_f32(),
69                },
70            }),
71            wasmtime::ValType::F64 => Ok(ExtismVal {
72                t: ValType::F64,
73                v: ValUnion {
74                    f64: value.unwrap_f64(),
75                },
76            }),
77            t => todo!("{}", t),
78        }
79    }
80}
81
82/// Get a plugin's ID, the returned bytes are a 16 byte buffer that represent a UUIDv4
83#[no_mangle]
84pub unsafe extern "C" fn extism_plugin_id(plugin: *mut Plugin) -> *const u8 {
85    if plugin.is_null() {
86        return std::ptr::null_mut();
87    }
88
89    let plugin = &mut *plugin;
90    plugin.id.as_bytes().as_ptr()
91}
92
93/// Get the current plugin's associated host context data. Returns null if call was made without
94/// host context.
95#[no_mangle]
96pub unsafe extern "C" fn extism_current_plugin_host_context(
97    plugin: *mut CurrentPlugin,
98) -> *mut std::ffi::c_void {
99    if plugin.is_null() {
100        return std::ptr::null_mut();
101    }
102
103    let plugin = &mut *plugin;
104    if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
105        *ptr
106    } else {
107        std::ptr::null_mut()
108    }
109}
110
111/// Returns a pointer to the memory of the currently running plugin
112/// NOTE: this should only be called from host functions.
113#[no_mangle]
114pub unsafe extern "C" fn extism_current_plugin_memory(plugin: *mut CurrentPlugin) -> *mut u8 {
115    if plugin.is_null() {
116        return std::ptr::null_mut();
117    }
118
119    let plugin = &mut *plugin;
120    plugin.memory_ptr()
121}
122
123/// Allocate a memory block in the currently running plugin
124/// NOTE: this should only be called from host functions.
125#[no_mangle]
126pub unsafe extern "C" fn extism_current_plugin_memory_alloc(
127    plugin: *mut CurrentPlugin,
128    n: Size,
129) -> ExtismMemoryHandle {
130    if plugin.is_null() {
131        return 0;
132    }
133
134    let plugin = &mut *plugin;
135    match plugin.memory_alloc(n) {
136        Ok(x) => x.offset(),
137        Err(_) => 0,
138    }
139}
140
141/// Get the length of an allocated block
142/// NOTE: this should only be called from host functions.
143#[no_mangle]
144pub unsafe extern "C" fn extism_current_plugin_memory_length(
145    plugin: *mut CurrentPlugin,
146    n: ExtismMemoryHandle,
147) -> Size {
148    if plugin.is_null() {
149        return 0;
150    }
151
152    let plugin = &mut *plugin;
153    plugin.memory_length(n).unwrap_or_default()
154}
155
156/// Free an allocated memory block
157/// NOTE: this should only be called from host functions.
158#[no_mangle]
159pub unsafe extern "C" fn extism_current_plugin_memory_free(
160    plugin: *mut CurrentPlugin,
161    ptr: ExtismMemoryHandle,
162) {
163    if plugin.is_null() {
164        return;
165    }
166
167    let plugin = &mut *plugin;
168    if let Some(handle) = plugin.memory_handle(ptr) {
169        let _ = plugin.memory_free(handle);
170    }
171}
172
173/// Create a new host function
174///
175/// Arguments
176/// - `name`: function name, this should be valid UTF-8
177/// - `inputs`: argument types
178/// - `n_inputs`: number of argument types
179/// - `outputs`: return types
180/// - `n_outputs`: number of return types
181/// - `func`: the function to call
182/// - `user_data`: a pointer that will be passed to the function when it's called
183///   this value should live as long as the function exists
184/// - `free_user_data`: a callback to release the `user_data` value when the resulting
185///   `ExtismFunction` is freed.
186///
187/// Returns a new `ExtismFunction` or `null` if the `name` argument is invalid.
188#[no_mangle]
189pub unsafe extern "C" fn extism_function_new(
190    name: *const std::ffi::c_char,
191    inputs: *const ValType,
192    n_inputs: Size,
193    outputs: *const ValType,
194    n_outputs: Size,
195    func: ExtismFunctionType,
196    user_data: *mut std::ffi::c_void,
197    free_user_data: Option<extern "C" fn(_: *mut std::ffi::c_void)>,
198) -> *mut ExtismFunction {
199    let name = match std::ffi::CStr::from_ptr(name).to_str() {
200        Ok(x) => x.to_string(),
201        Err(_) => {
202            return std::ptr::null_mut();
203        }
204    };
205
206    let inputs = if inputs.is_null() || n_inputs == 0 {
207        &[]
208    } else {
209        std::slice::from_raw_parts(inputs, n_inputs as usize)
210    }
211    .to_vec();
212
213    let output_types = if outputs.is_null() || n_outputs == 0 {
214        &[]
215    } else {
216        std::slice::from_raw_parts(outputs, n_outputs as usize)
217    }
218    .to_vec();
219
220    let user_data: UserData<()> = UserData::new_pointer(user_data, free_user_data);
221    let f = Function::new(
222        name,
223        inputs,
224        output_types.clone(),
225        user_data,
226        move |plugin, inputs, outputs, user_data| {
227            let store = &*plugin.store;
228            let inputs: Vec<_> = inputs
229                .iter()
230                .map(|x| ExtismVal::from_val(x, store).unwrap())
231                .collect();
232            let mut output_tmp: Vec<_> = output_types
233                .iter()
234                .map(|t| ExtismVal {
235                    t: t.clone(),
236                    v: ValUnion { i64: 0 },
237                })
238                .collect();
239
240            // We cannot simply "get" the Vec's storage pointer because
241            // the underlying storage might be invalid when the Vec is empty.
242            // In that case, we return (null, 0).
243
244            let (inputs_ptr, inputs_len) = if inputs.is_empty() {
245                (core::ptr::null(), 0 as Size)
246            } else {
247                (inputs.as_ptr(), inputs.len() as Size)
248            };
249
250            let (output_ptr, output_len) = if output_tmp.is_empty() {
251                (null_mut(), 0 as Size)
252            } else {
253                (output_tmp.as_mut_ptr(), output_tmp.len() as Size)
254            };
255
256            func(
257                plugin,
258                inputs_ptr,
259                inputs_len,
260                output_ptr,
261                output_len,
262                user_data.as_ptr(),
263            );
264
265            for (tmp, out) in output_tmp.iter().zip(outputs.iter_mut()) {
266                match tmp.t {
267                    ValType::I32 => *out = Val::I32(tmp.v.i32),
268                    ValType::I64 => *out = Val::I64(tmp.v.i64),
269                    ValType::F32 => *out = Val::F32(tmp.v.f32.to_bits()),
270                    ValType::F64 => *out = Val::F64(tmp.v.f64.to_bits()),
271                    _ => todo!(),
272                }
273            }
274            Ok(())
275        },
276    );
277    Box::into_raw(Box::new(ExtismFunction(std::cell::Cell::new(Some(f)))))
278}
279
280/// Free `ExtismFunction`
281#[no_mangle]
282pub unsafe extern "C" fn extism_function_free(f: *mut ExtismFunction) {
283    if f.is_null() {
284        return;
285    }
286
287    drop(Box::from_raw(f))
288}
289
290/// Set the namespace of an `ExtismFunction`
291#[no_mangle]
292pub unsafe extern "C" fn extism_function_set_namespace(
293    ptr: *mut ExtismFunction,
294    namespace: *const std::ffi::c_char,
295) {
296    let namespace = std::ffi::CStr::from_ptr(namespace);
297    let f = &mut *ptr;
298    if let Some(x) = f.0.get_mut() {
299        x.set_namespace(namespace.to_string_lossy().to_string());
300    } else {
301        debug!("Trying to set namespace of already registered function")
302    }
303}
304
305/// Pre-compile an Extism plugin
306#[no_mangle]
307pub unsafe extern "C" fn extism_compiled_plugin_new(
308    wasm: *const u8,
309    wasm_size: Size,
310    functions: *mut *const ExtismFunction,
311    n_functions: Size,
312    with_wasi: bool,
313    errmsg: *mut *mut std::ffi::c_char,
314) -> *mut CompiledPlugin {
315    trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
316    let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
317
318    let mut builder = PluginBuilder::new(data).with_wasi(with_wasi);
319
320    if !functions.is_null() {
321        let funcs = (0..n_functions)
322            .map(|i| unsafe { *functions.add(i as usize) })
323            .map(|ptr| {
324                if ptr.is_null() {
325                    return Err("Cannot pass null pointer");
326                }
327
328                let ExtismFunction(func) = &*ptr;
329                let Some(func) = func.take() else {
330                    return Err("Function cannot be registered with multiple different Plugins");
331                };
332
333                Ok(func)
334            })
335            .collect::<Result<Vec<_>, _>>()
336            .unwrap_or_else(|e| {
337                if !errmsg.is_null() {
338                    let e = std::ffi::CString::new(e.to_string()).unwrap();
339                    *errmsg = e.into_raw();
340                }
341                Vec::new()
342            });
343
344        if funcs.len() != n_functions as usize {
345            return std::ptr::null_mut();
346        }
347
348        builder = builder.with_functions(funcs);
349    }
350
351    CompiledPlugin::new(builder)
352        .map(|v| Box::into_raw(Box::new(v)))
353        .unwrap_or_else(|e| {
354            if !errmsg.is_null() {
355                let e = std::ffi::CString::new(format!(
356                    "Unable to compile Extism plugin: {}",
357                    e.root_cause(),
358                ))
359                .unwrap();
360                *errmsg = e.into_raw();
361            }
362            std::ptr::null_mut()
363        })
364}
365
366/// Free `ExtismCompiledPlugin`
367#[no_mangle]
368pub unsafe extern "C" fn extism_compiled_plugin_free(plugin: *mut CompiledPlugin) {
369    if plugin.is_null() {
370        return;
371    }
372
373    let plugin = Box::from_raw(plugin);
374    trace!("called extism_compiled_plugin_free");
375    drop(plugin)
376}
377
378/// Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
379///
380/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
381/// `wasm_size`: the length of the `wasm` parameter
382/// `functions`: an array of `ExtismFunction*`
383/// `n_functions`: the number of functions provided
384/// `with_wasi`: enables/disables WASI
385#[no_mangle]
386pub unsafe extern "C" fn extism_plugin_new(
387    wasm: *const u8,
388    wasm_size: Size,
389    functions: *mut *const ExtismFunction,
390    n_functions: Size,
391    with_wasi: bool,
392    errmsg: *mut *mut std::ffi::c_char,
393) -> *mut Plugin {
394    let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
395    let funcs = if functions.is_null() {
396        vec![]
397    } else {
398        let funcs = (0..n_functions)
399            .map(|i| unsafe { *functions.add(i as usize) })
400            .map(|ptr| {
401                if ptr.is_null() {
402                    return Err("Cannot pass null pointer");
403                }
404
405                let ExtismFunction(func) = &*ptr;
406                let Some(func) = func.take() else {
407                    return Err("Function cannot be registered with multiple different Plugins");
408                };
409
410                Ok(func)
411            })
412            .collect::<Result<Vec<_>, _>>()
413            .unwrap_or_else(|e| {
414                if !errmsg.is_null() {
415                    let e = std::ffi::CString::new(e.to_string()).unwrap();
416                    *errmsg = e.into_raw();
417                }
418                Vec::new()
419            });
420
421        if funcs.len() != n_functions as usize {
422            return std::ptr::null_mut();
423        }
424
425        funcs
426    };
427
428    Plugin::new(data, funcs, with_wasi)
429        .map(|v| Box::into_raw(Box::new(v)))
430        .unwrap_or_else(|e| {
431            if !errmsg.is_null() {
432                let e = std::ffi::CString::new(format!(
433                    "Unable to compile Extism plugin: {}",
434                    e.root_cause(),
435                ))
436                .unwrap();
437                *errmsg = e.into_raw();
438            }
439            std::ptr::null_mut()
440        })
441}
442
443/// Create a new plugin from an `ExtismCompiledPlugin`
444#[no_mangle]
445pub unsafe extern "C" fn extism_plugin_new_from_compiled(
446    compiled: *const CompiledPlugin,
447    errmsg: *mut *mut std::ffi::c_char,
448) -> *mut Plugin {
449    let plugin = Plugin::new_from_compiled(&*compiled);
450    match plugin {
451        Err(e) => {
452            if !errmsg.is_null() {
453                let e = std::ffi::CString::new(format!(
454                    "Unable to create Extism plugin: {}",
455                    e.root_cause(),
456                ))
457                .unwrap();
458                *errmsg = e.into_raw();
459            }
460            std::ptr::null_mut()
461        }
462        Ok(p) => Box::into_raw(Box::new(p)),
463    }
464}
465
466/// Create a new plugin and set the number of instructions a plugin is allowed to execute
467#[no_mangle]
468pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
469    wasm: *const u8,
470    wasm_size: Size,
471    functions: *mut *const ExtismFunction,
472    n_functions: Size,
473    with_wasi: bool,
474    fuel_limit: u64,
475    errmsg: *mut *mut std::ffi::c_char,
476) -> *mut Plugin {
477    trace!(
478        "Call to extism_plugin_new_with_fuel_limit with wasm pointer {:?}",
479        wasm
480    );
481    let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
482    let funcs = if functions.is_null() {
483        vec![]
484    } else {
485        let funcs = (0..n_functions)
486            .map(|i| unsafe { *functions.add(i as usize) })
487            .map(|ptr| {
488                if ptr.is_null() {
489                    return Err("Cannot pass null pointer");
490                }
491
492                let ExtismFunction(func) = &*ptr;
493                let Some(func) = func.take() else {
494                    return Err("Function cannot be registered with multiple different Plugins");
495                };
496
497                Ok(func)
498            })
499            .collect::<Result<Vec<_>, _>>()
500            .unwrap_or_else(|e| {
501                if !errmsg.is_null() {
502                    let e = std::ffi::CString::new(e.to_string()).unwrap();
503                    *errmsg = e.into_raw();
504                }
505                Vec::new()
506            });
507
508        if funcs.len() != n_functions as usize {
509            return std::ptr::null_mut();
510        }
511
512        funcs
513    };
514
515    let compiled = match CompiledPlugin::new(
516        PluginBuilder::new(data)
517            .with_functions(funcs)
518            .with_wasi(with_wasi)
519            .with_fuel_limit(fuel_limit),
520    ) {
521        Ok(x) => x,
522        Err(e) => {
523            if !errmsg.is_null() {
524                let e = std::ffi::CString::new(format!(
525                    "Unable to compile Extism plugin: {}",
526                    e.root_cause(),
527                ))
528                .unwrap();
529                *errmsg = e.into_raw();
530            }
531            return std::ptr::null_mut();
532        }
533    };
534
535    let plugin = Plugin::new_from_compiled(&compiled);
536
537    match plugin {
538        Err(e) => {
539            if !errmsg.is_null() {
540                let e = std::ffi::CString::new(format!(
541                    "Unable to create Extism plugin: {}",
542                    e.root_cause(),
543                ))
544                .unwrap();
545                *errmsg = e.into_raw();
546            }
547            std::ptr::null_mut()
548        }
549        Ok(p) => Box::into_raw(Box::new(p)),
550    }
551}
552
553/// Enable HTTP response headers in plugins using `extism:host/env::http_request`
554#[no_mangle]
555pub unsafe extern "C" fn extism_plugin_allow_http_response_headers(plugin: *mut Plugin) {
556    let plugin = &mut *plugin;
557    plugin.store.data_mut().http_headers = Some(BTreeMap::new());
558}
559
560/// Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
561#[no_mangle]
562pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char) {
563    if err.is_null() {
564        return;
565    }
566    drop(std::ffi::CString::from_raw(err))
567}
568
569/// Free `ExtismPlugin`
570#[no_mangle]
571pub unsafe extern "C" fn extism_plugin_free(plugin: *mut Plugin) {
572    if plugin.is_null() {
573        return;
574    }
575
576    let plugin = Box::from_raw(plugin);
577    trace!(plugin = plugin.id.to_string(), "called extism_plugin_free");
578    drop(plugin)
579}
580
581/// Get handle for plugin cancellation
582#[no_mangle]
583pub unsafe extern "C" fn extism_plugin_cancel_handle(plugin: *const Plugin) -> *const CancelHandle {
584    if plugin.is_null() {
585        return std::ptr::null();
586    }
587    let plugin = &*plugin;
588    trace!(
589        plugin = plugin.id.to_string(),
590        "called extism_plugin_cancel_handle"
591    );
592    &plugin.cancel_handle as *const _
593}
594
595/// Cancel a running plugin
596#[no_mangle]
597pub unsafe extern "C" fn extism_plugin_cancel(handle: *const CancelHandle) -> bool {
598    let handle = &*handle;
599    trace!(
600        plugin = handle.id.to_string(),
601        "called extism_plugin_cancel"
602    );
603    handle.cancel().is_ok()
604}
605
606/// Update plugin config values.
607//
608// This will merge with the existing values, if an existing value is set to `null` it will
609// be removed
610#[no_mangle]
611pub unsafe extern "C" fn extism_plugin_config(
612    plugin: *mut Plugin,
613    json: *const u8,
614    json_size: Size,
615) -> bool {
616    if plugin.is_null() {
617        return false;
618    }
619    let plugin = &mut *plugin;
620
621    trace!(
622        plugin = plugin.id.to_string(),
623        "call to extism_plugin_config with pointer {:?}",
624        json
625    );
626    let data = std::slice::from_raw_parts(json, json_size as usize);
627    let json: std::collections::BTreeMap<String, Option<String>> =
628        match serde_json::from_slice(data) {
629            Ok(x) => x,
630            Err(_) => {
631                return false;
632            }
633        };
634
635    let id = plugin.id;
636    let config = &mut plugin.current_plugin_mut().manifest.config;
637    for (k, v) in json.into_iter() {
638        match v {
639            Some(v) => {
640                trace!(plugin = id.to_string(), "config, adding {k}");
641                config.insert(k, v);
642            }
643            None => {
644                trace!(plugin = id.to_string(), "config, removing {k}");
645                config.remove(&k);
646            }
647        }
648    }
649
650    let _ = plugin.clear_error();
651    true
652}
653
654/// Returns true if `func_name` exists
655#[no_mangle]
656pub unsafe extern "C" fn extism_plugin_function_exists(
657    plugin: *mut Plugin,
658    func_name: *const c_char,
659) -> bool {
660    if plugin.is_null() {
661        return false;
662    }
663    let plugin = &mut *plugin;
664    let name = std::ffi::CStr::from_ptr(func_name);
665    trace!(
666        plugin = plugin.id.to_string(),
667        "extism_plugin_function_exists: {:?}",
668        name
669    );
670
671    let name = match name.to_str() {
672        Ok(x) => x,
673        Err(_) => {
674            return false;
675        }
676    };
677
678    let _ = plugin.clear_error();
679    plugin.function_exists(name)
680}
681
682/// Call a function
683///
684/// `func_name`: is the function to call
685/// `data`: is the input data
686/// `data_len`: is the length of `data`
687#[no_mangle]
688pub unsafe extern "C" fn extism_plugin_call(
689    plugin: *mut Plugin,
690    func_name: *const c_char,
691    data: *const u8,
692    data_len: Size,
693) -> i32 {
694    extism_plugin_call_with_host_context(plugin, func_name, data, data_len, std::ptr::null_mut())
695}
696
697#[derive(Clone)]
698#[repr(transparent)]
699struct CVoidContainer(*mut std::ffi::c_void);
700
701// "You break it, you buy it."
702unsafe impl Send for CVoidContainer {}
703unsafe impl Sync for CVoidContainer {}
704
705/// Call a function with host context.
706///
707/// `func_name`: is the function to call
708/// `data`: is the input data
709/// `data_len`: is the length of `data`
710/// `host_context`: a pointer to context data that will be available in host functions
711#[no_mangle]
712pub unsafe extern "C" fn extism_plugin_call_with_host_context(
713    plugin: *mut Plugin,
714    func_name: *const c_char,
715    data: *const u8,
716    data_len: Size,
717    host_context: *mut std::ffi::c_void,
718) -> i32 {
719    if plugin.is_null() {
720        return -1;
721    }
722    let plugin = &mut *plugin;
723    let lock = plugin.instance.clone();
724    let mut lock = lock.lock().unwrap();
725
726    // Get function name
727    let name = std::ffi::CStr::from_ptr(func_name);
728    let name = match name.to_str() {
729        Ok(name) => name,
730        Err(e) => {
731            plugin.error_msg = Some(make_error_msg(e.to_string()));
732            return -1;
733        }
734    };
735
736    trace!(
737        plugin = plugin.id.to_string(),
738        "calling function {} using extism_plugin_call",
739        name
740    );
741    let input = std::slice::from_raw_parts(data, data_len as usize);
742    let r = if host_context.is_null() {
743        None
744    } else {
745        Some(CVoidContainer(host_context))
746    };
747    let res = plugin.raw_call(&mut lock, name, input, r);
748    match res {
749        Err((e, rc)) => {
750            plugin.error_msg = Some(make_error_msg(e.to_string()));
751            rc
752        }
753        Ok(x) => x,
754    }
755}
756
757/// Get the error associated with a `Plugin`
758#[no_mangle]
759#[deprecated]
760pub unsafe extern "C" fn extism_error(plugin: *mut Plugin) -> *const c_char {
761    extism_plugin_error(plugin)
762}
763
764/// Get the error associated with a `Plugin`
765#[no_mangle]
766pub unsafe extern "C" fn extism_plugin_error(plugin: *mut Plugin) -> *const c_char {
767    if plugin.is_null() {
768        return std::ptr::null();
769    }
770    let plugin = &mut *plugin;
771    let _lock = plugin.instance.clone();
772    let _lock = _lock.lock().unwrap();
773
774    if plugin.output.error_offset == 0 {
775        if let Some(err) = &plugin.error_msg {
776            return err.as_ptr() as *const _;
777        }
778        trace!(plugin = plugin.id.to_string(), "error is NULL");
779        return std::ptr::null();
780    }
781
782    let offs = plugin.output.error_offset;
783
784    let ptr = plugin.current_plugin_mut().memory_ptr().add(offs as usize) as *const _;
785
786    let len = plugin
787        .current_plugin_mut()
788        .memory_length(offs)
789        .unwrap_or_default();
790
791    let mut data = std::slice::from_raw_parts(ptr, len as usize).to_vec();
792    data.push(0);
793    plugin.error_msg = Some(data);
794    plugin.error_msg.as_ref().unwrap().as_ptr() as *const _
795}
796
797/// Get the length of a plugin's output data
798#[no_mangle]
799pub unsafe extern "C" fn extism_plugin_output_length(plugin: *mut Plugin) -> Size {
800    if plugin.is_null() {
801        return 0;
802    }
803    let plugin = &mut *plugin;
804    let _lock = plugin.instance.clone();
805    let _lock = _lock.lock().unwrap();
806    plugin.output.length
807}
808
809/// Get a pointer to the output data
810#[no_mangle]
811pub unsafe extern "C" fn extism_plugin_output_data(plugin: *mut Plugin) -> *const u8 {
812    if plugin.is_null() {
813        return std::ptr::null();
814    }
815    let plugin = &mut *plugin;
816    let _lock = plugin.instance.clone();
817    let _lock = _lock.lock().unwrap();
818    trace!(
819        plugin = plugin.id.to_string(),
820        "extism_plugin_output_data: offset={}, length={}",
821        plugin.output.offset,
822        plugin.output.length
823    );
824
825    let ptr = plugin.current_plugin_mut().memory_ptr();
826    ptr.add(plugin.output.offset as usize)
827}
828
829/// Set log file and level.
830/// The log level can be either one of: info, error, trace, debug, warn or a more
831/// complex filter like `extism=trace,cranelift=debug`
832/// The file will be created if it doesn't exist.
833#[no_mangle]
834pub unsafe extern "C" fn extism_log_file(
835    filename: *const c_char,
836    log_level: *const c_char,
837) -> bool {
838    let file = if !filename.is_null() {
839        let file = std::ffi::CStr::from_ptr(filename);
840        match file.to_str() {
841            Ok(x) => x,
842            Err(_) => {
843                return false;
844            }
845        }
846    } else {
847        "stderr"
848    };
849
850    let level = if !log_level.is_null() {
851        let level = std::ffi::CStr::from_ptr(log_level);
852        match level.to_str() {
853            Ok(x) => x,
854            Err(_) => {
855                return false;
856            }
857        }
858    } else {
859        "error"
860    };
861
862    set_log_file(file, level).is_ok()
863}
864
865// Set the log file Extism will use, this is a global configuration
866fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result<(), Error> {
867    let log_file = log_file.into();
868    let s = log_file.to_str();
869    let is_level = tracing::Level::from_str(filter).is_ok();
870    let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
871        let x = tracing_subscriber::EnvFilter::builder()
872            .with_default_directive(tracing::Level::ERROR.into());
873        if is_level {
874            x.parse_lossy(format!("extism={filter}"))
875        } else {
876            x.parse_lossy(filter)
877        }
878    });
879
880    let res = if s == Some("-") || s == Some("stderr") {
881        cfg.with_ansi(true).with_writer(std::io::stderr).try_init()
882    } else if s == Some("stdout") {
883        cfg.with_ansi(true).with_writer(std::io::stdout).try_init()
884    } else {
885        let log_file = log_file.to_path_buf();
886        let f = std::fs::OpenOptions::new()
887            .create(true)
888            .append(true)
889            .open(log_file)
890            .expect("Open log file");
891        cfg.with_ansi(false)
892            .with_writer(move || f.try_clone().unwrap())
893            .try_init()
894    };
895
896    if let Err(e) = res {
897        return Err(Error::msg(e.to_string()));
898    }
899    Ok(())
900}
901
902static LOG_BUFFER: std::sync::Mutex<Option<LogBuffer>> = std::sync::Mutex::new(None);
903
904/// Enable a custom log handler, this will buffer logs until `extism_log_drain` is called
905/// Log level should be one of: info, error, trace, debug, warn
906#[no_mangle]
907pub unsafe extern "C" fn extism_log_custom(log_level: *const c_char) -> bool {
908    let level = if !log_level.is_null() {
909        let level = std::ffi::CStr::from_ptr(log_level);
910        match level.to_str() {
911            Ok(x) => x,
912            Err(_) => {
913                return false;
914            }
915        }
916    } else {
917        "error"
918    };
919
920    set_log_buffer(level).is_ok()
921}
922
923unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
924    let is_level = tracing::Level::from_str(filter).is_ok();
925    let cfg = tracing_subscriber::FmtSubscriber::builder().with_env_filter({
926        let x = tracing_subscriber::EnvFilter::builder()
927            .with_default_directive(tracing::Level::ERROR.into());
928        if is_level {
929            x.parse_lossy(format!("extism={filter}"))
930        } else {
931            x.parse_lossy(filter)
932        }
933    });
934    *LOG_BUFFER.lock().unwrap() = Some(LogBuffer::default());
935    let buf = LOG_BUFFER.lock().unwrap().clone().unwrap();
936    cfg.with_ansi(false)
937        .with_writer(move || buf.clone())
938        .try_init()
939        .map_err(|x| Error::msg(x.to_string()))?;
940    Ok(())
941}
942
943#[no_mangle]
944/// Calls the provided callback function for each buffered log line.
945/// This is only needed when `extism_log_custom` is used.
946pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
947    if let Some(buf) = LOG_BUFFER.lock().unwrap().as_mut() {
948        if let Ok(mut buf) = buf.buffer.lock() {
949            for (line, len) in buf.drain(..) {
950                handler(line.as_ptr(), len as u64);
951            }
952        }
953    }
954}
955
956#[derive(Default, Clone)]
957struct LogBuffer {
958    buffer:
959        std::sync::Arc<std::sync::Mutex<std::collections::VecDeque<(std::ffi::CString, usize)>>>,
960}
961
962unsafe impl Send for LogBuffer {}
963unsafe impl Sync for LogBuffer {}
964
965impl std::io::Write for LogBuffer {
966    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
967        if let Ok(s) = std::str::from_utf8(buf) {
968            if let Ok(mut buf) = self.buffer.lock() {
969                buf.push_back((std::ffi::CString::new(s)?, s.len()));
970            }
971        }
972
973        Ok(buf.len())
974    }
975
976    fn flush(&mut self) -> std::io::Result<()> {
977        Ok(())
978    }
979}
980
981/// Reset the Extism runtime, this will invalidate all allocated memory
982#[no_mangle]
983pub unsafe extern "C" fn extism_plugin_reset(plugin: *mut Plugin) -> bool {
984    let plugin = &mut *plugin;
985
986    if let Err(e) = plugin.reset() {
987        error!(
988            plugin = plugin.id.to_string(),
989            "unable to reset plugin: {}",
990            e.to_string()
991        );
992        if let Err(e) = plugin.current_plugin_mut().set_error(e.to_string()) {
993            error!(
994                plugin = plugin.id.to_string(),
995                "unable to set error after failed plugin reset: {}",
996                e.to_string()
997            );
998        }
999        false
1000    } else {
1001        true
1002    }
1003}
1004
1005/// Get the Extism version string
1006#[no_mangle]
1007pub unsafe extern "C" fn extism_version() -> *const c_char {
1008    VERSION.as_ptr() as *const _
1009}