diff options
-rw-r--r-- | Setup.hs | 2 | ||||
-rw-r--r-- | harness/CMakeLists.txt | 20 | ||||
-rw-r--r-- | harness/include/plugin.h | 64 | ||||
-rw-r--r-- | harness/src/main.c | 93 | ||||
-rw-r--r-- | harness/src/plugin.c | 86 | ||||
-rw-r--r-- | harness/tools/genbuild.pl | 43 | ||||
-rw-r--r-- | harness/tools/genintf.pl | 26 | ||||
-rw-r--r-- | package.yaml | 3 | ||||
-rw-r--r-- | src/Wetterhorn/Core.hs | 26 | ||||
-rw-r--r-- | src/Wetterhorn/FFI.hs | 55 | ||||
-rw-r--r-- | src/harness_adapter.c | 21 |
11 files changed, 340 insertions, 99 deletions
@@ -11,6 +11,8 @@ import Text.Printf import Data.Maybe (fromJust) main = do + putStrLn "Running Setup.hs" + defaultMainWithHooks $ simpleUserHooks { preConf = \_ conf -> do diff --git a/harness/CMakeLists.txt b/harness/CMakeLists.txt index b38cb77..a254afd 100644 --- a/harness/CMakeLists.txt +++ b/harness/CMakeLists.txt @@ -6,10 +6,28 @@ project ( include_directories(include/) +set(PLUGIN_INTF ${CMAKE_BINARY_DIR}/plugin_interface.h) +add_custom_command( + OUTPUT ${PLUGIN_INTF} + COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genintf.pl < + ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_INTF} + DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h + DEPENDS ${PROJECT_SOURCE_DIR}/tools/genintf.pl +) + +set(PLUGIN_LOAD ${CMAKE_BINARY_DIR}/gen_plugin_load.c) +add_custom_command( + OUTPUT ${PLUGIN_LOAD} + COMMAND perl ${PROJECT_SOURCE_DIR}/tools/genbuild.pl < + ${PROJECT_SOURCE_DIR}/include/plugin.h > ${PLUGIN_LOAD} + DEPENDS ${PROJECT_SOURCE_DIR}/include/plugin.h + DEPENDS ${PROJECT_SOURCE_DIR}/tools/genbuild.pl +) + file (GLOB_RECURSE SOURCES src/*.c) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -add_executable (wtr_harness ${SOURCES}) +add_executable (wtr_harness ${SOURCES} ${PLUGIN_LOAD} ${PLUGIN_INTF}) target_link_libraries(wtr_harness dl) diff --git a/harness/include/plugin.h b/harness/include/plugin.h index 70ca198..db15401 100644 --- a/harness/include/plugin.h +++ b/harness/include/plugin.h @@ -2,16 +2,20 @@ #define _PLUGIN_H_ #include <dlfcn.h> -#include <stdint.h> #include <pthread.h> +#include <stdint.h> -#define PRIVATE(a) a +/* + * Marker macro to define what functions should be exported. This generates the + * interface which the plugin needs to implement. + */ +#define EXPORT(a) a -typedef void* dlhandle_t; +typedef void *dlhandle_t; /* Opaque state for a plugin. Not to be touched by the harness (not that it * really can be.) */ -typedef void* opqst_t; +typedef void *opqst_t; /* * Structure for the plugin. @@ -28,25 +32,34 @@ typedef struct PLUGIN { * * It's guaranteed that this state is used linearly, meaning the harness gives * up all ownership to it once passed into a handler. */ - PRIVATE(opqst_t state); + opqst_t state; /* This plugin's lock. This avoids potential issues with multiple threads * trying to change the opaque state at once which can lead to undesireable * outcomes. */ - PRIVATE(pthread_mutex_t lock); + pthread_mutex_t lock; /* The handle to the shared library. */ - PRIVATE(dlhandle_t library_handle); + dlhandle_t library_handle; /* Pointer to the plugin name. This is in the shared library and a * null-terminated string. If the library does not have a plugin name, this * will be NULL. */ - PRIVATE(const char* plugin_name); + const char *plugin_name; + + /** + * This is the first thing called on the plugin when loading it. This is + * different from plugin_load in that it will not be called on a hot-reload. + * + * This is used as a hack to get around GHC's limitation where it can't + * start up the RTS once it has been shutdown. + */ + EXPORT(void (*plugin_metaload)(int argc, char** argv)); /** Intializes the plugin with the given argc/argv. This is the first thing * called on the plugin and is called immediately after the library is loaded. */ - void (*plugin_load)(int argc, char** argv); + EXPORT(void (*plugin_load)(int argc, char **argv)); /* Start the plugin with the marshalled state from the previous plugin. * @@ -57,13 +70,13 @@ typedef struct PLUGIN { * hot-reloading can produce incompatibilities between the old state and the * new state, and this should not cause a failure. */ - opqst_t (*plugin_hot_start)(uint8_t* mashalled_state, uint32_t n); + EXPORT(opqst_t (*plugin_hot_start)(uint8_t *mashalled_state, uint32_t n)); /* * Starts the plugin without a marshalled state. Happens during the first boot * when there is not state. */ - opqst_t (*plugin_cold_start)(); + EXPORT(opqst_t (*plugin_cold_start)()); /* * Marshals the state to a bytestring. The returned pointer should be malloc'd @@ -72,30 +85,29 @@ typedef struct PLUGIN { * This is usually called in preparation for a teardown followed by a * hot-start. */ - uint8_t* (*plugin_marshal_state)(opqst_t st, uint32_t* szout); + EXPORT(uint8_t *(*plugin_marshal_state)(opqst_t st, uint32_t *szout)); /* * Teardown the plugin in preperation for the library's imminent unloading. */ - void (*plugin_teardown)(opqst_t); - - /* This anonymous structure contains event handlers. It is wrapped in this - * structure to make it distinct from the management functions above. - * - * The handlers are all optional and if any of them are NULL, the opaque state - * remains untouched. - */ - struct { - /* Right now, empty */ - } handlers; + EXPORT(void (*plugin_teardown)(opqst_t)); } plugin_t; /** Loads a plugin from the dynamic library handle. Returns a non-zero error * code on error. */ -int load_plugin_from_dlhandle(plugin_t* out, dlhandle_t library); +int load_plugin_from_dl(dlhandle_t library, plugin_t *out); + +/* Reloads the plugin. This tears down the existing plugin, marshals the state + * for it and reloads it. + * + * This function will call dlclose on the plugin's library handle if it is not + * the same as 'library'. + */ +int plugin_hot_reload(int argc, char **argv, dlhandle_t library, + plugin_t *plugin); -/* Like the above, but load the library from the given file. */ -int load_plugin_from_file(plugin_t* out, const char* dlfilename); +/* Reads a plugin from a filename. */ +int read_plugin_from_file(const char *filename, plugin_t *plugin); #endif /* _PLUGIN_H_ */ diff --git a/harness/src/main.c b/harness/src/main.c index 1580bd6..648473f 100644 --- a/harness/src/main.c +++ b/harness/src/main.c @@ -1,69 +1,72 @@ -#include <stdio.h> +#include "plugin.h" +#include <ctype.h> #include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> -typedef void* dllib_t; +typedef void *dllib_t; -typedef void* opqst_t; +typedef void *opqst_t; -dllib_t open_library(const char* library, int* err) -{ - dllib_t lib = dlopen(library, RTLD_LAZY); +void shx(uint8_t *state, uint32_t sz) { + uint32_t i = 0; + while (i < sz) { + for (int j = 0; j < 16; ++j) { + if (i < sz) { + printf("%02x ", (unsigned int)state[i]); + } else { + printf(" "); + } + ++i; + } - if (!lib) { - fprintf(stderr, "Error opening shared library: %s\n", dlerror()); - *err = 1; - } - *err = 0; - return lib; -} + i -= 16; + + printf(" "); -void* getsym(dllib_t handle, char* sym, int* err) -{ - void* ret = dlsym(handle, sym); - if (!ret) { - fprintf(stderr, "Unable to read symbol '%s'\n", sym); - *err |= 1; - return NULL; + for (int j = 0; j < 16; ++j) { + if (i < sz) { + if (isprint(state[i]) && !isspace(state[i])) { + printf("%c", state[i]); + } else { + printf("."); + } + } else { + printf(" "); + } + ++i; + } + printf("\n"); } - return ret; } -void use_library(int argc, char** argv, dllib_t lib, int* err) -{ - *err = 0; +void run_plugin(int argc, char **argv, plugin_t *plugin) { + fprintf(stderr, "Running plugin: %s\n", plugin->plugin_name); - opqst_t (*init)(int* argc, char*** argv) = getsym(lib, "plugin_init", err); - void (*teardown)() = getsym(lib, "plugin_teardown", err); - opqst_t (*sym)(opqst_t) = getsym(lib, "handle_thing", err); + uint32_t sz; - if (*err) { - fprintf(stderr, "Unable to find symbol call_in.\n"); - return; - } + plugin_hot_reload(argc, argv, plugin->library_handle, plugin); - opqst_t st = init(&argc, &argv); - for (int i = 0; i < 100000; ++ i) { - st = sym(st); - printf("stable_ptr: %p\n", st); - } - teardown(); + return; } -int main(int argc, char** argv) -{ +int main(int argc, char **argv) { if (!argv[0] && !argv[1]) { fprintf(stderr, "Missing argument.\n"); return 1; } - int err = 0; - dllib_t lib = open_library(argv[1], &err); - - if (err) { - return err; + plugin_t plugin; + if (read_plugin_from_file(argv[1], &plugin)) { + fprintf(stderr, "Exiting due to other failures.\n"); + return 1; } + plugin.plugin_metaload(argc, argv); + plugin.plugin_load(argc, argv); + plugin.state = plugin.plugin_cold_start(); + + run_plugin(argc, argv, &plugin); - use_library(argc, argv, lib, &err); return 0; } diff --git a/harness/src/plugin.c b/harness/src/plugin.c new file mode 100644 index 0000000..9b71c49 --- /dev/null +++ b/harness/src/plugin.c @@ -0,0 +1,86 @@ +#include "plugin.h" + +#include <ctype.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> + +static void shx(uint8_t *state, uint32_t sz) { + uint32_t i = 0; + while (i < sz) { + for (int j = 0; j < 16; ++j) { + if (i < sz) { + printf("%02x ", (unsigned int)state[i]); + } else { + printf(" "); + } + ++i; + } + + i -= 16; + + printf(" "); + + for (int j = 0; j < 16; ++j) { + if (i < sz) { + if (isprint(state[i]) && !isspace(state[i])) { + printf("%c", state[i]); + } else { + printf("."); + } + } else { + printf(" "); + } + ++i; + } + printf("\n"); + } +} + +int read_plugin_from_file(const char *filename, plugin_t *plugin) { + dlhandle_t lib = dlopen(filename, RTLD_LAZY); + + if (!lib) { + fprintf(stderr, "Failed to open library: %s: %s\n", filename, dlerror()); + return 1; + } + + return load_plugin_from_dl(lib, plugin); +} + +int plugin_hot_reload(int argc, char **argv, dlhandle_t library, + plugin_t *plugin) { + int ec = 0; + uint32_t sz = 0; + uint8_t *marshalled_state = NULL; + + printf("Hot Reloading %s\n", plugin->plugin_name); + pthread_mutex_lock(&plugin->lock); + + printf("Marshalling state ...\n"); + marshalled_state = plugin->plugin_marshal_state(plugin->state, &sz); + + printf("Calling teardown ...\n"); + plugin->plugin_teardown(plugin->state); + + printf("State Marshalled:\n"); + shx(marshalled_state, sz); + + if (library != plugin->library_handle) { + dlclose(plugin->library_handle); + } + + if ((ec = load_plugin_from_dl(library, plugin))) { + goto fail; + } + + printf("Loading plugin ...\n"); + plugin->plugin_load(argc, argv); + printf("Hot starting plugin ...\n"); + plugin->state = plugin->plugin_hot_start(marshalled_state, sz); + +fail: + free(marshalled_state); + pthread_mutex_unlock(&plugin->lock); + return ec; +} diff --git a/harness/tools/genbuild.pl b/harness/tools/genbuild.pl new file mode 100644 index 0000000..b17ab6a --- /dev/null +++ b/harness/tools/genbuild.pl @@ -0,0 +1,43 @@ +#!/usr/bin/env perl + +$comment=""; + +print "#include <stdio.h>\n"; +print "#include <dlfcn.h>\n"; +print "#include <pthread.h>\n"; +print "#include \"plugin.h\"\n\n"; + +print "int load_plugin_from_dl(dlhandle_t dl, plugin_t* plug)\n"; +print "{\n"; +print " void* ptr;\n"; +print " int ret = 0;\n"; +print "\n"; +print " const char** name = dlsym(dl, \"plugin_name\");\n"; +print " if (name) {\n"; +print " plug->plugin_name = *name;\n"; +print " } else {\n"; +print " plug->plugin_name = NULL;\n"; +print " }\n"; +print " plug->state = NULL;\n"; +print " plug->library_handle = dl;\n"; +print "\n"; +while (<>) { + if (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) { + print "\n"; + print " ptr = dlsym(dl, \"$2\");\n"; + print " if (!ptr) {\n"; + print " fprintf(stderr, \"Plugin missing %s\\n\", \"$2\");\n"; + print " ret |= 1;\n"; + print " }\n"; + print " plug->$2 = ptr;\n"; + $comment=""; + } +} +print "\n if (!ret) {\n"; +print " if(pthread_mutex_init(&plug->lock, NULL)) {\n"; +print " fprintf(stderr, \"Failed to initalize plugin lock\");\n"; +print " ret |= 1;\n"; +print " }\n"; +print " }\n"; +print "\n return ret;\n"; +print "}\n"; diff --git a/harness/tools/genintf.pl b/harness/tools/genintf.pl new file mode 100644 index 0000000..6e9d477 --- /dev/null +++ b/harness/tools/genintf.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl + +$comment=""; + +print "#ifndef _PLUG_INTF\n"; +print "#define _PLUG_INTF\n"; +print "\n#include <stdint.h>\n"; +print "\ntypedef void* opqst_t;\n"; +while (<>) { + if (/^\s*\/\*/) { + $_ =~ s/^\s*//; + $comment="$_"; + } + + if (/^\s*\*/) { + $_ =~ s/^\s*/ /; + $comment="$comment$_"; + } + + if (/^\s*EXPORT\(\s*((?:\w|\s*\*\s*)+)\s*\(\*(\w+)\)\s*\((.*)\)\);/) { + print "$comment"; + print "$1 $2($3);\n\n"; + $comment=""; + } +} +print "#endif /* _PLUG_INTF */\n"; diff --git a/package.yaml b/package.yaml index 73d5e50..0dbf9a4 100644 --- a/package.yaml +++ b/package.yaml @@ -31,6 +31,7 @@ custom-setup: dependencies: - base >= 4.7 && < 5 - mtl +- bytestring ghc-options: - -Wall @@ -42,6 +43,7 @@ ghc-options: - -Wmissing-home-modules - -Wpartial-fields - -Wredundant-constraints +- -XTupleSections - -fPIC executables: @@ -56,6 +58,7 @@ executables: cc-options: - -g3 - -shared + - -Iharness/build/ tests: wetterhorn-test: diff --git a/src/Wetterhorn/Core.hs b/src/Wetterhorn/Core.hs index 5ce93f0..e04cb49 100644 --- a/src/Wetterhorn/Core.hs +++ b/src/Wetterhorn/Core.hs @@ -11,14 +11,18 @@ module Wetterhorn.Core initWetterhorn, wio, incrementState, + readWState, ) where -import Control.Applicative import Control.Arrow (first) -import Foreign (StablePtr, deRefStablePtr, freeStablePtr, newStablePtr) +import Control.Exception +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as CH +import Foreign (StablePtr, newStablePtr) -type Wetterhorn = (StablePtr (WConfig, WState)) +-- This is this opaque state presented to the harness. +type Wetterhorn = StablePtr (WConfig, WState) initWetterhorn :: WConfig -> IO Wetterhorn initWetterhorn conf = do @@ -28,20 +32,20 @@ data WState = WState { someString :: String, integer :: Int } + deriving (Show, Read) data WConfig = WConfig { someConfig :: String, handleSomething :: W () } -foreign export ccall handle_thing :: StablePtr (WConfig, WState) -> IO (StablePtr (WConfig, WState)) - -handle_thing :: StablePtr (WConfig, WState) -> IO (StablePtr (WConfig, WState)) -handle_thing ptr = do - (conf, st) <- deRefStablePtr ptr - (_, st') <- runW (handleSomething conf) (conf, st) - newStablePtr (conf, st') - <* freeStablePtr ptr +readWState :: ByteString -> IO WState +readWState bs = + catch + (return $ read (CH.unpack bs)) + ( \e -> + let _ = (e :: SomeException) in return (WState "" 0) + ) newtype W a = W ((WConfig, WState) -> IO (a, WState)) diff --git a/src/Wetterhorn/FFI.hs b/src/Wetterhorn/FFI.hs new file mode 100644 index 0000000..c27c129 --- /dev/null +++ b/src/Wetterhorn/FFI.hs @@ -0,0 +1,55 @@ +-- | This module does not export anything. It exists simply to provide C-symbols +-- for the plugin. +module Wetterhorn.FFI() where + +import Control.Monad (forM_) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as CH +import Foreign + ( Ptr, + Storable (poke, pokeByteOff), + Word32, + Word8, + deRefStablePtr, + freeStablePtr, + mallocBytes, + newStablePtr, + ) +import Foreign.C (CChar) +import Wetterhorn.Core + +foreign import ccall wetterhorn :: IO Wetterhorn + +-- | This function is the implementation of the "hotstart" mechanism. It gives a +-- pointer to the previously marshalled state and the length of that array and +-- this function returns a Wetterhorn instance. +foreign export ccall "plugin_hot_start" + pluginHotStart :: + Ptr CChar -> Word32 -> IO Wetterhorn + +pluginHotStart :: Ptr CChar -> Word32 -> IO Wetterhorn +pluginHotStart chars len = do + bs <- BS.packCStringLen (chars, fromIntegral len) + wtrPtr <- wetterhorn + (conf, _) <- deRefStablePtr wtrPtr + freeStablePtr wtrPtr + newStablePtr . (conf,) =<< readWState bs + +foreign export ccall "plugin_cold_start" + pluginColdStart :: IO Wetterhorn + +pluginColdStart :: IO Wetterhorn +pluginColdStart = wetterhorn + +foreign export ccall "plugin_marshal_state" + pluginMarshalState :: Wetterhorn -> Ptr Word32 -> IO (Ptr Word8) + +pluginMarshalState :: Wetterhorn -> Ptr Word32 -> IO (Ptr Word8) +pluginMarshalState stblptr outlen = do + (_, st) <- deRefStablePtr stblptr + let bs = CH.pack (show st) + ret <- mallocBytes (BS.length bs) + poke outlen (fromIntegral $ BS.length bs) + forM_ (zip [0 ..] (BS.unpack bs)) $ \(off, w8) -> do + pokeByteOff ret off w8 + return ret diff --git a/src/harness_adapter.c b/src/harness_adapter.c index dbae57a..15a2269 100644 --- a/src/harness_adapter.c +++ b/src/harness_adapter.c @@ -1,19 +1,8 @@ -#include <stdio.h> #include "HsFFI.h" +#include "plugin_interface.h" -typedef void* opst_t; +const char *plugin_name = "Wetterhorn"; -extern opst_t wetterhorn(); - -opst_t plugin_init(int* argc, char*** argv) -{ - hs_init(argc, argv); - return wetterhorn(); -} - -void plugin_teardown() -{ - hs_exit(); -} - -const char* plugin_name = "Wetterhorn"; +void plugin_load(int argc, char **argv) { hs_init(&argc, &argv); } +void plugin_metaload(int argc, char **argv) { hs_init(&argc, &argv); } +void plugin_teardown(opqst_t st) { hs_exit(); } |