/* Copyright Red Hat, Inc. 2002-2004, 2009 The Magma Cluster API Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The Magma Cluster API Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /** @file * Plugin loading routines */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct _plugin_list { list_head(); const listener_plugin_t *listener; const backend_plugin_t *backend; plugin_type_t type; } plugin_list_t; static plugin_list_t *server_plugins = NULL; int plugin_reg_backend(const backend_plugin_t *plugin) { plugin_list_t *newplug; if (plugin_find_backend(plugin->name)) { errno = EEXIST; return -1; } newplug = malloc(sizeof(*newplug)); if (!newplug) return -1; memset(newplug, 0, sizeof(*newplug)); newplug->backend = plugin; newplug->type = PLUGIN_BACKEND; list_insert(&server_plugins, newplug); return 0; } int plugin_reg_listener(const listener_plugin_t *plugin) { plugin_list_t *newplug; if (plugin_find_listener(plugin->name)) { errno = EEXIST; return -1; } newplug = malloc(sizeof(*newplug)); if (!newplug) return -1; memset(newplug, 0, sizeof(*newplug)); newplug->listener = plugin; newplug->type = PLUGIN_LISTENER; list_insert(&server_plugins, newplug); return 0; } void plugin_dump(void) { plugin_list_t *p; int x, y; y = 0; list_for(&server_plugins, p, x) { if (p->type == PLUGIN_BACKEND) { if (!y) { y = 1; printf("Available backends:\n"); } printf(" %s %s\n", p->backend->name, p->backend->version); } } y = 0; list_for(&server_plugins, p, x) { if (p->type == PLUGIN_LISTENER) { if (!y) { y = 1; printf("Available listeners:\n"); } printf(" %s %s\n", p->listener->name, p->listener->version); } } } const backend_plugin_t * plugin_find_backend(const char *name) { plugin_list_t *p; int x; list_for(&server_plugins, p, x) { if (p->type != PLUGIN_BACKEND) continue; if (!strcasecmp(name, p->backend->name)) return p->backend; } return NULL; } const listener_plugin_t * plugin_find_listener(const char *name) { plugin_list_t *p; int x; list_for(&server_plugins, p, x) { if (p->type != PLUGIN_LISTENER) continue; if (!strcasecmp(name, p->listener->name)) return p->listener; } return NULL; } static int backend_plugin_load(void *handle, const char *libpath) { const backend_plugin_t *plug = NULL; double (*modversion)(void); backend_plugin_t *(*modinfo)(void); modversion = dlsym(handle, BACKEND_VER_STR); if (!modversion) { dbg_printf(1, "Failed to map %s\n", BACKEND_VER_STR); errno = EINVAL; return -1; } if (modversion() != PLUGIN_VERSION_BACKEND) { dbg_printf(1, "API version mismatch in %s: \n" " %f expected; %f received.\n", libpath, PLUGIN_VERSION_BACKEND, modversion()); errno = EINVAL; return -1; } modinfo = dlsym(handle, BACKEND_INFO_STR); if (!modinfo) { dbg_printf(1, "Failed to map %s\n", BACKEND_INFO_STR); errno = EINVAL; return -1; } plug = modinfo(); if (plugin_reg_backend(plug) < 0) { dbg_printf(1, "Failed to register %s %s\n", plug->name, plug->version); errno = EINVAL; return -1; } else { dbg_printf(1, "Registered backend plugin %s %s\n", plug->name, plug->version); } return 0; } static int listener_plugin_load(void *handle, const char *libpath) { const listener_plugin_t *plug = NULL; double (*modversion)(void); listener_plugin_t *(*modinfo)(void); modversion = dlsym(handle, LISTENER_VER_STR); if (!modversion) { dbg_printf(1, "Failed to map %s\n", LISTENER_VER_STR); errno = EINVAL; return -1; } if (modversion() != PLUGIN_VERSION_LISTENER) { dbg_printf(1, "API version mismatch in %s: \n" " %f expected; %f received.\n", libpath, PLUGIN_VERSION_LISTENER, modversion()); dlclose(handle); errno = EINVAL; return -1; } modinfo = dlsym(handle, LISTENER_INFO_STR); if (!modinfo) { dbg_printf(1, "Failed to map %s\n", LISTENER_INFO_STR); errno = EINVAL; return -1; } plug = modinfo(); if (plugin_reg_listener(plug) < 0) { dbg_printf(1, "Failed to register %s %s\n", plug->name, plug->version); errno = EINVAL; return -1; } else { dbg_printf(1, "Registered listener plugin %s %s\n", plug->name, plug->version); } return 0; } /** * Load a cluster plugin .so file and map all the functions * provided to entries in a backend_plugin_t structure. * * @param libpath Path to file. * @return NULL on failure, or plugin-specific * (const) backend_plugin_t * structure on * success. */ int plugin_load(const char *libpath) { void *handle = NULL; struct stat sb; errno = 0; if (!libpath) { errno = EINVAL; return -1; } if (stat(libpath, &sb) != 0) { return -1; } /* If it's not owner-readable or it's a directory, ignore/fail. Thus, a user may change the permission of a plugin "u-r" and this would then prevent magma apps from loading it. */ if (S_ISDIR(sb.st_mode)) { errno = EISDIR; return -1; } if (!(sb.st_mode & S_IRUSR)) { dbg_printf(1, "Ignoring %s (User-readable bit not set)\n", libpath); errno = EPERM; return -1; } dbg_printf(3, "Loading plugin from %s\n", libpath); handle = dlopen(libpath, RTLD_NOW); if (!handle) { dbg_printf(3, "Could not dlopen %s: %s\n", libpath, dlerror()); errno = ELIBACC; return -1; } if (!backend_plugin_load(handle, libpath) || !listener_plugin_load(handle, libpath)) return 0; dbg_printf(3, "%s is not a valid plugin\n", libpath); dlclose(handle); errno = EINVAL; return -1; } /** Free up a null-terminated array of strings */ static void free_dirnames(char **dirnames) { int x = 0; for (; dirnames[x]; x++) free(dirnames[x]); free(dirnames); } static int _compare(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } /** Read all entries in a directory and return them in a NULL-terminated, sorted array. */ static int read_dirnames_sorted(const char *directory, char ***dirnames) { DIR *dir; struct dirent *entry; char filename[1024]; int count = 0, x = 0; dir = opendir(directory); if (!dir) return -1; /* Count the number of plugins */ while ((entry = readdir(dir)) != NULL) ++count; /* Malloc the entries */ *dirnames = malloc(sizeof(char *) * (count+1)); if (!*dirnames) { #ifdef DEBUG printf("%s: Failed to malloc %d bytes", __FUNCTION__, (int)(sizeof(char *) * (count+1))); #endif closedir(dir); errno = ENOMEM; return -1; } memset(*dirnames, 0, sizeof(char *) * (count + 1)); rewinddir(dir); /* Store the directory names. */ while ((entry = readdir(dir)) != NULL) { snprintf(filename, sizeof(filename), "%s/%s", directory, entry->d_name); (*dirnames)[x] = strdup(filename); if (!(*dirnames)[x]) { #ifdef DEBUG printf("Failed to duplicate %s\n", filename); #endif free_dirnames(*dirnames); closedir(dir); errno = ENOMEM; return -1; } ++x; } closedir(dir); /* Sort the directory names. */ qsort((*dirnames), count, sizeof(char *), _compare); return 0; } /** */ int plugin_search(const char *pathname) { int found = 0; int fcount = 0; char **filenames; dbg_printf(1, "Searching for plugins in %s\n", pathname); if (read_dirnames_sorted(pathname, &filenames) != 0) { return -1; } for (fcount = 0; filenames[fcount]; fcount++) { if (plugin_load(filenames[fcount]) == 0) ++found; } free_dirnames(filenames); if (!found) { dbg_printf(1, "No usable plugins found.\n"); errno = ELIBACC; return -1; } return found; }