diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3cd70d..5bbac96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,11 +57,11 @@ jobs: python-version: '3.9' check-latest: true - - name: Load cached Poetry installation - uses: actions/cache@v2 - with: - path: ~/.local - key: poetry-0 + # - name: Load cached Poetry installation + # uses: actions/cache@v2 + # with: + # path: ~/.local + # key: poetry-0 - name: Install Poetry uses: snok/install-poetry@v1 @@ -107,5 +107,11 @@ jobs: # opam install -y . # cd ocaml # opam exec -- dune exec extism - - \ No newline at end of file + + - name: Setup Haskell env + uses: haskell/actions/setup@v2 + + - name: Test Haskell SDK + run: | + cd haskell + LD_LIBRARY_PATH=/usr/local/lib cabal test diff --git a/.gitignore b/.gitignore index af390fa..51accc9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ ocaml/duniverse ocaml/_build wasm/rust-pdk/target php/Extism.php +dist-newstyle +.stack-work \ No newline at end of file diff --git a/README.md b/README.md index 912e090..d43decf 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The universal plug-in system. Run WebAssembly extensions inside your app. Use id [Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk), [Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk), [C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk), -[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk) & more (others coming soon). +[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk), [Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk) & more (others coming soon). Plug-in development kits (PDK) for plug-in authors supported in Rust, AssemblyScript, Go, C/C++. @@ -48,4 +48,4 @@ Extism is an open-source product from the team at:

