#include "plugin.h" #include "cstring.h" #include "list.h" #include "log/log.h" // TODO: Use the error library instead. Move it to clib. #include #include #include #include #include #include #include #include #include // Watching for IN_CREATE leads the plugin engine to try to reload a plugin's // shared library before the compiler has fully written to it. static const int WATCH_MASK = IN_CLOSE_WRITE; /// Plugin state. /// /// Each Plugin object points to a shared library and holds a state object. /// /// The same shared library can be loaded multiple times, resulting in multiple /// Plugin instances, each with its own internal state. typedef struct Plugin { void* handle; // First member so that Plugin can be cast to handle. void* state; // Plugin's internal state. bool reloaded; // Whether the plugin has been reloaded, state needs to be // re-created. PluginEngine* eng; // So that the public API can do stuff with just a Plugin*. mstring filename; } Plugin; DEF_LIST(Plugin); /// Plugin engine. typedef struct PluginEngine { int inotify_instance; int dir_watch; // inotify watch on the plugins directory. Plugin_list plugins; mstring plugins_dir; } PluginEngine; // ----------------------------------------------------------------------------- // Plugin. // ----------------------------------------------------------------------------- static mstring plugin_lib_name(const Plugin* plugin) { return mstring_concat( mstring_make("lib"), mstring_concat_cstr(plugin->filename, ".so")); } static mstring plugin_lib_path(const Plugin* plugin) { return mstring_concat(plugin->eng->plugins_dir, plugin_lib_name(plugin)); } static bool load_library(Plugin* plugin) { assert(plugin); assert(plugin->eng); // Handle reloading a previously-loaded library. if (plugin->handle) { dlclose(plugin->handle); plugin->handle = 0; } const mstring lib = plugin_lib_path(plugin); // If the plugin fails to load, make sure to keep the plugin's old handle to // handle the error gracefully. This handles reload failures, specifically. void* handle = 0; if ((handle = dlopen(mstring_cstr(&lib), RTLD_NOW))) { LOGD("Plugin [%s] loaded successfully", mstring_cstr(&plugin->filename)); plugin->handle = handle; return true; } else { LOGE("dlopen() failed: %s", dlerror()); } return false; } static void delete_plugin_state(Plugin* plugin) { if (plugin->state) { free(plugin->state); plugin->state = 0; } } void set_plugin_state(Plugin* plugin, void* state) { assert(plugin); delete_plugin_state(plugin); plugin->state = state; } void* get_plugin_state(Plugin* plugin) { assert(plugin); return plugin->state; } static void destroy_plugin(Plugin* plugin) { if (plugin) { if (plugin->handle) { dlclose(plugin->handle); plugin->handle = 0; } delete_plugin_state(plugin); } } Plugin* load_plugin(PluginEngine* eng, const char* filename) { assert(eng); assert(filename); Plugin plugin = (Plugin){.eng = eng, .filename = mstring_make(filename)}; if (!load_library(&plugin)) { return 0; } list_push(eng->plugins, plugin); return &eng->plugins.head->val; } void delete_plugin(Plugin** pPlugin) { assert(pPlugin); Plugin* plugin = *pPlugin; if (plugin) { assert(plugin->eng); destroy_plugin(plugin); list_remove_ptr(plugin->eng->plugins, plugin); *pPlugin = 0; } } bool plugin_reloaded(Plugin* plugin) { assert(plugin); const bool reloaded = plugin->reloaded; plugin->reloaded = false; return reloaded; } // ----------------------------------------------------------------------------- // Plugin Engine. // ----------------------------------------------------------------------------- PluginEngine* new_plugin_engine(const PluginEngineDesc* desc) { PluginEngine* eng = 0; if (!(eng = calloc(1, sizeof(PluginEngine)))) { goto cleanup; } eng->plugins = make_list(Plugin); eng->plugins_dir = mstring_concat_cstr(mstring_make(desc->plugins_dir), "/"); LOGD("Watch plugins directory: %s", mstring_cstr(&eng->plugins_dir)); if ((eng->inotify_instance = inotify_init()) == -1) { LOGE("Failed to create inotify instance"); goto cleanup; } if ((eng->dir_watch = inotify_add_watch( eng->inotify_instance, mstring_cstr(&eng->plugins_dir), WATCH_MASK)) == -1) { LOGE("Failed to watch directory: %s", mstring_cstr(&eng->plugins_dir)); goto cleanup; } return eng; cleanup: delete_plugin_engine(&eng); return 0; } void delete_plugin_engine(PluginEngine** pEng) { assert(pEng); PluginEngine* eng = *pEng; if (eng) { list_foreach_mut(eng->plugins, { destroy_plugin(value); }); del_list(eng->plugins); if (eng->dir_watch != -1) { inotify_rm_watch(eng->dir_watch, eng->inotify_instance); close(eng->dir_watch); eng->dir_watch = 0; } if (eng->inotify_instance != -1) { close(eng->inotify_instance); } free(eng); *pEng = 0; } } void plugin_engine_update(PluginEngine* eng) { assert(eng); struct pollfd pollfds[1] = { {eng->inotify_instance, POLLIN, 0} }; int ret = 0; while ((ret = poll(pollfds, 1, 0)) != 0) { if (ret > 0) { const struct pollfd* pfd = &pollfds[0]; if (pfd->revents & POLLIN) { // inotify instances don't like to be partially read, and the events, // when watching a directory, have a variable-length file name. uint8_t buf[sizeof(struct inotify_event) + NAME_MAX + 1] = {0}; ssize_t length = read(eng->inotify_instance, &buf, sizeof(buf)); if (length == -1) { LOGE( "read() on inotify instance failed with error [%d]: %s", errno, strerror(errno)); break; } const uint8_t* next = buf; const uint8_t* end = buf + sizeof(buf); while (next < end) { const struct inotify_event* event = (const struct inotify_event*)next; if (event->mask & WATCH_MASK) { if (event->wd == eng->dir_watch) { if (event->len > 0) { // Name does not include directory, e.g., libfoo.so const mstring file = mstring_make(event->name); list_foreach_mut(eng->plugins, { Plugin* plugin = value; if (mstring_eq(file, plugin_lib_name(plugin))) { if (load_library(plugin)) { plugin->reloaded = true; } break; } }); } } } next += sizeof(struct inotify_event) + event->len; } } if ((pfd->revents & POLLERR) || (pfd->revents & POLLHUP) || (pfd->revents & POLLNVAL)) { LOGE("inotify instance is in a bad state"); break; } } else if (ret == -1) { LOGE("poll() failed with error [%d]: %s", errno, strerror(errno)); break; } } }