blob: df3baa074a5059775698844fda4d9b1cf828f6ce [file] [log] [blame]
/*****************************************************************************\
* 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);
}