diff options
Diffstat (limited to 'plugin/src')
| -rw-r--r-- | plugin/src/plugin.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/plugin/src/plugin.c b/plugin/src/plugin.c new file mode 100644 index 0000000..f65132f --- /dev/null +++ b/plugin/src/plugin.c | |||
| @@ -0,0 +1,250 @@ | |||
| 1 | #include "plugin.h" | ||
| 2 | |||
| 3 | #include "cstring.h" | ||
| 4 | #include "list.h" | ||
| 5 | #include "log/log.h" // TODO: Use the error library instead. Move it to clib. | ||
| 6 | |||
| 7 | #include <assert.h> | ||
| 8 | #include <stdbool.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <string.h> | ||
| 11 | |||
| 12 | #include <errno.h> | ||
| 13 | #include <linux/limits.h> | ||
| 14 | #include <poll.h> | ||
| 15 | #include <sys/inotify.h> | ||
| 16 | #include <unistd.h> | ||
| 17 | |||
| 18 | // Watching for IN_CREATE leads the plugin engine to try to reload a plugin's | ||
| 19 | // shared library before the compiler has fully written to it. | ||
| 20 | static const int WATCH_MASK = IN_CLOSE_WRITE; | ||
| 21 | |||
| 22 | typedef struct Plugin { | ||
| 23 | void* handle; // First member so that Plugin can be cast to handle. | ||
| 24 | void* state; // Plugin's internal state. | ||
| 25 | bool reloaded; // Whether the plugin has been reloaded state needs to be | ||
| 26 | // re-created. | ||
| 27 | PluginEngine* eng; // So that the public API can do stuff with just a Plugin*. | ||
| 28 | mstring filename; | ||
| 29 | } Plugin; | ||
| 30 | |||
| 31 | DEF_LIST(Plugin); | ||
| 32 | |||
| 33 | typedef struct PluginEngine { | ||
| 34 | int inotify_instance; | ||
| 35 | int dir_watch; // inotify watch on the plugins directory. | ||
| 36 | Plugin_list plugins; | ||
| 37 | mstring plugins_dir; | ||
| 38 | } PluginEngine; | ||
| 39 | |||
| 40 | // ----------------------------------------------------------------------------- | ||
| 41 | // Plugin. | ||
| 42 | // ----------------------------------------------------------------------------- | ||
| 43 | |||
| 44 | static mstring plugin_lib_name(const Plugin* plugin) { | ||
| 45 | return mstring_concat( | ||
| 46 | mstring_make("lib"), mstring_concat_cstr(plugin->filename, ".so")); | ||
| 47 | } | ||
| 48 | |||
| 49 | static mstring plugin_lib_path(const Plugin* plugin) { | ||
| 50 | return mstring_concat(plugin->eng->plugins_dir, plugin_lib_name(plugin)); | ||
| 51 | } | ||
| 52 | |||
| 53 | static bool load_library(Plugin* plugin) { | ||
| 54 | assert(plugin); | ||
| 55 | assert(plugin->eng); | ||
| 56 | |||
| 57 | // Handle reloading a previously-loaded library. | ||
| 58 | if (plugin->handle) { | ||
| 59 | dlclose(plugin->handle); | ||
| 60 | plugin->handle = 0; | ||
| 61 | } | ||
| 62 | |||
| 63 | const mstring lib = plugin_lib_path(plugin); | ||
| 64 | |||
| 65 | // If the plugin fails to load, make sure to keep the plugin's old handle to | ||
| 66 | // handle the error gracefully. This handles reload failures, specifically. | ||
| 67 | void* handle = 0; | ||
| 68 | if ((handle = dlopen(mstring_cstr(&lib), RTLD_NOW))) { | ||
| 69 | LOGD("Plugin [%s] loaded successfully", mstring_cstr(&plugin->filename)); | ||
| 70 | plugin->handle = handle; | ||
| 71 | return true; | ||
| 72 | } else { | ||
| 73 | LOGE("dlopen() failed: %s", dlerror()); | ||
| 74 | } | ||
| 75 | |||
| 76 | return false; | ||
| 77 | } | ||
| 78 | |||
| 79 | static void destroy_plugin(Plugin* plugin) { | ||
| 80 | if (plugin) { | ||
| 81 | if (plugin->handle) { | ||
| 82 | dlclose(plugin->handle); | ||
| 83 | plugin->handle = 0; | ||
| 84 | } | ||
| 85 | if (plugin->state) { | ||
| 86 | free(plugin->state); | ||
| 87 | plugin->state = 0; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | Plugin* load_plugin(PluginEngine* eng, const char* filename) { | ||
| 93 | assert(eng); | ||
| 94 | assert(filename); | ||
| 95 | |||
| 96 | Plugin plugin = (Plugin){.eng = eng, .filename = mstring_make(filename)}; | ||
| 97 | |||
| 98 | if (!load_library(&plugin)) { | ||
| 99 | return 0; | ||
| 100 | } | ||
| 101 | |||
| 102 | list_push(eng->plugins, plugin); | ||
| 103 | return &eng->plugins.head->val; | ||
| 104 | } | ||
| 105 | |||
| 106 | void delete_plugin(Plugin** pPlugin) { | ||
| 107 | assert(pPlugin); | ||
| 108 | Plugin* plugin = *pPlugin; | ||
| 109 | if (plugin) { | ||
| 110 | assert(plugin->eng); | ||
| 111 | destroy_plugin(plugin); | ||
| 112 | list_remove_ptr(plugin->eng->plugins, plugin); | ||
| 113 | *pPlugin = 0; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | static void delete_plugin_state(Plugin* plugin) { | ||
| 118 | if (plugin->state) { | ||
| 119 | free(plugin->state); | ||
| 120 | plugin->state = 0; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | void set_plugin_state(Plugin* plugin, void* state) { | ||
| 125 | assert(plugin); | ||
| 126 | delete_plugin_state(plugin); | ||
| 127 | plugin->state = state; | ||
| 128 | } | ||
| 129 | |||
| 130 | void* get_plugin_state(Plugin* plugin) { | ||
| 131 | assert(plugin); | ||
| 132 | return plugin->state; | ||
| 133 | } | ||
| 134 | |||
| 135 | bool plugin_reloaded(Plugin* plugin) { | ||
| 136 | assert(plugin); | ||
| 137 | const bool reloaded = plugin->reloaded; | ||
| 138 | plugin->reloaded = false; | ||
| 139 | return reloaded; | ||
| 140 | } | ||
| 141 | |||
| 142 | // ----------------------------------------------------------------------------- | ||
| 143 | // Plugin Engine. | ||
| 144 | // ----------------------------------------------------------------------------- | ||
| 145 | |||
| 146 | PluginEngine* new_plugin_engine(const PluginEngineDesc* desc) { | ||
| 147 | PluginEngine* eng = 0; | ||
| 148 | |||
| 149 | if (!(eng = calloc(1, sizeof(PluginEngine)))) { | ||
| 150 | goto cleanup; | ||
| 151 | } | ||
| 152 | eng->plugins = make_list(Plugin); | ||
| 153 | eng->plugins_dir = mstring_concat_cstr(mstring_make(desc->plugins_dir), "/"); | ||
| 154 | |||
| 155 | LOGD("Watch plugins directory: %s", mstring_cstr(&eng->plugins_dir)); | ||
| 156 | |||
| 157 | if ((eng->inotify_instance = inotify_init()) == -1) { | ||
| 158 | LOGE("Failed to create inotify instance"); | ||
| 159 | goto cleanup; | ||
| 160 | } | ||
| 161 | if ((eng->dir_watch = inotify_add_watch( | ||
| 162 | eng->inotify_instance, mstring_cstr(&eng->plugins_dir), | ||
| 163 | WATCH_MASK)) == -1) { | ||
| 164 | LOGE("Failed to watch directory: %s", mstring_cstr(&eng->plugins_dir)); | ||
| 165 | goto cleanup; | ||
| 166 | } | ||
| 167 | |||
| 168 | return eng; | ||
| 169 | |||
| 170 | cleanup: | ||
| 171 | delete_plugin_engine(&eng); | ||
| 172 | return 0; | ||
| 173 | } | ||
| 174 | |||
| 175 | void delete_plugin_engine(PluginEngine** pEng) { | ||
| 176 | assert(pEng); | ||
| 177 | PluginEngine* eng = *pEng; | ||
| 178 | if (eng) { | ||
| 179 | list_foreach_mut(eng->plugins, { destroy_plugin(value); }); | ||
| 180 | del_list(eng->plugins); | ||
| 181 | if (eng->dir_watch != -1) { | ||
| 182 | inotify_rm_watch(eng->dir_watch, eng->inotify_instance); | ||
| 183 | close(eng->dir_watch); | ||
| 184 | eng->dir_watch = 0; | ||
| 185 | } | ||
| 186 | if (eng->inotify_instance != -1) { | ||
| 187 | close(eng->inotify_instance); | ||
| 188 | } | ||
| 189 | free(eng); | ||
| 190 | *pEng = 0; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | void plugin_engine_update(PluginEngine* eng) { | ||
| 195 | assert(eng); | ||
| 196 | |||
| 197 | struct pollfd pollfds[1] = { | ||
| 198 | {eng->inotify_instance, POLLIN, 0} | ||
| 199 | }; | ||
| 200 | |||
| 201 | int ret = 0; | ||
| 202 | while ((ret = poll(pollfds, 1, 0)) != 0) { | ||
| 203 | if (ret > 0) { | ||
| 204 | const struct pollfd* pfd = &pollfds[0]; | ||
| 205 | if (pfd->revents & POLLIN) { | ||
| 206 | // inotify instances don't like to be partially read, and the events, | ||
| 207 | // when watching a directory, have a variable-length file name. | ||
| 208 | uint8_t buf[sizeof(struct inotify_event) + NAME_MAX + 1] = {0}; | ||
| 209 | ssize_t length = read(eng->inotify_instance, &buf, sizeof(buf)); | ||
| 210 | if (length == -1) { | ||
| 211 | LOGE( | ||
| 212 | "read() on inotify instance failed with error [%d]: %s", errno, | ||
| 213 | strerror(errno)); | ||
| 214 | break; | ||
| 215 | } | ||
| 216 | const uint8_t* next = buf; | ||
| 217 | const uint8_t* end = buf + sizeof(buf); | ||
| 218 | while (next < end) { | ||
| 219 | const struct inotify_event* event = (const struct inotify_event*)next; | ||
| 220 | if (event->mask & WATCH_MASK) { | ||
| 221 | if (event->wd == eng->dir_watch) { | ||
| 222 | if (event->len > 0) { | ||
| 223 | // Name does not include directory, e.g., libfoo.so | ||
| 224 | const mstring file = mstring_make(event->name); | ||
| 225 | list_foreach_mut(eng->plugins, { | ||
| 226 | Plugin* plugin = value; | ||
| 227 | if (mstring_eq(file, plugin_lib_name(plugin))) { | ||
| 228 | if (load_library(plugin)) { | ||
| 229 | plugin->reloaded = true; | ||
| 230 | } | ||
| 231 | break; | ||
| 232 | } | ||
| 233 | }); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | next += sizeof(struct inotify_event) + event->len; | ||
| 238 | } | ||
| 239 | } | ||
| 240 | if ((pfd->revents & POLLERR) || (pfd->revents & POLLHUP) || | ||
| 241 | (pfd->revents & POLLNVAL)) { | ||
| 242 | LOGE("inotify instance is in a bad state"); | ||
| 243 | break; | ||
| 244 | } | ||
| 245 | } else if (ret == -1) { | ||
| 246 | LOGE("poll() failed with error [%d]: %s", errno, strerror(errno)); | ||
| 247 | break; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