-_Reach out and tell us what you're building! We'd love to help._ \ No newline at end of file +_Reach out and tell us what you're building! We'd love to help._ diff --git a/dune-project b/dune-project index 6d3e009..0a7785c 100644 --- a/dune-project +++ b/dune-project @@ -7,20 +7,18 @@ (source (github extism/extism)) -(authors "Author Name") +(authors "Extism Authors ") -(maintainers "Maintainer Name") +(maintainers "Extism Authors ") -(license LICENSE) +(license BSD-3) -(documentation https://url/to/documentation) +(documentation https://github.com/extism/extism) (package (name extism) - (synopsis "A short synopsis") - (description "A longer description") + (synopsis "Extism bindings") + (description "Bindings to Extism, the universal plugin system") (depends ocaml dune ctypes-foreign bigstringaf ppx_yojson_conv base64) (tags - (topics "to describe" your project))) - -; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project + (topics wasm plugin))) diff --git a/extism.opam b/extism.opam index ff58f32..8363b4b 100644 --- a/extism.opam +++ b/extism.opam @@ -1,13 +1,13 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" -synopsis: "A short synopsis" -description: "A longer description" -maintainer: ["Maintainer Name"] -authors: ["Author Name"] -license: "LICENSE" -tags: ["topics" "to describe" "your" "project"] +synopsis: "Extism bindings" +description: "Bindings to Extism, the universal plugin system" +maintainer: ["Extism Authors "] +authors: ["Extism Authors "] +license: "BSD-3" +tags: ["topics" "wasm" "plugin"] homepage: "https://github.com/extism/extism" -doc: "https://url/to/documentation" +doc: "https://github.com/extism/extism" bug-reports: "https://github.com/extism/extism/issues" depends: [ "ocaml" @@ -15,6 +15,7 @@ depends: [ "ctypes-foreign" "bigstringaf" "ppx_yojson_conv" + "base64" "odoc" {with-doc} ] build: [ diff --git a/haskell/CHANGELOG.md b/haskell/CHANGELOG.md new file mode 100644 index 0000000..e6392dd --- /dev/null +++ b/haskell/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for extism + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/haskell/Example.hs b/haskell/Example.hs new file mode 100644 index 0000000..9532671 --- /dev/null +++ b/haskell/Example.hs @@ -0,0 +1,17 @@ +module Main where + +import System.Exit (exitFailure, exitSuccess) +import qualified Data.ByteString as B +import Extism +import Extism.Manifest + +main = do + plugin <- Extism.registerManifest (manifest [wasmFile "../wasm/code.wasm"]) False + res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test") + case res of + Right (Error msg) -> do + _ <- putStrLn msg + exitFailure + Left bs -> do + _ <- putStrLn (Extism.fromByteString bs) + exitSuccess diff --git a/haskell/extism.cabal b/haskell/extism.cabal new file mode 100644 index 0000000..44e1e86 --- /dev/null +++ b/haskell/extism.cabal @@ -0,0 +1,47 @@ +cabal-version: 2.4 +name: extism +version: 0.0.1.0 + +-- A short (one-line) description of the package. +synopsis: Extism bindings + +-- A longer description of the package. +description: Bindings to Extism, the universal plugin system + +-- A URL where users can report bugs. +bug-reports: https://github.com/extism/extism + +-- The license under which the package is released. +license: BSD-3-Clause + +author: Extism authors +maintainer: oss@extism.org + +-- A copyright notice. +-- copyright: +category: Plugins +extra-source-files: CHANGELOG.md + +library + exposed-modules: Extism Extism.Manifest + + -- Modules included in this library but not exported. + other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + build-depends: + base ^>=4.16.1.0 + , bytestring + , base64-bytestring + , json + hs-source-dirs: src + default-language: Haskell2010 + extra-libraries: extism + extra-lib-dirs: /usr/local/lib + +Test-Suite extism-example + type: exitcode-stdio-1.0 + main-is: Example.hs + build-depends: base, extism, bytestring + default-language: Haskell2010 diff --git a/haskell/src/Extism.hs b/haskell/src/Extism.hs new file mode 100644 index 0000000..de9abc2 --- /dev/null +++ b/haskell/src/Extism.hs @@ -0,0 +1,91 @@ +{-# LANGUAGE ForeignFunctionInterface #-} + +module Extism (module Extism, module Extism.Manifest) where +import GHC.Int +import GHC.Word +import Foreign.C.Types +import Foreign.Ptr +import Foreign.C.String +import Control.Monad (void) +import Data.ByteString as B +import Data.ByteString.Internal (c2w, w2c) +import Data.ByteString.Unsafe (unsafeUseAsCString) +import Text.JSON (JSON, toJSObject, encode) +import Extism.Manifest (Manifest, toString) + +foreign import ccall unsafe "extism.h extism_plugin_register" extism_plugin_register :: Ptr Word8 -> Word64 -> CBool -> IO Int32 +foreign import ccall unsafe "extism.h extism_call" extism_call :: Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32 +foreign import ccall unsafe "extism.h extism_function_exists" extism_function_exists :: Int32 -> CString -> IO CBool +foreign import ccall unsafe "extism.h extism_error" extism_error :: Int32 -> IO CString +foreign import ccall unsafe "extism.h extism_output_length" extism_output_length :: Int32 -> IO Word64 +foreign import ccall unsafe "extism.h extism_output_get" extism_output_get :: Int32 -> Ptr Word8 -> Word64 -> IO () +foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool +foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Int32 -> Ptr Word8 -> Int64 -> IO CBool + +newtype Plugin = Plugin Int32 deriving Show +newtype Error = Error String deriving Show + +toByteString :: String -> ByteString +toByteString x = B.pack (Prelude.map c2w x) + +fromByteString :: ByteString -> String +fromByteString bs = Prelude.map w2c $ B.unpack bs + +register :: B.ByteString -> Bool -> IO Plugin +register wasm useWasi = + let length = fromIntegral (B.length wasm) in + let wasi = fromInteger (if useWasi then 1 else 0) in + do + p <- unsafeUseAsCString wasm (\s -> + extism_plugin_register (castPtr s) length wasi) + return $ Plugin p + +registerManifest :: Manifest -> Bool -> IO Plugin +registerManifest manifest useWasi = + let wasm = toByteString $ toString manifest in + register wasm useWasi + +isValid :: Plugin -> Bool +isValid (Plugin p) = p >= 0 + +setConfig :: Plugin -> [(String, String)] -> IO () +setConfig (Plugin plugin) x = + if plugin < 0 + then return () + else + let obj = toJSObject x in + let bs = toByteString (encode obj) in + let length = fromIntegral (B.length bs) in + unsafeUseAsCString bs (\s -> do + void $ extism_plugin_config plugin (castPtr s) length) + +setLogFile :: String -> String -> IO () +setLogFile filename level = + withCString filename (\f -> + withCString level (\l -> do + void $ extism_log_file f l)) + +functionExists :: Plugin -> String -> IO Bool +functionExists (Plugin plugin) name = do + b <- withCString name (extism_function_exists plugin) + if b == 1 then return True else return False + +call :: Plugin -> String -> B.ByteString -> IO (Either B.ByteString Error) +call (Plugin plugin) name input = + let length = fromIntegral (B.length input) in + do + rc <- withCString name (\name -> + unsafeUseAsCString input (\input -> + extism_call plugin name (castPtr input) length)) + err <- extism_error plugin + if err /= nullPtr + then do e <- peekCString err + return $ Right (Error e) + else if rc == 0 + then do + length <- extism_output_length plugin + let output = B.replicate (fromIntegral length) 0 + () <- unsafeUseAsCString output (\a -> + extism_output_get plugin (castPtr a) length) + return $ Left output + else return $ Right (Error "Call failed") diff --git a/haskell/src/Extism/Manifest.hs b/haskell/src/Extism/Manifest.hs new file mode 100644 index 0000000..ddb7928 --- /dev/null +++ b/haskell/src/Extism/Manifest.hs @@ -0,0 +1,168 @@ +module Extism.Manifest where + +import Text.JSON + ( + JSValue(JSNull, JSString, JSArray), + toJSString, showJSON, makeObj, encode + ) +import qualified Data.ByteString as B +import qualified Data.ByteString.Base64 as B64 +import qualified Data.ByteString.Char8 as BS (unpack) + +valueOrNull f Nothing = JSNull +valueOrNull f (Just x) = f x +makeString s = JSString (toJSString s) +stringOrNull = valueOrNull makeString +makeArray f x = JSArray [f a | a <- x] +filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)] +mapObj f x = makeObj (filterNulls [(a, f b) | (a, b) <- x]) +isNull JSNull = True +isNull _ = False + +newtype Memory = Memory + { + memoryMax :: Maybe Int + } + +class JSONValue a where + toJSONValue :: a -> JSValue + +instance JSONValue Memory where + toJSONValue x = + case memoryMax x of + Nothing -> makeObj [] + Just max -> makeObj [("max", showJSON max)] + +data HttpRequest = HttpRequest + { + url :: String + , header :: [(String, String)] + , method :: Maybe String + } + +requestObj x = + let meth = stringOrNull $ method x in + let h = mapObj makeString $ header x in + filterNulls [ + ("url", makeString $ url x), + ("header", h), + ("method", meth) + ] + +instance JSONValue HttpRequest where + toJSONValue x = + makeObj $ requestObj x + +data WasmFile = WasmFile + { + filePath :: String + , fileName :: Maybe String + , fileHash :: Maybe String + } + +instance JSONValue WasmFile where + toJSONValue x = + let path = makeString $ filePath x in + let name = stringOrNull $ fileName x in + let hash = stringOrNull $ fileHash x in + makeObj $ filterNulls [ + ("path", path), + ("name", name), + ("hash", hash) + ] + +data WasmCode = WasmCode + { + codeBytes :: B.ByteString + , codeName :: Maybe String + , codeHash :: Maybe String + } + + +instance JSONValue WasmCode where + toJSONValue x = + let bytes = makeString $ BS.unpack $ B64.encode $ codeBytes x in + let name = stringOrNull $ codeName x in + let hash = stringOrNull $ codeHash x in + makeObj $ filterNulls [ + ("data", bytes), + ("name", name), + ("hash", hash) + ] + +data WasmURL = WasmURL + { + req :: HttpRequest + , urlName :: Maybe String + , urlHash :: Maybe String + } + + +instance JSONValue WasmURL where + toJSONValue x = + let request = requestObj $ req x in + let name = stringOrNull $ urlName x in + let hash = stringOrNull $ urlHash x in + makeObj $ filterNulls $ ("name", name) : ("hash", hash) : request + +data Wasm = File WasmFile | Code WasmCode | URL WasmURL + +instance JSONValue Wasm where + toJSONValue x = + case x of + File f -> toJSONValue f + Code d -> toJSONValue d + URL u -> toJSONValue u + +wasmFile :: String -> Wasm +wasmFile path = + File WasmFile { filePath = path, fileName = Nothing, fileHash = Nothing} + +wasmURL :: String -> String -> Wasm +wasmURL method url = + let r = HttpRequest { url = url, header = [], method = Just method } in + URL WasmURL { req = r, urlName = Nothing, urlHash = Nothing } + +wasmCode :: B.ByteString -> Wasm +wasmCode code = + Code WasmCode { codeBytes = code, codeName = Nothing, codeHash = Nothing } + +withName :: Wasm -> String -> Wasm +withName (Code code) name = Code code { codeName = Just name } +withName (URL url) name = URL url { urlName = Just name } +withName (File f) name = File f { fileName = Just name } + + +withHash :: Wasm -> String -> Wasm +withHash (Code code) hash = Code code { codeHash = Just hash } +withHash (URL url) hash = URL url { urlHash = Just hash } +withHash (File f) hash = File f { fileHash = Just hash } + +data Manifest = Manifest + { + wasm :: [Wasm] + , memory :: Maybe Memory + , config :: [(String, String)] + } + +manifest :: [Wasm] -> Manifest +manifest wasm = Manifest {wasm = wasm, memory = Nothing, config = []} + +withConfig :: Manifest -> [(String, String)] -> Manifest +withConfig m config = + m { config = config } + +instance JSONValue Manifest where + toJSONValue x = + let w = makeArray toJSONValue $ wasm x in + let mem = valueOrNull toJSONValue $ memory x in + let c = mapObj makeString $ config x in + makeObj $ filterNulls [ + ("wasm", w), + ("memory", mem), + ("config", c) + ] + +toString :: Manifest -> String +toString manifest = + encode (toJSONValue manifest) diff --git a/haskell/stack.yaml b/haskell/stack.yaml new file mode 100644 index 0000000..6e21afc --- /dev/null +++ b/haskell/stack.yaml @@ -0,0 +1,69 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +- dist-newstyle/tmp/src-3036517/semialign-1.2.0.1 +- dist-newstyle/tmp/src-3036516/witherable-0.4.2 +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +# extra-deps: +# - acme-missiles-0.3 +# - git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# +# extra-deps: [] + +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.7" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor diff --git a/haskell/stack.yaml.lock b/haskell/stack.yaml.lock new file mode 100644 index 0000000..8a4b526 --- /dev/null +++ b/haskell/stack.yaml.lock @@ -0,0 +1,13 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + size: 632828 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml + sha256: 5b02c2ce430ac62843fb884126765628da2ca2280bb9de0c6635c723e32a9f6b + original: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2022/8/30.yaml diff --git a/manifest/Cargo.toml b/manifest/Cargo.toml index 51a8e06..54e6d53 100644 --- a/manifest/Cargo.toml +++ b/manifest/Cargo.toml @@ -10,4 +10,16 @@ description = "Extism plug-in manifest crate" [dependencies] serde = {version = "1", features=["derive"]} -base64 = "0.20.0-alpha" \ No newline at end of file +base64 = "0.20.0-alpha" +schemars = {version = "0.8", optional=true} + +[features] +json_schema = ["schemars"] + +[dev-dependencies] +serde_json = "1" + +[[example]] +name = "json_schema" +required-features = ["json_schema"] + diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index f08f43c..3164a56 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -15,8 +15,8 @@ crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -wasmtime = "0.39.1" -wasmtime-wasi = "0.39.1" +wasmtime = "0.40.0" +wasmtime-wasi = "0.40.0" anyhow = "1" serde = { version = "1", features=["derive"] } toml = "0.5" @@ -26,14 +26,13 @@ log = "0.4" log4rs = "1.1" ureq = {version = "2.5", optional=true} extism-manifest = { version = "0.0.1-alpha", path = "../manifest" } -pretty-hex = { version = "0.3", optional = true } +pretty-hex = { version = "0.3" } [features] default = ["http", "register-http", "register-filesystem"] register-http = ["ureq"] # enables wasm to be downloaded using http register-filesystem = [] # enables wasm to be loaded from disk http = ["ureq"] # enables extism_http_request -debug = ["pretty-hex"] [build-dependencies] cbindgen = "0.24" diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e464be3..64e07f7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,4 +16,4 @@ pub use plugin_ref::PluginRef; pub type Size = u64; pub type PluginIndex = i32; -pub(crate) use log::{debug, error, info}; +pub(crate) use log::{debug, error, info, trace}; diff --git a/runtime/src/memory.rs b/runtime/src/memory.rs index 23b2ea5..5bb0b0d 100644 --- a/runtime/src/memory.rs +++ b/runtime/src/memory.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use crate::*; -#[cfg(feature = "debug")] use pretty_hex::PrettyHex; /// Handles memory for plugins @@ -32,6 +31,7 @@ impl PluginMemory { } pub(crate) fn store_u8(&mut self, offs: usize, data: u8) -> Result<(), MemoryAccessError> { + trace!("store_u8: {data:x} at offset {offs}"); if offs >= self.size() { // This should raise MemoryAccessError let buf = &mut [0]; @@ -44,6 +44,7 @@ impl PluginMemory { /// Read from memory pub(crate) fn load_u8(&self, offs: usize) -> Result { + trace!("load_u8: offset {offs}"); if offs >= self.size() { // This should raise MemoryAccessError let buf = &mut [0]; @@ -54,6 +55,7 @@ impl PluginMemory { } pub(crate) fn store_u32(&mut self, offs: usize, data: u32) -> Result<(), MemoryAccessError> { + trace!("store_u32: {data:x} at offset {offs}"); let handle = MemoryBlock { offset: offs, length: 4, @@ -64,6 +66,7 @@ impl PluginMemory { /// Read from memory pub(crate) fn load_u32(&self, offs: usize) -> Result { + trace!("load_u32: offset {offs}"); let mut buf = [0; 4]; let handle = MemoryBlock { @@ -75,6 +78,7 @@ impl PluginMemory { } pub(crate) fn store_u64(&mut self, offs: usize, data: u64) -> Result<(), MemoryAccessError> { + trace!("store_u64: {data:x} at offset {offs}"); let handle = MemoryBlock { offset: offs, length: 8, @@ -84,6 +88,7 @@ impl PluginMemory { } pub(crate) fn load_u64(&self, offs: usize) -> Result { + trace!("load_u64: offset {offs}"); let mut buf = [0; 8]; let handle = MemoryBlock { offset: offs, @@ -222,11 +227,10 @@ impl PluginMemory { } } - #[cfg(feature = "debug")] pub fn dump(&self) { let data = self.memory.data(&self.store); - println!("{:?}", data[..self.position].hex_dump()); + trace!("{:?}", data[..self.position].hex_dump()); } /// Reset memory diff --git a/runtime/src/plugin.rs b/runtime/src/plugin.rs index 7135e2b..0d214ba 100644 --- a/runtime/src/plugin.rs +++ b/runtime/src/plugin.rs @@ -152,6 +152,7 @@ impl Plugin { /// Set `last_error` field pub fn set_error(&mut self, e: impl std::fmt::Debug) { + debug!("Set error: {:?}", e); let x = format!("{:?}", e).into_bytes(); let x = if x[0] == b'"' && x[x.len() - 1] == b'"' { x[1..x.len() - 1].to_vec() @@ -181,7 +182,6 @@ impl Plugin { internal.plugin = ptr; } - #[cfg(feature = "debug")] pub fn dump_memory(&self) { self.memory.dump(); } diff --git a/runtime/src/plugin_ref.rs b/runtime/src/plugin_ref.rs index 50140df..06ff4a2 100644 --- a/runtime/src/plugin_ref.rs +++ b/runtime/src/plugin_ref.rs @@ -2,12 +2,17 @@ use crate::*; // PluginRef is used to access a plugin from the global plugin registry pub struct PluginRef<'a> { + pub id: PluginIndex, pub plugins: std::sync::MutexGuard<'a, Vec>, plugin: *mut Plugin, } impl<'a> PluginRef<'a> { pub fn init(mut self) -> Self { + trace!( + "Resetting memory and clearing error message for plugin {}", + self.id, + ); // Initialize self.as_mut().clear_error(); self.as_mut().memory.reset(); @@ -17,20 +22,26 @@ impl<'a> PluginRef<'a> { /// # Safety /// /// This function is used to access the static `PLUGINS` registry - pub unsafe fn new(plugin: PluginIndex) -> Self { + pub unsafe fn new(plugin_id: PluginIndex) -> Self { let mut plugins = match PLUGINS.lock() { Ok(p) => p, Err(e) => e.into_inner(), }; - if plugin < 0 || plugin as usize >= plugins.len() { + trace!("Loading plugin {plugin_id}"); + + if plugin_id < 0 || plugin_id as usize >= plugins.len() { drop(plugins); - panic!("Invalid PluginIndex {plugin}"); + panic!("Invalid PluginIndex {plugin_id}"); } - let plugin = plugins.get_unchecked_mut(plugin as usize) as *mut _; + let plugin = plugins.get_unchecked_mut(plugin_id as usize) as *mut _; - PluginRef { plugins, plugin } + PluginRef { + id: plugin_id, + plugins, + plugin, + } } } @@ -48,6 +59,7 @@ impl<'a> AsMut for PluginRef<'a> { impl<'a> Drop for PluginRef<'a> { fn drop(&mut self) { + trace!("Dropping plugin {}", self.id); // Cleanup? } } diff --git a/runtime/src/sdk.rs b/runtime/src/sdk.rs index 23f4626..3ddf615 100644 --- a/runtime/src/sdk.rs +++ b/runtime/src/sdk.rs @@ -11,6 +11,10 @@ pub unsafe extern "C" fn extism_plugin_register( wasm_size: Size, with_wasi: bool, ) -> PluginIndex { + trace!( + "Call to extism_plugin_register with wasm pointer {:?}", + wasm + ); let data = std::slice::from_raw_parts(wasm, wasm_size as usize); let plugin = match Plugin::new(data, with_wasi) { Ok(x) => x, @@ -40,6 +44,12 @@ pub unsafe extern "C" fn extism_plugin_config( ) -> bool { let mut plugin = PluginRef::new(plugin); + trace!( + "Call to extism_plugin_config for {} with json pointer {:?}", + plugin.id, + json + ); + let data = std::slice::from_raw_parts(json, json_size as usize); let json: std::collections::BTreeMap = match serde_json::from_slice(data) { Ok(x) => x, @@ -53,6 +63,7 @@ pub unsafe extern "C" fn extism_plugin_config( let wasi = &mut plugin.memory.store.data_mut().wasi; let config = &mut plugin.manifest.as_mut().config; for (k, v) in json.into_iter() { + trace!("Config, adding {k}"); let _ = wasi.push_env(&k, &v); config.insert(k, v); } @@ -107,7 +118,6 @@ pub unsafe extern "C" fn extism_call( Err(e) => return plugin.error(e.context("Unable to allocate bytes"), -1), }; - #[cfg(feature = "debug")] plugin.dump_memory(); // Always needs to be called before `func.call()` @@ -118,14 +128,12 @@ pub unsafe extern "C" fn extism_call( match func.call(&mut plugin.memory.store, &[], results.as_mut_slice()) { Ok(r) => r, Err(e) => { - #[cfg(feature = "debug")] plugin.dump_memory(); error!("Call: {e:?}"); return plugin.error(e.context("Call failed"), -1); } }; - #[cfg(feature = "debug")] plugin.dump_memory(); // Return result to caller @@ -134,22 +142,31 @@ pub unsafe extern "C" fn extism_call( #[no_mangle] pub unsafe extern "C" fn extism_error(plugin: PluginIndex) -> *const c_char { + trace!("Call to extism_error for plugin {plugin}"); let plugin = PluginRef::new(plugin); match &plugin.as_ref().last_error { Some(e) => e.as_ptr() as *const _, - None => std::ptr::null(), + None => { + trace!("Error is NULL"); + std::ptr::null() + } } } #[no_mangle] pub unsafe extern "C" fn extism_output_length(plugin: PluginIndex) -> Size { + trace!("Call to extism_output_length for plugin {plugin}"); let plugin = PluginRef::new(plugin); - plugin.as_ref().memory.store.data().output_length as Size + let len = plugin.as_ref().memory.store.data().output_length as Size; + trace!("Output length: {len}"); + len } #[no_mangle] pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex, buf: *mut u8, len: Size) { + trace!("Call to extism_output_get for plugin {plugin}, length {len}"); + let plugin = PluginRef::new(plugin); let data = plugin.as_ref().memory.store.data(); @@ -170,16 +187,21 @@ pub unsafe extern "C" fn extism_log_file( log_level: *const c_char, ) -> bool { use log::LevelFilter; + use log4rs::append::console::ConsoleAppender; use log4rs::append::file::FileAppender; - use log4rs::config::{Appender, Config, Root}; + use log4rs::config::{Appender, Config, Logger, Root}; use log4rs::encode::pattern::PatternEncoder; - let file = std::ffi::CStr::from_ptr(filename); - let file = match file.to_str() { - Ok(x) => x, - Err(_) => { - return false; + let file = if !filename.is_null() { + let file = std::ffi::CStr::from_ptr(filename); + match file.to_str() { + Ok(x) => x, + Err(_) => { + return false; + } } + } else { + "-" }; let level = if log_level.is_null() { @@ -201,28 +223,34 @@ pub unsafe extern "C" fn extism_log_file( } }; - let logfile = match FileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{d}: {l} - {m}\n"))) - .build(file) - { - Ok(x) => x, - Err(e) => { - error!("Unable to set up log encoder: {e:?}"); - return false; + let encoder = Box::new(PatternEncoder::new("{t} {l} {d} (({f}:{L})) - {m}\n")); + + let logfile: Box = if file == "-" { + let console = ConsoleAppender::builder().encoder(encoder); + Box::new(console.build()) + } else { + match FileAppender::builder().encoder(encoder).build(file) { + Ok(x) => Box::new(x), + Err(e) => { + error!("Unable to set up log encoder: {e:?}"); + return false; + } } }; let config = match Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .build(Root::builder().appender("logfile").build(level)) + .appender(Appender::builder().build("logfile", logfile)) + .logger(Logger::builder().appender("logfile").build("extism", level)) + .build(Root::builder().build(LevelFilter::Off)) { Ok(x) => x, - Err(e) => { - error!("Unable to configure log file: {e:?}"); + Err(_) => { return false; } }; - log4rs::init_config(config).expect("Log initialization failed"); + if log4rs::init_config(config).is_err() { + return false; + } true }