#include "plugins.h" #include #include #include #include #include #include #include #include #include #include #include struct ptx__plugin_manager_tag { ptx_plugin_t *pl_head, *pl_tail; }; struct ptx__plugin_handle_tag { /* what plugin manager is this plugin known to? */ ptx_plugin_manager_t *manager; /* handle to library */ void *modp; const struct ptx_plugin_desc *desc; /* plugin list entries */ ptx_plugin_t *prev; ptx_plugin_t *next; int path_fd; int pl_state; /* used to assert that the application is following its contract with plugins */ /* plugin filename */ char fname[1]; }; enum { PL_STATE_NEW, /* plugin struct just initialized */ PL_STATE_LOADED, /* plugin module loaded */ PL_STATE_INITIALIZING, /* plugin initializer call started */ PL_STATE_INITIALIZED, /* plugin initializer completed */ PL_STATE_DEINITIALIZED, /* plugin deinitializer called */ PL_STATE_ERROR, /* error in initializer, only valid operation is free */ PL_STATE_UNLOADED /* plugin unloaded */ }; ptx_plugin_manager_t *ptx_plugin_manager_new(void) { ptx_plugin_manager_t *mgr = malloc(sizeof(ptx_plugin_manager_t)); if (!mgr) return NULL; /* do init */ mgr->pl_head = mgr->pl_tail = NULL; return mgr; } /* inform the plugin that it is about to be unloaded. It should clean up its resources. */ static int deinit_plugin(ptx_plugin_t *plugin, void **presume); /* unload/cleanup/free the plugin. */ static void free_plugin(ptx_plugin_t *plugin); void ptx_plugin_manager_free(ptx_plugin_manager_t *mgr) { if (!mgr) return; /* do cleanup */ for (ptx_plugin_t *plugin = mgr->pl_head, *next; plugin; plugin = next) { next = plugin->next; deinit_plugin(plugin, NULL); /* result ignored (not resuming) */ free_plugin(plugin); } free(mgr); return; } static ptx_plugin_t *load_plugin(ptx_plugin_manager_t *manager, const char *plugin_file, const char *plugin_fname) { /* define and initialize all variables to be cleaned up here! */ size_t fnamelen = strlen(plugin_fname); ptx_plugin_t *plugin = NULL; void *mod_dl = NULL; const struct ptx_plugin_desc *pl_desc; plugin = calloc(1, sizeof(ptx_plugin_t) + fnamelen); if (!plugin) { fprintf(stderr, "(plugin) malloc (loading %s): %s\n", plugin_file, strerror(errno)); /* FIXME: maybe abort() for these sorts of issues? */ goto cleanup; } plugin->pl_state = PL_STATE_NEW; plugin->manager = manager; memcpy(plugin->fname, plugin_fname, fnamelen); /* NUL-termination achieved thanks to calloc */ mod_dl = dlopen(plugin_file, RTLD_NOW | RTLD_LOCAL); if (!mod_dl) { fprintf(stderr, "(plugin) dlopen: failed to load %s: %s\n", plugin_fname, dlerror()); goto cleanup; } plugin->modp = mod_dl; pl_desc = dlsym(mod_dl, PTX_STR(PTX__INTERNAL_PLUGIN_DESC_SYM)); if (!pl_desc) { fprintf(stderr, "(plugin) dlsym(descriptor): failed to load %s: %s\n", plugin_fname, dlerror()); goto cleanup; } if (pl_desc->desc_len != sizeof(struct ptx_plugin_desc)) { fprintf(stderr, "(plugin) %s: descriptor struct size mismatch: %zu != %zu (expected)\n", plugin_fname, pl_desc->desc_len, sizeof(struct ptx_plugin_desc)); goto cleanup; } if (pl_desc->desc_ver != PTX_CUR_PLUGIN_VERSION) { fprintf(stderr, "(plugin) %s: is for a different version of ptxmc! %u != %u (expected)\n", plugin_fname, pl_desc->desc_ver, PTX_CUR_PLUGIN_VERSION); goto cleanup; } plugin->desc = pl_desc; plugin->pl_state = PL_STATE_LOADED; return plugin; cleanup: if (mod_dl) dlclose(mod_dl); free(plugin); return NULL; } #define PATHBUF_LEN (32) int ptx_plugin_manager_load_dir(ptx_plugin_manager_t *mgr, const char *dirname) { struct dirent *ent; char pl_pathbuf[PATHBUF_LEN] = { 0 }; struct stat st; DIR *dir = opendir(dirname); if (!dir) { fprintf(stderr, "Failed to load plugins from %s (opendir): %s\n", dirname, strerror(errno)); return -1; } int pl_dir_fd = dirfd(dir); assert(pl_dir_fd >= 0); while ((errno = 0, ent = readdir(dir))) { int pl_fd = openat(pl_dir_fd, ent->d_name, O_PATH); if (pl_fd < 0) { fprintf(stderr, "(plugin) openat: failed to open %s/%s: %s\n", dirname, ent->d_name, strerror(errno)); continue; } if (fstat(pl_fd, &st) < 0) { fprintf(stderr, "(plugin) fstat: failed to stat %s/%s: %s\n", dirname, ent->d_name, strerror(errno)); goto cleanup_iter; } if (!S_ISREG(st.st_mode)) { goto cleanup_iter; /* not a file. skip silently */ } int rlen = snprintf(pl_pathbuf, PATHBUF_LEN, "/proc/self/fd/%d", pl_fd); assert(rlen < PATHBUF_LEN); assert(pl_pathbuf[PATHBUF_LEN-1] == '\0'); ptx_plugin_t *plugin = load_plugin(mgr, pl_pathbuf, ent->d_name); if (!plugin) goto cleanup_iter; /* error has been reported by load_plugin already. */ plugin->path_fd = pl_fd; if (mgr->pl_head) { assert(mgr->pl_tail); mgr->pl_tail->next = plugin; plugin->prev = mgr->pl_tail; plugin->next = NULL; mgr->pl_tail = plugin; } else { assert(!mgr->pl_tail); plugin->prev = plugin->next = NULL; mgr->pl_head = mgr->pl_tail = plugin; } continue; cleanup_iter: close(pl_fd); } int errno_save = errno; closedir(dir); if (errno_save != 0) { fprintf(stderr, "Error reading entries from %s (readdir): %s\n", dirname, strerror(errno)); return -1; } return 0; } static int init_plugin(ptx_plugin_t *plugin, void *resume) { assert(plugin); assert(plugin->pl_state == plugin->pl_state == PL_STATE_LOADED); plugin->pl_state = PL_STATE_INITIALIZING; int result = 0; if (plugin->desc->init_proc) { result = (plugin->desc->init_proc)(plugin, resume); } else { fprintf(stderr, "(plugin) warning: plugin %s/%s has no initialization function! (does it do anything?)\n", plugin->desc->name, plugin->fname); } if (result < 0) { plugin->pl_state = PL_STATE_ERROR; fprintf(stderr, "(plugin) init_plugin: %s/%s: initializer failed (%d)\n", plugin->desc->name, plugin->fname, result); } else { plugin->pl_state = PL_STATE_INITIALIZED; } return result; } int ptx_plugin_manager_init_plugins(ptx_plugin_manager_t *mgr) { for (ptx_plugin_t *plugin = mgr->pl_head; plugin; plugin = plugin->next) { if (plugin->pl_state >= PL_STATE_INITIALIZED) continue; assert(plugin->pl_state != PL_STATE_INITIALIZING); /* function should never be called from a plugin initializer */ init_plugin(plugin, NULL); } } static int deinit_plugin(ptx_plugin_t *plugin, void **presume) { assert(plugin); assert(plugin->pl_state >= PL_STATE_INITIALIZED && plugin->pl_state < PL_STATE_DEINITIALIZED); int result = DEINIT_OK; if (plugin->desc->deinit_proc) { result = (plugin->desc->deinit_proc)(plugin, presume); } else { result = presume ? DEINIT_ERR_RESUME_UNSUPPORTED : DEINIT_OK; } plugin->pl_state = PL_STATE_DEINITIALIZED; return result; } static void free_plugin(ptx_plugin_t *plugin) { assert(plugin->pl_state < PL_STATE_INITIALIZED || plugin->pl_state >= PL_STATE_DEINITIALIZED); dlclose(plugin->modp); close(plugin->path_fd); plugin->pl_state = PL_STATE_UNLOADED; /* state never observed by the software */ free(plugin); } PTX_API void ptx_plugin_set_mainloop_func(ptx_plugin_t *plugin, void *func) { assert(plugin); assert(plugin->pl_state == PL_STATE_INITIALIZING || plugin->pl_state == PL_STATE_INITIALIZED); UNUSED(plugin, func); }