|  | /*****************************************************************************\ | 
|  | *  plugrack.c - an intelligent container for plugins | 
|  | ***************************************************************************** | 
|  | *  Copyright (C) 2002-2007 The Regents of the University of California. | 
|  | *  Copyright (C) 2008-2009 Lawrence Livermore National Security. | 
|  | *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). | 
|  | *  Written by Jay Windley <jwindley@lnxi.com>. | 
|  | *  CODE-OCEC-09-009. All rights reserved. | 
|  | * | 
|  | *  This file is part of Slurm, a resource management program. | 
|  | *  For details, see <https://slurm.schedmd.com/>. | 
|  | *  Please also read the included file: DISCLAIMER. | 
|  | * | 
|  | *  Slurm is free software; you can redistribute it and/or modify it under | 
|  | *  the terms of the GNU General Public License as published by the Free | 
|  | *  Software Foundation; either version 2 of the License, or (at your option) | 
|  | *  any later version. | 
|  | * | 
|  | *  In addition, as a special exception, the copyright holders give permission | 
|  | *  to link the code of portions of this program with the OpenSSL library under | 
|  | *  certain conditions as described in each individual source file, and | 
|  | *  distribute linked combinations including the two. You must obey the GNU | 
|  | *  General Public License in all respects for all of the code used other than | 
|  | *  OpenSSL. If you modify file(s) with this exception, you may extend this | 
|  | *  exception to your version of the file(s), but you are not obligated to do | 
|  | *  so. If you do not wish to do so, delete this exception statement from your | 
|  | *  version.  If you delete this exception statement from all source files in | 
|  | *  the program, then also delete it here. | 
|  | * | 
|  | *  Slurm 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 General Public License for more | 
|  | *  details. | 
|  | * | 
|  | *  You should have received a copy of the GNU General Public License along | 
|  | *  with Slurm; if not, write to the Free Software Foundation, Inc., | 
|  | *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA. | 
|  | \*****************************************************************************/ | 
|  |  | 
|  | #include <dirent.h> | 
|  | #include <dlfcn.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include "src/common/macros.h" | 
|  | #include "src/common/plugrack.h" | 
|  | #include "src/common/read_config.h" | 
|  | #include "src/common/xassert.h" | 
|  | #include "src/common/xmalloc.h" | 
|  | #include "src/common/xstring.h" | 
|  |  | 
|  | strong_alias(plugrack_create,         slurm_plugrack_create); | 
|  | strong_alias(plugrack_destroy,        slurm_plugrack_destroy); | 
|  | strong_alias(plugrack_read_dir,       slurm_plugrack_read_dir); | 
|  | strong_alias(plugrack_use_by_type,    slurm_plugrack_use_by_type); | 
|  |  | 
|  | /* | 
|  | * Represents a plugin in the rack. | 
|  | * | 
|  | * full_type is the fully-qualified plugin type, e.g., "auth/kerberos". | 
|  | * For the low-level plugin interface the type can be whatever it needs | 
|  | * to be.  For the rack-level interface, the type exported by the plugin | 
|  | * must be of the form "<major>/<minor>". | 
|  | * | 
|  | * fq_path is the fully-qualified pathname to the plugin. | 
|  | * | 
|  | * plug is the plugin handle.  If it is equal to PLUGIN_INVALID_HANDLE | 
|  | * then the plugin is not currently loaded in memory. | 
|  | * | 
|  | * refcount shows how many clients have requested to use the plugin. | 
|  | * If this is zero, the rack code may decide to unload the plugin. | 
|  | */ | 
|  | typedef struct _plugrack_entry { | 
|  | const char *full_type; | 
|  | const char *fq_path; | 
|  | plugin_handle_t	plug; | 
|  | int refcount; | 
|  | } plugrack_entry_t; | 
|  |  | 
|  | /* | 
|  | * Implementation of the plugin rack. | 
|  | * | 
|  | * entries is the list of plugrack_entry_t. | 
|  | */ | 
|  | struct _plugrack { | 
|  | list_t *entries; | 
|  | const char *major_type; | 
|  | }; | 
|  |  | 
|  | static bool _match_major(const char *path_name, const char *major_type); | 
|  | static int _plugrack_read_single_dir(plugrack_t *rack, char *dir); | 
|  | static bool _so_file(char *pathname); | 
|  |  | 
|  | /* | 
|  | * Destructor function for the List code.  This should entirely | 
|  | * clean up a plugin_entry_t. | 
|  | */ | 
|  | static void plugrack_entry_destructor(void *v) | 
|  | { | 
|  | plugrack_entry_t *victim = v; | 
|  |  | 
|  | if (victim == NULL) | 
|  | return; | 
|  |  | 
|  | /* | 
|  | * Free memory and unload the plugin if necessary.  The assert | 
|  | * is to make sure we were actually called from the List destructor | 
|  | * which should only be callable from plugrack_destroy(). | 
|  | */ | 
|  | xassert(victim->refcount == 0); | 
|  | xfree(victim->full_type); | 
|  | xfree(victim->fq_path); | 
|  | if (victim->plug != PLUGIN_INVALID_HANDLE) | 
|  | plugin_unload(victim->plug); | 
|  | xfree(victim); | 
|  | } | 
|  |  | 
|  | plugrack_t *plugrack_create(const char *major_type) | 
|  | { | 
|  | plugrack_t *rack = xmalloc(sizeof(*rack)); | 
|  |  | 
|  | rack->major_type = xstrdup(major_type); | 
|  | rack->entries = list_create(plugrack_entry_destructor); | 
|  | return rack; | 
|  | } | 
|  |  | 
|  | int plugrack_destroy(plugrack_t *rack) | 
|  | { | 
|  | list_itr_t *it; | 
|  | plugrack_entry_t *e; | 
|  |  | 
|  | if (!rack) | 
|  | return SLURM_ERROR; | 
|  |  | 
|  | /* | 
|  | * See if there are any plugins still being used.  If we unload them, | 
|  | * the program might crash because cached virtual mapped addresses | 
|  | * will suddenly be outside our virtual address space. | 
|  | */ | 
|  | it = list_iterator_create(rack->entries); | 
|  | while ((e = list_next(it))) { | 
|  | if (e->refcount > 0) { | 
|  | debug2("%s: attempt to destroy %s plugin rack that is still in use", | 
|  | __func__, rack->major_type); | 
|  | list_iterator_destroy(it); | 
|  | return SLURM_ERROR; /* plugins still in use. */ | 
|  | } | 
|  | } | 
|  | list_iterator_destroy(it); | 
|  |  | 
|  | FREE_NULL_LIST(rack->entries); | 
|  | xfree(rack->major_type); | 
|  | xfree(rack); | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | static int plugrack_add_plugin_path(plugrack_t *rack, | 
|  | const char *full_type, | 
|  | const char *fq_path) | 
|  | { | 
|  | plugrack_entry_t *e; | 
|  |  | 
|  | if ((!rack) || (!fq_path)) | 
|  | return SLURM_ERROR; | 
|  |  | 
|  | e = xmalloc(sizeof(*e)); | 
|  |  | 
|  | e->full_type = xstrdup(full_type); | 
|  | e->fq_path   = xstrdup(fq_path); | 
|  | e->plug      = PLUGIN_INVALID_HANDLE; | 
|  | e->refcount  = 0; | 
|  | list_append(rack->entries, e); | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* test for the plugin in the various colon separated directories */ | 
|  | int plugrack_read_dir(plugrack_t *rack, const char *dir) | 
|  | { | 
|  | char *head, *dir_array; | 
|  | int i, rc = SLURM_SUCCESS; | 
|  |  | 
|  | if ((!rack) || (!dir)) | 
|  | return SLURM_ERROR; | 
|  |  | 
|  | dir_array = xstrdup(dir); | 
|  | head = dir_array; | 
|  | for (i = 0; ; i++) { | 
|  | if (dir_array[i] == '\0') { | 
|  | if (_plugrack_read_single_dir(rack, head) == | 
|  | SLURM_ERROR) | 
|  | rc = SLURM_ERROR; | 
|  | break; | 
|  | } else if (dir_array[i] == ':') { | 
|  | dir_array[i] = '\0'; | 
|  | if (_plugrack_read_single_dir(rack, head) == | 
|  | SLURM_ERROR) | 
|  | rc = SLURM_ERROR; | 
|  | head = dir_array + i + 1; | 
|  | } | 
|  | } | 
|  | xfree(dir_array); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int _plugrack_read_single_dir(plugrack_t *rack, char *dir) | 
|  | { | 
|  | char *fq_path; | 
|  | char *tail; | 
|  | DIR *dirp; | 
|  | struct dirent *e; | 
|  | struct stat st; | 
|  | static const size_t type_len = 64; | 
|  | char plugin_type[type_len]; | 
|  | static int max_path_len = 0; | 
|  |  | 
|  | /* Allocate a buffer for fully-qualified path names. */ | 
|  | if (max_path_len == 0) { | 
|  | max_path_len = pathconf("/", _PC_NAME_MAX); | 
|  | if (max_path_len <= 0) | 
|  | max_path_len = 256; | 
|  | } | 
|  | fq_path = xmalloc(strlen(dir) + max_path_len + 1); | 
|  |  | 
|  | /* | 
|  | * Write the directory name in it, then a separator, then | 
|  | * keep track of where we want to write the individual file | 
|  | * names. | 
|  | */ | 
|  | strcpy(fq_path, dir); | 
|  | tail = &fq_path[strlen(dir)]; | 
|  | *tail = '/'; | 
|  | ++tail; | 
|  |  | 
|  | /* Open the directory. */ | 
|  | dirp = opendir(dir); | 
|  | if (dirp == NULL) { | 
|  | error("cannot open plugin directory %s", dir); | 
|  | xfree(fq_path); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  |  | 
|  | while (1) { | 
|  | e = readdir(dirp); | 
|  | if (e == NULL) | 
|  | break; | 
|  |  | 
|  | /* | 
|  | * Compose file name.  Where NAME_MAX is defined it represents | 
|  | * the largest file name given in a dirent.  This macro is used | 
|  | * in the  allocation of "tail" above, so this unbounded copy | 
|  | * should work. | 
|  | */ | 
|  | strcpy(tail, e->d_name); | 
|  |  | 
|  | /* Check only regular files. */ | 
|  | if ((xstrncmp(e->d_name, ".", 1) == 0) || | 
|  | (stat(fq_path, &st) < 0) || | 
|  | (!S_ISREG(st.st_mode))) | 
|  | continue; | 
|  |  | 
|  | /* Check only shared object files */ | 
|  | if (!_so_file(e->d_name)) | 
|  | continue; | 
|  |  | 
|  | /* file's prefix must match specified major_type | 
|  | * to avoid having some program try to open a | 
|  | * plugin designed for a different program and | 
|  | * discovering undefined symbols */ | 
|  | if ((rack->major_type) && | 
|  | (!_match_major(e->d_name, rack->major_type))) | 
|  | continue; | 
|  |  | 
|  | /* Test the type. */ | 
|  | if (plugin_peek(fq_path, plugin_type, type_len) != | 
|  | SLURM_SUCCESS) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (rack->major_type && | 
|  | (xstrncmp(rack->major_type, plugin_type, | 
|  | strlen(rack->major_type)) != 0)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Add it to the list. */ | 
|  | (void) plugrack_add_plugin_path(rack, plugin_type, fq_path); | 
|  | } | 
|  |  | 
|  | closedir(dirp); | 
|  |  | 
|  | xfree(fq_path); | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Return true if the specified pathname is recognized as that of a shared | 
|  | * object (i.e. containing ".so\0") | 
|  | */ | 
|  | static bool _so_file(char *file_name) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | if (file_name == NULL) | 
|  | return false; | 
|  |  | 
|  | for (i=0; file_name[i]; i++) { | 
|  | if ((file_name[i]   == '.') && (file_name[i+1] == 's') && | 
|  | (file_name[i+2] == 'o') && (file_name[i+3] == '\0')) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Return true of the specified major_type is a prefix of the shared object | 
|  | * pathname (i.e. either "<major_name>..." or "lib<major_name>...") */ | 
|  | static bool _match_major(const char *path_name, const char *major_type) | 
|  | { | 
|  | char *head = (char *)path_name; | 
|  |  | 
|  | if (xstrncmp(head, "lib", 3) == 0) | 
|  | head += 3; | 
|  | if (xstrncmp(head, major_type, strlen(major_type))) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | plugin_handle_t plugrack_use_by_type(plugrack_t *rack, const char *full_type) | 
|  | { | 
|  | list_itr_t *it; | 
|  | plugrack_entry_t *e; | 
|  |  | 
|  | if ((!rack) || (!full_type)) | 
|  | return PLUGIN_INVALID_HANDLE; | 
|  |  | 
|  | it = list_iterator_create(rack->entries); | 
|  | while ((e = list_next(it))) { | 
|  | int err; | 
|  |  | 
|  | if (xstrcmp(full_type, e->full_type) != 0) | 
|  | continue; | 
|  |  | 
|  | /* See if plugin is loaded. */ | 
|  | if (e->plug == PLUGIN_INVALID_HANDLE  && | 
|  | (err = plugin_load_from_file(&e->plug, e->fq_path))) | 
|  | error("%s: %s", e->fq_path, slurm_strerror(err)); | 
|  |  | 
|  | /* If load was successful, increment the reference count. */ | 
|  | if (e->plug != PLUGIN_INVALID_HANDLE) { | 
|  | e->refcount++; | 
|  | debug3("%s: loaded plugin %s for type:%s", | 
|  | __func__, e->fq_path, full_type); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Return the plugin, even if it failed to load -- this serves | 
|  | * as an error return value. | 
|  | */ | 
|  | list_iterator_destroy(it); | 
|  | return e->plug; | 
|  | } | 
|  |  | 
|  | /* Couldn't find a suitable plugin. */ | 
|  | list_iterator_destroy(it); | 
|  | return PLUGIN_INVALID_HANDLE; | 
|  | } | 
|  |  | 
|  | static int _foreach_release_plugin(void *x, void *arg) | 
|  | { | 
|  | plugrack_entry_t *entry = (plugrack_entry_t *) x; | 
|  | const char *type = (const char *) arg; | 
|  |  | 
|  | if (entry->plug == PLUGIN_INVALID_HANDLE) | 
|  | return 0; | 
|  |  | 
|  | if (xstrcmp(entry->full_type, type)) | 
|  | return 0; | 
|  |  | 
|  | entry->refcount--; | 
|  |  | 
|  | if (entry->refcount <= 0) { | 
|  | debug5("%s: closing plugin type: %s", __func__, type); | 
|  | if (dlclose(entry->plug)) | 
|  | fatal_abort("%s: unable to dlclose plugin type: %s", | 
|  | __func__, type); | 
|  | entry->plug = PLUGIN_INVALID_HANDLE; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void plugrack_release_by_type(plugrack_t *rack, const char *type) | 
|  | { | 
|  | (void) list_for_each(rack->entries, _foreach_release_plugin, | 
|  | (void *) type); | 
|  | } | 
|  |  | 
|  | typedef struct { | 
|  | plugrack_foreach_t f; | 
|  | void *arg; | 
|  | } plugrack_foreach_args_t; | 
|  |  | 
|  | extern int plugrack_print_mpi_plugins(plugrack_t *rack) | 
|  | { | 
|  | list_itr_t *itr; | 
|  | plugrack_entry_t *e = NULL; | 
|  | char *sep, tmp[64], *pmix_vers = NULL, *comma = ""; | 
|  | int i; | 
|  |  | 
|  | xassert(rack->entries); | 
|  | itr = list_iterator_create(rack->entries); | 
|  | printf("MPI plugin types are...\n"); | 
|  | printf("\tnone\n"); | 
|  | while ((e = list_next(itr))) { | 
|  | /* | 
|  | * Support symbolic links for various pmix plugins with names | 
|  | * that contain version numbers without listing duplicates | 
|  | */ | 
|  | sep = strstr(e->fq_path, "/mpi_"); | 
|  | if (sep) { | 
|  | sep += 5; | 
|  | i = snprintf(tmp, sizeof(tmp), "%s", sep); | 
|  | if (i >= sizeof(tmp)) | 
|  | tmp[sizeof(tmp)-1] = '\0'; | 
|  | sep = strstr(tmp, ".so"); | 
|  | if (sep) | 
|  | sep[0] = '\0'; | 
|  | sep = tmp; | 
|  |  | 
|  | if (!xstrncmp(sep, "pmix_", 5)) { | 
|  | xstrfmtcat(pmix_vers, "%s%s", comma, sep); | 
|  | comma = ","; | 
|  | continue; | 
|  | } | 
|  | } else | 
|  | sep = (char *) e->full_type;	/* Remove "const" */ | 
|  | printf("\t%s\n", sep); | 
|  | } | 
|  | list_iterator_destroy(itr); | 
|  |  | 
|  | if (pmix_vers) | 
|  | printf("specific pmix plugin versions available: %s\n", pmix_vers); | 
|  | xfree(pmix_vers); | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | static int _foreach_plugin(void *x, void *arg) | 
|  | { | 
|  | plugrack_entry_t *entry = (plugrack_entry_t *) x; | 
|  | plugrack_foreach_args_t *args = arg; | 
|  |  | 
|  | args->f(entry->full_type, entry->fq_path, entry->plug, args->arg); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | extern void plugrack_foreach(plugrack_t *rack, plugrack_foreach_t f, void *arg) | 
|  | { | 
|  | plugrack_foreach_args_t args = { | 
|  | .f = f, | 
|  | .arg = arg, | 
|  | }; | 
|  |  | 
|  | (void) list_for_each(rack->entries, _foreach_plugin, &args); | 
|  | } | 
|  |  | 
|  | static bool _plugin_loaded(plugins_t *plugins, const char *plugin) | 
|  | { | 
|  | xassert(plugins->magic == PLUGINS_MAGIC); | 
|  |  | 
|  | if (plugins->count <= 0) | 
|  | return false; | 
|  |  | 
|  | for (int i = 0; i < plugins->count; i++) | 
|  | if (!xstrcasecmp(plugin, plugins->types[i])) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void _plugrack_foreach(const char *full_type, const char *fq_path, | 
|  | const plugin_handle_t id, void *arg) | 
|  | { | 
|  | plugins_t *plugins = arg; | 
|  | size_t i = plugins->count; | 
|  |  | 
|  | xassert(plugins->magic == PLUGINS_MAGIC); | 
|  |  | 
|  | if (_plugin_loaded(plugins, full_type)) { | 
|  | debug("%s: %s plugin type %s already loaded", | 
|  | __func__, plugins->rack->major_type, full_type); | 
|  | /* effectively a no-op if already loaded */ | 
|  | return; | 
|  | } | 
|  |  | 
|  | plugins->count++; | 
|  | xrecalloc(plugins->handles, plugins->count, sizeof(*plugins->handles)); | 
|  | xrecalloc(plugins->types, plugins->count, sizeof(*plugins->types)); | 
|  |  | 
|  | plugins->types[i] = xstrdup(full_type); | 
|  | plugins->handles[i] = id; | 
|  |  | 
|  | debug("%s: %s plugin type:%s path:%s", | 
|  | __func__, plugins->rack->major_type, full_type, fq_path); | 
|  | } | 
|  |  | 
|  | extern int load_plugins(plugins_t **plugins_ptr, const char *major_type, | 
|  | const char *plugin_list, plugrack_foreach_t listf, | 
|  | const char **syms, size_t syms_count) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  | plugins_t *plugins; | 
|  |  | 
|  | xassert(plugins_ptr); | 
|  | if (!*plugins_ptr) { | 
|  | const char *plugin_dir; | 
|  |  | 
|  | plugins = xmalloc(sizeof(*plugins)); | 
|  | plugins->magic = PLUGINS_MAGIC; | 
|  | plugins->rack = plugrack_create(major_type); | 
|  |  | 
|  | if (slurm_conf.plugindir) | 
|  | plugin_dir = slurm_conf.plugindir; | 
|  | else | 
|  | plugin_dir = default_plugin_path; | 
|  |  | 
|  | if ((rc = plugrack_read_dir(plugins->rack, plugin_dir))) { | 
|  | error("%s: plugrack_read_dir(%s) failed: %s", | 
|  | __func__, slurm_conf.plugindir, slurm_strerror(rc)); | 
|  | goto cleanup; | 
|  | } | 
|  | } else { | 
|  | plugins = *plugins_ptr; | 
|  | xassert(plugins->magic == PLUGINS_MAGIC); | 
|  | } | 
|  |  | 
|  | if (listf && !xstrcasecmp(plugin_list, "list")) { | 
|  | /* call list function ptr and then load all */ | 
|  | plugrack_foreach(plugins->rack, listf, NULL); | 
|  | rc = SLURM_SUCCESS; | 
|  | /* reusing plugins on a list request makes no sense */ | 
|  | xassert(!*plugins_ptr); | 
|  | goto cleanup; | 
|  | } | 
|  |  | 
|  | if (!plugin_list) { | 
|  | /* no filter specified: load them all */ | 
|  | plugrack_foreach(plugins->rack, _plugrack_foreach, plugins); | 
|  | } else if (plugin_list[0] == '\0') { | 
|  | debug("%s: not loading any %s plugins", __func__, major_type); | 
|  | } else { | 
|  | /* Caller provided which plugins they want */ | 
|  | char *type, *last = NULL, *pl; | 
|  | char *typeslash = xstrdup_printf("%s/", major_type); | 
|  |  | 
|  | pl = xstrdup(plugin_list); | 
|  | type = strtok_r(pl, ",", &last); | 
|  | while (type) { | 
|  | char *ntype, *otype; | 
|  | const size_t offset = strlen(typeslash); | 
|  |  | 
|  | /* | 
|  | * Permit both prefix and no-prefix for | 
|  | * plugin names. | 
|  | */ | 
|  | if (!xstrncmp(type, typeslash, offset)) | 
|  | otype = type + offset; | 
|  | else | 
|  | otype = type; | 
|  |  | 
|  | ntype = xstrdup_printf("%s/%s", major_type, otype); | 
|  | _plugrack_foreach(ntype, NULL, PLUGIN_INVALID_HANDLE, | 
|  | plugins); | 
|  | xfree(ntype); | 
|  |  | 
|  | type = strtok_r(NULL, ",", &last); | 
|  | } | 
|  |  | 
|  | xfree(pl); | 
|  | xfree(typeslash); | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < plugins->count; i++) { | 
|  | if (plugins->handles[i] == PLUGIN_INVALID_HANDLE) { | 
|  | plugins->handles[i] = plugrack_use_by_type( | 
|  | plugins->rack, plugins->types[i]); | 
|  |  | 
|  | if (plugins->handles[i] == PLUGIN_INVALID_HANDLE) { | 
|  | error("%s: unable to find plugin: %s", | 
|  | __func__, plugins->types[i]); | 
|  | rc = ESLURM_PLUGIN_INVALID; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | xrecalloc(plugins->functions, plugins->count, | 
|  | sizeof(*plugins->functions)); | 
|  |  | 
|  | if (!plugins->count || rc) | 
|  | goto cleanup; | 
|  |  | 
|  | for (size_t i = 0; (i < plugins->count); i++) { | 
|  | if (plugins->functions[i]) { | 
|  | /* already resolved symbols */ | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (plugins->handles[i] == PLUGIN_INVALID_HANDLE) | 
|  | fatal("Invalid plugin to load?"); | 
|  |  | 
|  | xrecalloc(plugins->functions[i], syms_count + 1, sizeof(void *)); | 
|  |  | 
|  | if (plugin_get_syms(plugins->handles[i], syms_count, | 
|  | syms, plugins->functions[i]) | 
|  | < syms_count) { | 
|  | rc = ESLURM_PLUGIN_INCOMPLETE; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | cleanup: | 
|  | if (!rc) | 
|  | *plugins_ptr = plugins; | 
|  | else | 
|  | unload_plugins(plugins); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern void unload_plugins(plugins_t *plugins) | 
|  | { | 
|  | if (!plugins) | 
|  | return; | 
|  |  | 
|  | if (plugins->rack) { | 
|  | for (size_t i = 0; i < plugins->count; i++) | 
|  | plugrack_release_by_type(plugins->rack, | 
|  | plugins->types[i]); | 
|  |  | 
|  | (void) plugrack_destroy(plugins->rack); | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < plugins->count; i++) { | 
|  | if (plugins->functions) | 
|  | xfree(plugins->functions[i]); | 
|  | if (plugins->types) | 
|  | xfree(plugins->types[i]); | 
|  | } | 
|  |  | 
|  | xfree(plugins->functions); | 
|  | xfree(plugins->handles); | 
|  | xfree(plugins->types); | 
|  | xfree(plugins); | 
|  | } |