/*****************************************************************************\
 *  plugstack.c -- stackable plugin architecture for node job kontrol (SPANK)
 *****************************************************************************
 *  Copyright (C) 2005-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  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.
\*****************************************************************************/

#define _GNU_SOURCE

#include "config.h"

#include <ctype.h>
#include <dlfcn.h>
#include <glob.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>

#include "src/common/plugin.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/common/xassert.h"
#include "src/common/strlcpy.h"
#include "src/common/read_config.h"
#include "src/common/plugstack.h"
#include "src/common/optz.h"
#include "src/common/job_options.h"
#include "src/common/env.h"

#include "src/slurmd/slurmstepd/slurmstepd_job.h"

#include "slurm/spank.h"

#define REQUIRED "required"
#define OPTIONAL "optional"
#define INCLUDE  "include"

static bool has_prolog = false;
static bool has_epilog = false;

struct spank_plugin_operations {
	spank_f *init;
	spank_f *job_prolog;
	spank_f *init_post_opt;
	spank_f *local_user_init;
	spank_f *user_init;
	spank_f *task_init_privileged;
	spank_f *user_task_init;
	spank_f *task_post_fork;
	spank_f *task_exit;
	spank_f *job_epilog;
	spank_f *slurmd_exit;
	spank_f *exit;
};

const int n_spank_syms = 12;
const char *spank_syms[] = {
	"slurm_spank_init",
	"slurm_spank_job_prolog",
	"slurm_spank_init_post_opt",
	"slurm_spank_local_user_init",
	"slurm_spank_user_init",
	"slurm_spank_task_init_privileged",
	"slurm_spank_task_init",
	"slurm_spank_task_post_fork",
	"slurm_spank_task_exit",
	"slurm_spank_job_epilog",
	"slurm_spank_slurmd_exit",
	"slurm_spank_exit"
};

struct spank_plugin {
	const char *name;
	char *fq_path;
	plugin_handle_t plugin;
	bool required;
	int ac;
	char **argv;
	struct spank_plugin_operations ops;
	struct spank_option *opts;
	struct spank_stack *stack;
};

/*
 *  SPANK Plugin options
 */

struct spank_plugin_opt {
	struct spank_option *opt;   /* Copy of plugin option info           */
	struct spank_plugin *plugin;/* Link back to plugin structure        */
	int optval;                 /* Globally unique value                */
	int found:1;                /* 1 if option was found, 0 otherwise   */
	int disabled:1;             /* 1 if option is cached but disabled   */
	char *optarg;               /* Option argument.                     */
	bool set;                   /* true if argument is set              */
	bool set_by_env;            /* true if argument is set by environ   */
};

/*
 *  SPANK plugin context type (local, remote, allocator)
 */
enum spank_context_type {
	S_TYPE_NONE,
	S_TYPE_LOCAL,           /* LOCAL == srun              */
	S_TYPE_REMOTE,          /* REMOTE == slurmstepd       */
	S_TYPE_ALLOCATOR,       /* ALLOCATOR == sbatch/salloc */
	S_TYPE_SLURMD,          /* SLURMD == slurmd           */
	S_TYPE_JOB_SCRIPT,      /* JOB_SCRIPT == prolog/epilog*/
};

/*
 *  SPANK plugin hook types:
 */
typedef enum step_fn {
	SPANK_INIT = 0,
	SPANK_JOB_PROLOG = 2,
	SPANK_INIT_POST_OPT,
	LOCAL_USER_INIT,
	STEP_USER_INIT,
	STEP_TASK_INIT_PRIV,
	STEP_USER_TASK_INIT,
	STEP_TASK_POST_FORK,
	STEP_TASK_EXIT,
	SPANK_JOB_EPILOG,
	SPANK_SLURMD_EXIT,
	SPANK_EXIT
} step_fn_t;

/*
 *  Job information in prolog/epilog context:
 */
struct job_script_info {
	uint32_t  jobid;
	uid_t     uid;
	gid_t gid;
};

struct spank_handle {
#   define SPANK_MAGIC 0x00a5a500
	int                  magic;  /* Magic identifier to ensure validity. */
	struct spank_plugin *plugin; /* Current plugin using handle          */
	step_fn_t            phase;  /* Which spank fn are we called from?   */
	void               * job;    /* Reference to current srun|slurmd job */
	stepd_step_task_info_t * task;   /* Reference to current
					      * task (if valid) */
	struct spank_stack  *stack;  /* Reference to the current plugin stack*/
};

/*
 *  SPANK stack. The stack of loaded plugins and associated state.
 */
struct spank_stack {
	enum spank_context_type type;/*  Type of context for this stack      */
	List plugin_list;	     /*  Stack of spank plugins              */
	List option_cache;           /*  Cache of plugin options in this ctx */
	int  spank_optval;           /*  optvalue for next plugin option     */
	const char * plugin_path;    /*  default path to search for plugins  */
};

/*
 *  The global spank plugin stack:
 */
static struct spank_stack *global_spank_stack = NULL;

/*
 *  Forward declarations
 */
static int _spank_plugin_options_cache(struct spank_plugin *p);
static int _spank_stack_load (struct spank_stack *stack, const char *file);
static void _spank_plugin_destroy (struct spank_plugin *);
static void _spank_plugin_opt_destroy (struct spank_plugin_opt *);
static int spank_stack_get_remote_options(struct spank_stack *, job_options_t);
static int spank_stack_get_remote_options_env (struct spank_stack *, char **);
static int spank_stack_set_remote_options_env (struct spank_stack * stack);
static int dyn_spank_set_job_env (const char *var, const char *val, int ovwt);
static char *_opt_env_name(struct spank_plugin_opt *p, char *buf, size_t siz);

static void spank_stack_destroy (struct spank_stack *stack)
{
	FREE_NULL_LIST (stack->plugin_list);
	FREE_NULL_LIST (stack->option_cache);
	xfree (stack->plugin_path);
	xfree (stack);
}

static struct spank_stack *
spank_stack_create (const char *file, enum spank_context_type type)
{
	slurm_conf_t *conf;
	struct spank_stack *stack = xmalloc (sizeof (*stack));

	conf = slurm_conf_lock();
	stack->plugin_path = xstrdup (conf->plugindir);
	slurm_conf_unlock();

	stack->type = type;
	stack->spank_optval = 0xfff;
	stack->plugin_list =
		list_create ((ListDelF) _spank_plugin_destroy);
	stack->option_cache =
		list_create ((ListDelF) _spank_plugin_opt_destroy);

	if (_spank_stack_load (stack, file) < 0) {
		spank_stack_destroy (stack);
		return (NULL);
	}

	return (stack);
}

static List get_global_option_cache (void)
{
	if (global_spank_stack)
		return (global_spank_stack->option_cache);
	else
		return (NULL);
}


static int plugin_in_list (List l, struct spank_plugin *sp)
{
	int rc = 0;
	struct spank_plugin *p;
	ListIterator i = list_iterator_create (l);
	while ((p = list_next (i))) {
		if (p->fq_path == sp->fq_path) {
			rc = 1;
			break;
		}
	}
	list_iterator_destroy (i);
	return (rc);
}

static void _argv_append(char ***argv, int ac, const char *newarg)
{
	*argv = xrealloc(*argv, (++ac + 1) * sizeof(char *));
	(*argv)[ac] = NULL;
	(*argv)[ac - 1] = xstrdup(newarg);
	return;
}

typedef enum {
   CF_ERROR = 0,
   CF_OPTIONAL,
   CF_REQUIRED,
   CF_INCLUDE,
} cf_line_t;

static cf_line_t _plugin_stack_line_type (const char *str)
{
	if (xstrcmp(str, REQUIRED) == 0)
		return (CF_REQUIRED);
	else if (xstrcmp(str, OPTIONAL) == 0)
		return (CF_OPTIONAL);
	else if (xstrcmp(str, INCLUDE) == 0)
		return (CF_INCLUDE);
	else {
		error("spank: Invalid option \"%s\". Must be %s, %s or %s",
		     str, REQUIRED, OPTIONAL, INCLUDE);
		return (CF_ERROR);
	}
}


static int
_plugin_stack_parse_line(char *line, char **plugin, int *acp, char ***argv,
			 cf_line_t * type)
{
	int ac;
	const char *separators = " \t\n";
	char *path;
	char *option;
	char *s;
	char **av;
	char *sp;

	*plugin = NULL;
	*argv = NULL;
	*acp = 0;

	/* Nullify any comments
	 */
	if ((s = strchr(line, '#')))
		*s = '\0';

	if (!(option = strtok_r(line, separators, &sp)))
		return (0);

	if (((*type) = _plugin_stack_line_type(option)) == CF_ERROR)
		return (-1);

	if (!(path = strtok_r(NULL, separators, &sp)))
		return (-1);

	ac = 0;
	av = NULL;

	while ((s = strtok_r(NULL, separators, &sp)))
		_argv_append(&av, ac++, s);

	*plugin = xstrdup(path);
	*argv = av;
	*acp = ac;

	return (0);
}

static struct spank_plugin *_spank_plugin_create(struct spank_stack *stack,
						 char *path, int ac,
						 char **av, bool required)
{
	struct spank_plugin *plugin;
	plugin_handle_t p;
	plugin_err_t e;
	struct spank_plugin_operations ops;

	if ((e = plugin_load_from_file(&p, path)) != EPLUGIN_SUCCESS) {
		error ("spank: %s: %s", path, plugin_strerror(e));
		return NULL;
	}

	if (plugin_get_syms(p, n_spank_syms, spank_syms, (void **)&ops) == 0) {
		error("spank: \"%s\" exports 0 symbols", path);
		return NULL;
	}

	plugin = xmalloc(sizeof(struct spank_plugin));

	plugin->fq_path = path;	/* fq_path is xstrdup'd in *process_line */
	plugin->plugin = p;
	plugin->name = plugin_get_name(p);	/* no need to dup */
	plugin->required = required;
	plugin->ac = ac;
	plugin->argv = av;
	plugin->ops = ops;
	plugin->stack = stack;

	/*
	 *  Do not load static plugin options table in allocator context.
	 */
	if (stack->type != S_TYPE_ALLOCATOR)
		plugin->opts = plugin_get_sym(p, "spank_options");

	return (plugin);
}

void _spank_plugin_destroy(struct spank_plugin *sp)
{
	if (sp == NULL)
		return;

	xfree(sp->fq_path);

	/* No need to free "name" it was defined within plugin */
	sp->name = NULL;

	plugin_unload(sp->plugin);
	sp->plugin = NULL;
	if (sp->argv) {
		int i;
		for (i = 0; sp->argv[i]; i++)
			xfree(sp->argv[i]);
		xfree(sp->argv);
	}
	xfree(sp);
	return;
}

static char *
_spank_plugin_find (const char *path, const char *file)
{
	char dir [4096];
	char *p, *entry;
	int pathlen = strlen (path);

	if (strlcpy(dir, path, sizeof (dir)) > sizeof (dir))
		return (NULL);

	/*
	 * Ensure PATH ends with a :
	 */
	if (dir[pathlen - 1] != ':') {
		dir[pathlen] = ':';
		dir[pathlen+1] = '\0';
	}

	entry = dir;
	while ((p = strchr(entry, ':'))) {
		char *fq_path;
		*(p++) = '\0';

		fq_path = xstrdup (entry);
		if (entry [strlen(entry) - 1] != '/')
			xstrcatchar (fq_path, '/');
		xstrcat (fq_path, file);

		if (plugin_peek (fq_path, NULL, 0, NULL) != SLURM_ERROR)
			return (fq_path);

		xfree (fq_path);
		entry = p;
	}

	return (NULL);
}

static int _spank_conf_include (struct spank_stack *,
	const char *, int, const char *);

static int
spank_stack_plugin_valid_for_context (struct spank_stack *stack,
	struct spank_plugin *p)
{
	switch (stack->type) {
	case S_TYPE_JOB_SCRIPT:
		if (p->ops.job_prolog || p->ops.job_epilog)
			return (1);
		break;
	case S_TYPE_SLURMD:
		/*
		 * Set flags if prolog/epilog exist, but only return 1 if
		 * slurmd_exit is defined so that spank_init is only called when
		 * slurmd_exit exists. slurmd needs to know whether to create a
		 * spank stepd to run spank prolog/epilog in job_script context.
		 */
		if (p->ops.job_prolog)
			has_prolog = true;
		if (p->ops.job_epilog)
			has_epilog = true;

		if (p->ops.slurmd_exit)
			return (1);
		break;
	case S_TYPE_LOCAL:
	case S_TYPE_ALLOCATOR:
	case S_TYPE_REMOTE:
		/*
		 *  For backwards compatibility: All plugins were
		 *   always loaded in these contexts, so continue
		 *   to do so
		 */
		return (1);
	default:
		return (0);
	}
	return (0);
}

static int
_spank_stack_process_line(struct spank_stack *stack,
	const char *file, int line, char *buf)
{
	char **argv;
	int ac;
	char *path;
	cf_line_t type = CF_REQUIRED;
	bool required;

	struct spank_plugin *p;

	if (_plugin_stack_parse_line(buf, &path, &ac, &argv, &type) < 0) {
		error("spank: %s:%d: Invalid line. Ignoring.", file, line);
		return (0);
	}

       if (type == CF_INCLUDE) {
	       int rc = _spank_conf_include (stack, file, line, path);
	       xfree (path);
	       return (rc);
       }

	if (path == NULL)	/* No plugin listed on this line */
		return (0);

	if (path[0] != '/') {
		char *f;

		if ((f = _spank_plugin_find (stack->plugin_path, path))) {
			xfree (path);
			path = f;
		}
	}

	required = (type == CF_REQUIRED);
	if (!(p = _spank_plugin_create(stack, path, ac, argv, required))) {
		if (required)
			error ("spank: %s:%d:"
			       " Failed to load plugin %s. Aborting.",
			       file, line, path);
		else
			verbose ("spank: %s:%d:"
				 "Failed to load optional plugin %s. Ignored.",
				 file, line, path);
		return (required ? -1 : 0);
	}

	if (plugin_in_list (stack->plugin_list, p)) {
		error ("spank: %s: cowardly refusing to load a second time",
			p->fq_path);
		_spank_plugin_destroy (p);
		return (0);
	}

	if (!spank_stack_plugin_valid_for_context (stack, p)) {
		debug2 ("spank: %s: no callbacks in this context", p->fq_path);
		_spank_plugin_destroy (p);
		return (0);
	}

	debug ("spank: %s:%d: Loaded plugin %s",
			file, line, xbasename (p->fq_path));

	list_append (stack->plugin_list, p);
	_spank_plugin_options_cache(p);

	return (0);
}

static int _spank_stack_load(struct spank_stack *stack, const char *path)
{
	int rc = 0;
	int line;
	char buf[4096];
	int fd;
	FILE *fp;

	debug ("spank: opening plugin stack %s", path);

	/*
	 *  Try to open plugstack.conf. A missing config file is not an
	 *   error, but is equivalent to an empty file.
	 */
	if ((fd = open(path, O_RDONLY | O_CLOEXEC)) < 0 ||
	    (fp = fdopen(fd, "r")) == NULL) {
		if (errno == ENOENT)
			return (0);
		error("spank: Failed to open %s: %m", path);
		return (-1);
	}

	line = 1;
	while (fgets(buf, sizeof(buf), fp)) {
		rc = _spank_stack_process_line(stack, path, line, buf);
		if (rc < 0)
			break;
		line++;
	}

	fclose(fp);
	return (rc);
}

static int _spank_conf_include (struct spank_stack *stack,
		const char *file, int lineno, const char *pattern)
{
	int rc = 0;
	glob_t gl;
	size_t i;
	char *copy = NULL;

	if (pattern == NULL) {
		error ("%s: %d: Invalid include directive", file, lineno);
		return (SLURM_ERROR);
	}

	if (pattern[0] != '/') {
		char *dirc = xstrdup (file);
		char *dname = dirname (dirc);

		if (dname != NULL)  {
			xstrfmtcat (copy, "%s/%s", dname, pattern);
			pattern = copy;
		}
		xfree (dirc);
	}

	debug ("%s: %d: include \"%s\"", file, lineno, pattern);

	rc = glob (pattern, 0, NULL, &gl);
	switch (rc) {
	  case 0:
	  	for (i = 0; i < gl.gl_pathc; i++) {
			rc = _spank_stack_load (stack, gl.gl_pathv[i]);
			if (rc < 0)
				break;
		}
	  	break;
	  case GLOB_NOMATCH:
		break;
	  case GLOB_NOSPACE:
		errno = ENOMEM;
		break;
	  case GLOB_ABORTED:
		verbose ("%s:%d: cannot read dir %s: %m",
			file, lineno, pattern);
		break;
	  default:
		error ("Unknown glob(3) return code = %d", rc);
		break;
	}

	xfree (copy);
	globfree (&gl);
	return (rc);
}

static int
_spank_handle_init(struct spank_handle *spank, struct spank_stack *stack,
		void * arg, int taskid, step_fn_t fn)
{
	memset(spank, 0, sizeof(*spank));
	spank->magic = SPANK_MAGIC;
	spank->plugin = NULL;

	spank->phase = fn;
	spank->stack = stack;

	if (arg != NULL) {
		spank->job = arg;
		if (stack->type == S_TYPE_REMOTE && taskid >= 0) {
			spank->task = ((stepd_step_rec_t *) arg)->task[taskid];
		}
	}
	return (0);
}

static const char *_step_fn_name(step_fn_t type)
{
	switch (type) {
	case SPANK_INIT:
		return ("init");
	case SPANK_JOB_PROLOG:
		return ("job_prolog");
	case SPANK_INIT_POST_OPT:
		return ("init_post_opt");
	case LOCAL_USER_INIT:
		return ("local_user_init");
	case STEP_USER_INIT:
		return ("user_init");
	case STEP_TASK_INIT_PRIV:
		return ("task_init_privileged");
	case STEP_USER_TASK_INIT:
		return ("task_init");
	case STEP_TASK_POST_FORK:
		return ("task_post_fork");
	case STEP_TASK_EXIT:
		return ("task_exit");
	case SPANK_JOB_EPILOG:
		return ("job_epilog");
	case SPANK_SLURMD_EXIT:
		return ("slurmd_exit");
	case SPANK_EXIT:
		return ("exit");
	}

	/* NOTREACHED */
	return ("unknown");
}

static spank_f *spank_plugin_get_fn (struct spank_plugin *sp, step_fn_t type)
{
	switch (type) {
	case SPANK_INIT:
		return (sp->ops.init);
	case SPANK_JOB_PROLOG:
		return (sp->ops.job_prolog);
	case SPANK_INIT_POST_OPT:
		return (sp->ops.init_post_opt);
	case LOCAL_USER_INIT:
		return (sp->ops.local_user_init);
	case STEP_USER_INIT:
		return (sp->ops.user_init);
	case STEP_TASK_INIT_PRIV:
		return (sp->ops.task_init_privileged);
	case STEP_USER_TASK_INIT:
		return (sp->ops.user_task_init);
	case STEP_TASK_POST_FORK:
		return (sp->ops.task_post_fork);
	case STEP_TASK_EXIT:
		return (sp->ops.task_exit);
	case SPANK_JOB_EPILOG:
		return (sp->ops.job_epilog);
	case SPANK_SLURMD_EXIT:
		return (sp->ops.slurmd_exit);
	case SPANK_EXIT:
		return (sp->ops.exit);
	default:
		error("Unhandled spank function type=%d", type);
		return (NULL);
	}
	return (NULL);
}

static int _do_call_stack(struct spank_stack *stack,
	step_fn_t type, void * job, int taskid)
{
	int rc = 0;
	ListIterator i;
	struct spank_plugin *sp;
	struct spank_handle spank[1];
	const char *fn_name;

	if (!stack)
		return (-1);

	if (_spank_handle_init(spank, stack, job, taskid, type) < 0) {
		error("spank: Failed to initialize handle for plugins");
		return (-1);
	}

	fn_name = _step_fn_name(type);

	i = list_iterator_create(stack->plugin_list);
	while ((sp = list_next(i))) {
		const char *name = xbasename(sp->fq_path);
		spank_f *spank_fn;

		spank->plugin = sp;

		spank_fn = spank_plugin_get_fn (sp, type);
		if (!spank_fn)
			continue;

		rc = (*spank_fn) (spank, sp->ac, sp->argv);
		debug2("spank: %s: %s = %d", name, fn_name, rc);

		if ((rc < 0) && sp->required) {
			error("spank: required plugin %s: "
			      "%s() failed with rc=%d", name, fn_name, rc);
			break;
		} else
			rc = 0;
	}

	list_iterator_destroy(i);

	return (rc);
}

struct spank_stack *spank_stack_init(enum spank_context_type context)
{
	char *path;
	struct spank_stack *stack = NULL;

	if (!(path = xstrdup(slurm_conf.plugstack)))
		path = get_extra_conf_path("plugstack.conf");

	stack = spank_stack_create(path, context);
	xfree(path);

	return stack;
}

int _spank_init(enum spank_context_type context, stepd_step_rec_t * job)
{
	struct spank_stack *stack;

	if (!(stack = spank_stack_init (context)))
		return (-1);
	global_spank_stack = stack;

	return (_do_call_stack(stack, SPANK_INIT, job, -1));
}

static int spank_stack_post_opt (struct spank_stack * stack,
				 stepd_step_rec_t *job)
{
	/*
	 *  Get any remote options from job launch message:
	 */
	if (spank_stack_get_remote_options(stack, job->options) < 0) {
		error("spank: Unable to get remote options");
		return (-1);
	}

	/*
	 *  Get any remote option passed thru environment
	 */
	if (spank_stack_get_remote_options_env(stack, job->env) < 0) {
		error("spank: Unable to get remote options from environment");
		return (-1);
	}

	/*
	 * Now clear any remaining options passed through environment
	 */
	spank_clear_remote_options_env (job->env);

	/*
	 *  Now that all options have been processed, we can
	 *   call the post_opt handlers here in remote context.
	 */
	return (_do_call_stack(stack, SPANK_INIT_POST_OPT, job, -1));

}

static int spank_init_remote (stepd_step_rec_t *job)
{
	if (_spank_init (S_TYPE_REMOTE, job) < 0)
		return (-1);

	/*
	 * _spank_init initializes global_spank_stack
	 */
	return (spank_stack_post_opt (global_spank_stack, job));
}

int spank_init (stepd_step_rec_t * job)
{
	if (job)
		return spank_init_remote (job);
	else
		return _spank_init (S_TYPE_LOCAL, NULL);
}

int spank_init_allocator (void)
{
	return _spank_init (S_TYPE_ALLOCATOR, NULL);
}

int spank_slurmd_init (void)
{
	return _spank_init (S_TYPE_SLURMD, NULL);
}

int spank_init_post_opt (void)
{
	struct spank_stack *stack = global_spank_stack;

	/*
	 *  Set remote options in our environment and the
	 *   spank_job_env so that we can always pull them out
	 *   on the remote side and/or job prolog epilog.
	 */
	spank_stack_set_remote_options_env (stack);

	return (_do_call_stack(stack, SPANK_INIT_POST_OPT, NULL, -1));
}

int spank_user(stepd_step_rec_t * job)
{
	return (_do_call_stack(global_spank_stack, STEP_USER_INIT, job, -1));
}

int spank_local_user(struct spank_launcher_job_info *job)
{
	return (_do_call_stack(global_spank_stack, LOCAL_USER_INIT, job, -1));
}

int spank_task_privileged(stepd_step_rec_t *job, int taskid)
{
	return (_do_call_stack(global_spank_stack, STEP_TASK_INIT_PRIV, job, taskid));
}

int spank_user_task(stepd_step_rec_t * job, int taskid)
{
	return (_do_call_stack(global_spank_stack, STEP_USER_TASK_INIT, job, taskid));
}

int spank_task_post_fork(stepd_step_rec_t * job, int taskid)
{
	return (_do_call_stack(global_spank_stack, STEP_TASK_POST_FORK, job, taskid));
}

int spank_task_exit(stepd_step_rec_t * job, int taskid)
{
	return (_do_call_stack(global_spank_stack, STEP_TASK_EXIT, job, taskid));
}

int spank_slurmd_exit (void)
{
	int rc;
	rc =  _do_call_stack (global_spank_stack, SPANK_SLURMD_EXIT, NULL, 0);
	spank_stack_destroy (global_spank_stack);
	global_spank_stack = NULL;
	return (rc);
}

int spank_fini(stepd_step_rec_t * job)
{
	int rc = _do_call_stack(global_spank_stack, SPANK_EXIT, job, -1);

	spank_stack_destroy (global_spank_stack);
	global_spank_stack = NULL;

	return (rc);
}

/*
 *  Run job_epilog or job_prolog callbacks in a private spank context.
 */
static int spank_job_script(step_fn_t fn, uint32_t jobid, uid_t uid, gid_t gid)
{
	int rc = 0;
	struct spank_stack *stack;
	struct job_script_info jobinfo = { jobid, uid, gid };

	stack = spank_stack_init (S_TYPE_JOB_SCRIPT);
	if (!stack)
		return (-1);
	global_spank_stack = stack;

	rc = _do_call_stack (stack, fn, &jobinfo, -1);

	spank_stack_destroy (stack);
	global_spank_stack = NULL;
	return (rc);
}

int spank_job_prolog(uint32_t jobid, uid_t uid, gid_t gid)
{
	return spank_job_script(SPANK_JOB_PROLOG, jobid, uid, gid);
}

int spank_job_epilog(uint32_t jobid, uid_t uid, gid_t gid)
{
	return spank_job_script(SPANK_JOB_EPILOG, jobid, uid, gid);
}

/*
 *  SPANK options functions
 */

static int _spank_next_option_val(struct spank_stack *stack)
{
	return (stack->spank_optval++);
}

static struct spank_option * _spank_option_copy(struct spank_option *opt)
{
	struct spank_option *copy = xmalloc (sizeof (*copy));

	memset (copy, 0, sizeof (*copy));

	copy->name = xstrdup (opt->name);
	copy->has_arg = opt->has_arg;
	copy->val = opt->val;
	copy->cb = opt->cb;

	if (opt->arginfo)
		copy->arginfo = xstrdup (opt->arginfo);
	if (opt->usage)
		copy->usage = xstrdup (opt->usage);

	return (copy);
}

static void _spank_option_destroy(struct spank_option *opt)
{
	xfree (opt->name);
	xfree (opt->arginfo);
	xfree (opt->usage);
	xfree (opt);
}

static struct spank_plugin_opt *_spank_plugin_opt_create(struct
							 spank_plugin *p,
							 struct
							 spank_option *opt,
							 int disabled)
{
	struct spank_plugin_opt *spopt = xmalloc(sizeof(*spopt));
	spopt->opt = _spank_option_copy (opt);
	spopt->plugin = p;
	spopt->optval = _spank_next_option_val(p->stack);
	spopt->found = 0;
	spopt->optarg = NULL;
	spopt->set = false;
	spopt->set_by_env = false;

	spopt->disabled = disabled;

	return (spopt);
}

void _spank_plugin_opt_destroy(struct spank_plugin_opt *spopt)
{
	_spank_option_destroy (spopt->opt);
	xfree(spopt->optarg);
	xfree(spopt);
}

static int _opt_by_val(struct spank_plugin_opt *opt, int *optvalp)
{
	return (opt->optval == *optvalp);
}

static int _opt_by_name(struct spank_plugin_opt *opt, char *optname)
{
	return (xstrcmp(opt->opt->name, optname) == 0);
}

static int
_spank_option_register(struct spank_plugin *p, struct spank_option *opt)
{
	int disabled = 0;
	struct spank_plugin_opt *spopt;
	struct spank_stack *stack;
	List option_cache;

	stack = p->stack;
	if (stack == NULL) {
		error ("spank: %s: can't determine plugin context", p->name);
		return (ESPANK_BAD_ARG);
	}
	option_cache = stack->option_cache;

	spopt = list_find_first(option_cache,
			(ListFindF) _opt_by_name, opt->name);
	if (spopt) {
		struct spank_plugin *q = spopt->plugin;
		info("spank: option \"%s\" provided by both %s and %s",
				opt->name, xbasename(p->fq_path),
				xbasename(q->fq_path));
		/*
		 *  Disable this option, but still cache it, in case
		 *    options are loaded in a different order on the
		 *    remote side.
		 */
		disabled = 1;
	}

	if ((strlen(opt->name) > SPANK_OPTION_MAXLEN)) {
		error("spank: option \"%s\" provided by %s too long. "
		      "Ignoring.", opt->name, p->name);
		return (ESPANK_NOSPACE);
	}

	debug ("SPANK: appending plugin option \"%s\"", opt->name);
	list_append(option_cache, _spank_plugin_opt_create(p, opt, disabled));

	return (ESPANK_SUCCESS);
}

spank_err_t spank_option_register(spank_t sp, struct spank_option *opt)
{
	if (sp->phase != SPANK_INIT)
		return (ESPANK_BAD_ARG);

	if (!sp->plugin)
		error ("Uh, oh, no current plugin!");

	if (!opt || !opt->name || !opt->usage)
		return (ESPANK_BAD_ARG);

	return (_spank_option_register(sp->plugin, opt));
}

static int _spank_plugin_options_cache(struct spank_plugin *p)
{
	struct spank_option *opt = p->opts;

	if ((opt == NULL) || opt->name == NULL)
		return (0);

	for (; opt && opt->name != NULL; opt++)
		_spank_option_register(p, opt);

	return (0);
}

static int _add_one_option(struct option **optz,
			   struct spank_plugin_opt *spopt)
{
	struct option opt;

	opt.name = spopt->opt->name;
	opt.has_arg = spopt->opt->has_arg;
	opt.flag = NULL;
	opt.val = spopt->optval;

	if (optz_add(optz, &opt) < 0) {
		if (errno == EEXIST) {
			error ("Ignoring conflicting option \"%s\" "
			       "in plugin \"%s\"",
			       opt.name, spopt->plugin->name);
		} else {
			error("Unable to add option \"%s\" "
			      "from plugin \"%s\"",
			      opt.name, spopt->plugin->name);
		}

		return (-1);
	}

	return (0);
}


struct option *spank_option_table_create(const struct option *orig)
{
	struct spank_plugin_opt *spopt;
	struct option *opts = NULL;
	ListIterator i = NULL;

	List option_cache = get_global_option_cache();
	if (option_cache == NULL)
		return (NULL);

	opts = optz_create();

	/*
	 *  Start with original options:
	 */
	if ((orig != NULL) && (optz_append(&opts, orig) < 0)) {
		optz_destroy(opts);
		return (NULL);
	}

	if (option_cache == NULL || (list_count(option_cache) == 0))
		return (opts);

	i = list_iterator_create(option_cache);
	while ((spopt = list_next(i))) {
		if (!spopt->disabled && (_add_one_option (&opts, spopt) < 0))
			spopt->disabled = 1;
	}

	list_iterator_destroy(i);

	return (opts);
}

void spank_option_table_destroy(struct option *optz)
{
	optz_destroy(optz);
}

static int _do_option_cb(struct spank_plugin_opt *opt, const char *arg,
			 int remote)
{
	int rc = 0;

	xassert(opt);
	xassert(arg);

	/*
	 *  Call plugin callback if such a one exists
	 */
	if (opt->opt->cb
	    && (rc = ((*opt->opt->cb) (opt->opt->val, arg, remote))))
		return (rc);

	/*
	 *  Set optarg and "found" so that option will be forwarded
	 *    to remote side.
	 */
	if (opt->opt->has_arg) {
		xfree(opt->optarg);
		opt->optarg = xstrdup(arg);
	}
	opt->found = 1;
	opt->set = true;

	return rc;
}

extern int spank_process_option(int optval, const char *arg)
{
	struct spank_plugin_opt *opt;
	int rc = 0;
	List option_cache = get_global_option_cache();

	if (option_cache == NULL || (list_count(option_cache) == 0)) {
		debug("No spank option cache");
		return (-1);
	}

	opt = list_find_first(option_cache, (ListFindF)_opt_by_val, &optval);
	if (!opt) {
		error("Failed to find spank option for optval: %d", optval);
		return (-1);
	}

	if ((rc = _do_option_cb(opt, arg, 0))) {
		error("Invalid --%s argument: %s", opt->opt->name, arg);
		return (rc);
	}

	return (0);
}

extern int spank_process_env_options()
{
	char var[1024];
	const char *arg;
	struct spank_plugin_opt *option;
	ListIterator i;
	List option_cache = get_global_option_cache();
	int rc = 0;

	if (option_cache == NULL || (list_count(option_cache) == 0))
		return 0;

	i = list_iterator_create(option_cache);
	while ((option = list_next(i))) {
		char *env_name;
		env_name = xstrdup_printf("SLURM_SPANK_%s",
					  _opt_env_name(option, var,
							sizeof(var)));
		if (!(arg = getenv(env_name))) {
			xfree(env_name);
			continue;
		}

		if ((rc = _do_option_cb(option, arg, 0))) {
			error("Invalid argument (%s) for environment variable: %s",
			      arg, env_name);
			xfree(env_name);
			break;
		}
		option->set_by_env = true;
		xfree(env_name);
	}
	list_iterator_destroy(i);

	return rc;
}

static char *
_find_word_boundary(char *str, char *from, char **next)
{
	char *p = from;

	/*
	 * Back up past any non-whitespace if we are pointing in
	 *  the middle of a word.
	 */
	while ((p != str) && !isspace ((int)*p))
		--p;

	/*
	 * Next holds next word boundary
	 */
	*next = p+1;

	/*
	 * Now move back to the end of the previous word
	 */
	while ((p != str) && isspace ((int)*p))
		--p;

	if (p == str) {
		*next = str;
		return (NULL);
	}

	return (p+1);
}

static char *
_get_next_segment (char **from, int width, char *buf, int bufsiz)
{
	int len;
	char * seg = *from;
	char *p;

	if (**from == '\0')
		return (NULL);

	if ((len = strlen (*from)) <= width) {
		*from = *from + len;
		return (seg);
	}

	if (!(p = _find_word_boundary (seg, *from + width, from))) {
		/*
		 *	Need to break up a word. Use user-supplied buffer.
		 */
		strlcpy (buf, seg, width+1);
		buf [width - 1]  = '-';
		/*
		 * Adjust from to character eaten by '-'
		 *  And return pointer to buf.
		 */
		*from = seg + width - 1;
		return (buf);
	}

	*p = '\0';

	return (seg);
}

static int
_term_columns (void)
{
	char *val;
	int  cols = 80;

	if ((val = getenv ("COLUMNS"))) {
		char *p;
		long lval = strtol (val, &p, 10);

		if (p && (*p == '\0'))
			cols = (int) lval;
	}

	return (cols);
}

static void
_spank_opt_print(struct spank_option *opt, FILE * fp, int left_pad, int width)
{
	int n;
	char *equals = "";
	char *arginfo = "";
	char *p, *q;
	char info [81];
	char seg [81];
	char buf [4096];

	int  columns = _term_columns ();
	int  descrsiz = columns - width;

	if (opt->arginfo) {
		equals = "=";
		arginfo = opt->arginfo;
	}

	n = snprintf(info, sizeof(info), "%*s--%s%s%s",
		     left_pad, "", opt->name, equals, arginfo);

	if ((n < 0) || (n > columns)) {
		const char trunc[] = "+";
		int len = strlen(trunc);
		p = info + columns - len - 1;
		snprintf(p, len + 1, "%s", trunc);
	}


	q = buf;
	strlcpy (buf, opt->usage, sizeof (buf));

	p = _get_next_segment (&q, descrsiz, seg, sizeof (seg));

	if (n < width)
		fprintf(fp, "%-*s%s\n", width, info, p);
	else
		fprintf(fp, "\n%s\n%*s%s\n", info, width, "", p);

	/* Get remaining line-wrapped lines.
	 */
	while ((p = _get_next_segment (&q, descrsiz, seg, sizeof (seg))))
		fprintf(fp, "%*s%s\n", width, "", p);

	return;
}

int spank_print_options(FILE * fp, int left_pad, int width)
{
	struct spank_plugin_opt *p;
	ListIterator i;
	List option_cache = get_global_option_cache();

	if ((option_cache == NULL) || (list_count(option_cache) == 0))
		return (0);

	fprintf(fp, "\nOptions provided by plugins:\n");

	i = list_iterator_create(option_cache);
	while ((p = list_next(i))) {
		if (p->disabled)
			continue;
		_spank_opt_print(p->opt, fp, left_pad, width);
	}
	list_iterator_destroy(i);

	return (0);
}

#define OPT_TYPE_SPANK 0x4400

static char _canonical_char (char c)
{
	if (!isalnum ((int)c))
		return '_';
	else
		return c;
}

/*
 *  Create spank option environment variable name from option name.
 */
static char * _opt_env_name (struct spank_plugin_opt *p, char *buf, size_t siz)
{
	const char * name = p->opt->name;
	const char * pname = p->plugin->name;
	int i, n;

	strlcpy (buf, SPANK_OPTION_ENV_PREFIX, siz);

	/*
	 *  First append the plugin name associated with this option:
	 */
	n = 0;
	for (i = strlen (buf); i < siz - 1 && n < strlen (pname); i++)
	    buf[i] = _canonical_char (pname[n++]);

	/*
	 *  Append _
	 */
	buf[i] = '_';
	buf[i+1] = '\0';

	/*
	 *  Now incorporate the option name:
	 */
	n = 0;
	for (i = strlen (buf); i < siz - 1 && n < strlen (name); i++)
	    buf[i] = _canonical_char (name[n++]);
	buf[i] = '\0';

	return (buf);
}

static int _option_setenv (struct spank_plugin_opt *option)
{
	char var[1024];
	char *arg = option->optarg;

	_opt_env_name(option, var, sizeof(var));

	/*
	 * Old glibc behavior was to set the variable with an empty value if
	 * the option was NULL. Newer glibc versions will segfault instead,
	 * so feed it an empty string when necessary to maintain backwards
	 * compatibility.
	 */
	if (!option->optarg)
		arg = "";

	if (setenv(var, arg, 1) < 0)
		error("failed to set %s=%s in env", var, arg);

	/*
	 * Use the possibly-NULL value and let the command itself figure
	 * out how to handle it. This will usually result in "(null)"
	 * instead of "" used above.
	 */

	if (dyn_spank_set_job_env(var, option->optarg, 1) < 0)
		error("failed to set %s=%s in env", var, option->optarg);

	return (0);
}

static int spank_stack_set_remote_options_env (struct spank_stack *stack)
{
	struct spank_plugin_opt *p;
	ListIterator i;
	List option_cache;

	if (stack == NULL)
		return (0);
	option_cache = stack->option_cache;

	if ((option_cache == NULL) || (list_count(option_cache) == 0))
		return (0);

	i = list_iterator_create(option_cache);
	while ((p = list_next(i))) {
		if (p->found)
			_option_setenv (p);
	}
	list_iterator_destroy(i);
	return (0);
}

int spank_set_remote_options(job_options_t opts)
{
	struct spank_plugin_opt *p;
	ListIterator i;
	List option_cache;

	if (global_spank_stack == NULL)
		return (0);
	option_cache = global_spank_stack->option_cache;

	if ((option_cache == NULL) || (list_count(option_cache) == 0))
		return (0);

	i = list_iterator_create(option_cache);
	while ((p = list_next(i))) {
		char optstr[1024];

		if (!p->found)
			continue;

		snprintf(optstr, sizeof(optstr), "%s:%s",
			 p->opt->name, p->plugin->name);

		job_options_append(opts, OPT_TYPE_SPANK, optstr,
				   p->optarg);
	}
	list_iterator_destroy(i);
	return (0);
}

struct opt_find_args {
	const char *optname;
	const char *plugin_name;
};

static int _opt_find(struct spank_plugin_opt *p,
		     struct opt_find_args *args)
{
	if (xstrcmp(p->plugin->name, args->plugin_name) != 0)
		return (0);
	if (xstrcmp(p->opt->name, args->optname) != 0)
		return (0);
	return (1);
}

static struct spank_plugin_opt *
spank_stack_find_option_by_name(struct spank_stack *stack, const char *str)
{
	struct spank_plugin_opt *opt = NULL;
	struct opt_find_args args;
	char buf[256];
	char *name;
	List option_cache = stack->option_cache;

	if (strlcpy(buf, str, sizeof(buf)) >= sizeof(buf)) {
		error("plugin option \"%s\" too big. Ignoring.", str);
		return (NULL);
	}

	if (!(name = strchr(buf, ':'))) {
		error("Malformed plugin option \"%s\" received. Ignoring",
		      str);
		return (NULL);
	}

	*(name++) = '\0';

	args.optname = buf;
	args.plugin_name = name;

	if (option_cache) {
		opt = list_find_first(option_cache, (ListFindF) _opt_find,
				      &args);
		if (opt == NULL) {
			error("Warning: SPANK plugin \"%s\" option \"%s\" not "
			      "found", name, buf);
			return (NULL);
		}
	} else {
		error("Warning: no SPANK plugin found to process option \"%s\"",
		      name);
		return (NULL);
	}

	return (opt);
}

spank_err_t
spank_option_getopt (spank_t sp, struct spank_option *opt, char **argp)
{
	const char *val;
	char var[1024];
	List option_cache;
	struct spank_plugin_opt *spopt;

	if (argp)
		*argp = NULL;

	if (!sp->plugin) {
		error ("spank_option_getopt: Not called from a plugin!?");
		return (ESPANK_NOT_AVAIL);
	}

	if ((sp->phase == SPANK_INIT) ||
	    (sp->phase == SPANK_INIT_POST_OPT) ||
	    (sp->phase == STEP_TASK_POST_FORK) ||
	    (sp->phase == SPANK_SLURMD_EXIT) ||
	    (sp->phase == SPANK_EXIT))
		return (ESPANK_NOT_AVAIL);

	if (!opt || !opt->name)
		return (ESPANK_BAD_ARG);

	if (opt->has_arg && !argp)
		return (ESPANK_BAD_ARG);

	/*
	 *   First check the cache:
	 */
	option_cache = sp->stack->option_cache;
	spopt = list_find_first (option_cache,
				 (ListFindF) _opt_by_name,
				 opt->name);
	if (spopt) {
		/*
		 *  Return failure if option is cached but hasn't been
		 *   used on the command line or specified by user.
		 */
		if (!spopt->found)
			return (ESPANK_ERROR);

		if (opt->has_arg && argp)
			*argp = spopt->optarg;
		return (ESPANK_SUCCESS);
	}

	/*
	 *  Otherwise, check current environment:
	 *
	 *  We need to check for variables that start with either
	 *   the default spank option env prefix, or the default
	 *   prefix + an *extra* prefix of SPANK_, in case we're
	 *   running in prolog/epilog, where Slurm prepends SPANK_
	 *   to all spank job environment variables.
	 */
	spopt = _spank_plugin_opt_create (sp->plugin, opt, 0);
	memcpy (var, "SPANK_", 6);
	if ((val = getenv (_opt_env_name(spopt, var+6, sizeof (var) - 6))) ||
	    (val = getenv (var))) {
		spopt->optarg = xstrdup (val);
		spopt->found = 1;
		if (opt->has_arg && argp)
			*argp = spopt->optarg;
	}

	/*
	 *  Cache the result
	 */
	list_append (option_cache, spopt);

	if (!spopt->found)
		return (ESPANK_ERROR);

	return (ESPANK_SUCCESS);
}


int spank_get_remote_options_env (char **env)
{
	return spank_stack_get_remote_options_env (global_spank_stack, env);
}


static int
spank_stack_get_remote_options_env (struct spank_stack *stack, char **env)
{
	char var [1024];
	const char *arg;
	struct spank_plugin_opt *option;
	ListIterator i;
	List option_cache = stack->option_cache;

	if (!option_cache)
		return (0);

	i = list_iterator_create (option_cache);
	while ((option = list_next (i))) {
		if (!(arg = getenvp (env, _opt_env_name (option, var, sizeof(var)))))
			continue;

		if (_do_option_cb(option, arg, 1)) {
			error ("spank: failed to process option %s=%s",
			       option->opt->name, arg);
		}

		/*
		 *  Now remove the environment variable.
		 *   It is no longer needed.
		 */
		unsetenvp (env, var);

	}
	list_iterator_destroy (i);

	return (0);
}

int spank_get_remote_options(job_options_t opts)
{
	return spank_stack_get_remote_options (global_spank_stack, opts);
}

static int
spank_stack_get_remote_options(struct spank_stack *stack, job_options_t opts)
{
	const struct job_option_info *j;

	job_options_iterator_reset(opts);
	while ((j = job_options_next(opts))) {
		struct spank_plugin_opt *opt;

		if (j->type != OPT_TYPE_SPANK)
			continue;

		if (!(opt = spank_stack_find_option_by_name(stack, j->option)))
			continue;

		if (_do_option_cb(opt, j->optarg, 1)) {
			error("spank: failed to process option %s=%s",
			      opt->opt->name, j->optarg);
		}
	}

	return (0);
}

/*
 *  Clear any environment variables for spank options.
 *   spank option env vars  have a prefix of SPANK_OPTION_ENV_PREFIX,
 *   or SPANK_ + SPANK_OPTION_ENV_PREFIX
 */
int spank_clear_remote_options_env (char **env)
{
	char **ep;
	int len = strlen (SPANK_OPTION_ENV_PREFIX);

	for (ep = env; *ep; ep++) {
		char *p = *ep;
		if (xstrncmp (*ep, "SPANK_", 6) == 0)
			p = *ep+6;
		if (xstrncmp (p, SPANK_OPTION_ENV_PREFIX, len) == 0) {
			char *end = strchr (p+len, '=');
			if (end) {
				char name[1024];
				memcpy (name, *ep, end - *ep);
				name [end - *ep] = '\0';
				debug("unsetenv (%s)", name);
				unsetenvp (env, name);
			}
		}
	}
	return (0);
}



static int tasks_execd (spank_t spank)
{
	return ( (spank->phase == STEP_TASK_POST_FORK)
	      || (spank->phase == STEP_TASK_EXIT)
	      || (spank->phase == SPANK_EXIT) );
}

static spank_err_t
_global_to_local_id(stepd_step_rec_t *job, uint32_t gid, uint32_t *p2uint32)
{
	int i;
	*p2uint32 = (uint32_t) -1;
	if ((job == NULL) || (gid >= job->ntasks))
		return (ESPANK_BAD_ARG);
	for (i = 0; i < job->node_tasks; i++) {
		if (job->task[i]->gtid == gid) {
			*p2uint32 = job->task[i]->id;
			return (ESPANK_SUCCESS);
		}
	}
	return (ESPANK_NOEXIST);
}


/*
 *  Return 1 if spank_item_t is valid for S_TYPE_LOCAL
 */
static int _valid_in_local_context (spank_item_t item)
{
	int rc = 0;
	switch (item) {
	case S_JOB_UID:
	case S_JOB_GID:
	case S_JOB_ID:
	case S_JOB_STEPID:
	case S_JOB_ARGV:
	case S_JOB_ENV:
	case S_JOB_TOTAL_TASK_COUNT:
	case S_JOB_NNODES:
		rc = 1;
		break;
	default:
		rc = 0;
	}
	return (rc);
}

static int _valid_in_allocator_context (spank_item_t item)
{
	switch (item) {
	  case S_JOB_UID:
	  case S_JOB_GID:
		  return 1;
	  default:
		  return 0;
	}
}

static spank_err_t _check_spank_item_validity (spank_t spank, spank_item_t item)
{
	/*
	 *  Valid in all contexts:
	 */
	switch (item) {
	  case S_SLURM_VERSION:
	  case S_SLURM_VERSION_MAJOR:
	  case S_SLURM_VERSION_MINOR:
	  case S_SLURM_VERSION_MICRO:
		  return ESPANK_SUCCESS;
	  default:
		  break; /* fallthru */
	}

	/*
	 *  No spank_item_t is available in slurmd context at this time.
	 */
	if (spank->stack->type == S_TYPE_SLURMD)
		return ESPANK_NOT_AVAIL;
	else if (spank->stack->type == S_TYPE_JOB_SCRIPT) {
		if (item != S_JOB_GID && item != S_JOB_UID && item != S_JOB_ID)
			return ESPANK_NOT_AVAIL;
	}
	else if (spank->stack->type == S_TYPE_LOCAL) {
		if (!_valid_in_local_context (item))
			return ESPANK_NOT_REMOTE;
		else if (spank->job == NULL)
			return ESPANK_NOT_AVAIL;
	}
	else if (spank->stack->type == S_TYPE_ALLOCATOR) {
		if (_valid_in_allocator_context (item)) {
			if (spank->job)
				return ESPANK_SUCCESS;
			else
				return ESPANK_NOT_AVAIL;
		}
		else if (_valid_in_local_context (item))
			return ESPANK_BAD_ARG;
		else
			return ESPANK_NOT_REMOTE;
	}

	/* All items presumably valid in remote context */
	return ESPANK_SUCCESS;
}

/*
 *  Global functions for SPANK plugins
 */

const char * spank_strerror (spank_err_t err)
{
	switch (err) {
	case ESPANK_SUCCESS:
		return "Success";
	case ESPANK_ERROR:
		return "Generic error";
	case ESPANK_BAD_ARG:
		return "Bad argument";
	case ESPANK_NOT_TASK:
		return "Not in task context";
	case ESPANK_ENV_EXISTS:
		return "Environment variable exists";
	case ESPANK_ENV_NOEXIST:
		return "No such environment variable";
	case ESPANK_NOSPACE:
		return "Buffer too small";
	case ESPANK_NOT_REMOTE:
		return "Valid only in remote context";
	case ESPANK_NOEXIST:
		return "Id/PID does not exist on this node";
	case ESPANK_NOT_EXECD:
		return "Lookup by PID requested, but no tasks running";
	case ESPANK_NOT_AVAIL:
		return "Item not available from this callback";
	case ESPANK_NOT_LOCAL:
		return "Valid only in local or allocator context";
	}

	return "Unknown";
}

int spank_symbol_supported (const char *name)
{
	int i;

	if (name == NULL)
		return (-1);

	for (i = 0; i < n_spank_syms; i++) {
		if (xstrcmp (spank_syms [i], name) == 0)
			return (1);
	}

	return (0);
}

int spank_remote(spank_t spank)
{
	if ((spank == NULL) || (spank->magic != SPANK_MAGIC))
		return (-1);
	if (spank->stack->type == S_TYPE_REMOTE)
		return (1);
	else
		return (0);
}

spank_context_t spank_context (void)
{
	if (global_spank_stack == NULL)
		return S_CTX_ERROR;
	switch (global_spank_stack->type) {
	  case S_TYPE_REMOTE:
		  return S_CTX_REMOTE;
	  case S_TYPE_LOCAL:
		  return S_CTX_LOCAL;
	  case S_TYPE_ALLOCATOR:
		  return S_CTX_ALLOCATOR;
	  case S_TYPE_SLURMD:
		  return S_CTX_SLURMD;
	  case S_TYPE_JOB_SCRIPT:
		  return S_CTX_JOB_SCRIPT;
	  default:
		  return S_CTX_ERROR;
	}

	return S_CTX_ERROR;
}

spank_err_t spank_get_item(spank_t spank, spank_item_t item, ...)
{
	int *p2int;
	uint32_t *p2uint32;
	uint64_t *p2uint64;
	uint32_t  uint32;
	uint16_t *p2uint16;
	uid_t *p2uid;
	gid_t *p2gid;
	gid_t **p2gids;
	pid_t *p2pid;
	pid_t  pid;
	char ***p2argv;
	char **p2str;
	char **p2vers;
	stepd_step_task_info_t *task;
	stepd_step_rec_t  *slurmd_job = NULL;
	struct spank_launcher_job_info *launcher_job = NULL;
	struct job_script_info *s_job_info = NULL;
	va_list vargs;
	spank_err_t rc = ESPANK_SUCCESS;

	if ((spank == NULL) || (spank->magic != SPANK_MAGIC))
		return (ESPANK_BAD_ARG);

	/*
	 *  Check for validity of the given item in the current context
	 */
	rc = _check_spank_item_validity (spank, item);
	if (rc != ESPANK_SUCCESS)
		return (rc);

	if (spank->stack->type == S_TYPE_LOCAL)
		launcher_job = spank->job;
	else if (spank->stack->type == S_TYPE_REMOTE)
		slurmd_job = spank->job;
	else if (spank->stack->type == S_TYPE_JOB_SCRIPT)
		s_job_info = spank->job;

	va_start(vargs, item);
	switch (item) {
	case S_JOB_UID:
		p2uid = va_arg(vargs, uid_t *);
		if (spank->stack->type == S_TYPE_LOCAL)
			*p2uid = launcher_job->uid;
		else if (spank->stack->type == S_TYPE_REMOTE)
			*p2uid = slurmd_job->uid;
		else if (spank->stack->type == S_TYPE_JOB_SCRIPT)
			*p2uid = s_job_info->uid;
		else
			*p2uid = getuid();
		break;
	case S_JOB_GID:
		p2gid = va_arg(vargs, gid_t *);
		if (spank->stack->type == S_TYPE_LOCAL)
			*p2gid = launcher_job->gid;
		else if (spank->stack->type == S_TYPE_REMOTE)
			*p2gid = slurmd_job->gid;
		else if (spank->stack->type == S_TYPE_JOB_SCRIPT)
			*p2gid = s_job_info->gid;
		else
			*p2gid = getgid();
		break;
	case S_JOB_SUPPLEMENTARY_GIDS:
		p2gids = va_arg(vargs, gid_t **);
		p2int = va_arg(vargs, int *);
		if (slurmd_job) {
			*p2gids = slurmd_job->gids;
			*p2int = slurmd_job->ngids;
		} else {
			*p2gids = NULL;
			*p2int = 0;
		}
		break;
	case S_JOB_ID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_LOCAL)
			*p2uint32 = launcher_job->jobid;
		else if (spank->stack->type == S_TYPE_REMOTE)
			*p2uint32 = slurmd_job->step_id.job_id;
		else if (spank->stack->type == S_TYPE_JOB_SCRIPT)
			*p2uint32 = s_job_info->jobid;
		break;
	case S_JOB_STEPID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_LOCAL)
			*p2uint32 = launcher_job->stepid;
		else if (slurmd_job)
			*p2uint32 = slurmd_job->step_id.step_id;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_ARRAY_ID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_REMOTE)
			*p2uint32 = slurmd_job->array_job_id;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_ARRAY_TASK_ID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_REMOTE)
			*p2uint32 = slurmd_job->array_task_id;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_NNODES:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_LOCAL) {
			if (launcher_job->step_layout)
				*p2uint32 = launcher_job->step_layout->
					    node_cnt;
			else {
				*p2uint32 = 0;
				rc = ESPANK_ENV_NOEXIST;
			}
		} else if (slurmd_job)
			*p2uint32 = slurmd_job->nnodes;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_NODEID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (slurmd_job)
			*p2uint32 = slurmd_job->nodeid;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_LOCAL_TASK_COUNT:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (slurmd_job)
			*p2uint32 = slurmd_job->node_tasks;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_TOTAL_TASK_COUNT:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (spank->stack->type == S_TYPE_LOCAL) {
			if (launcher_job->step_layout)
				*p2uint32 = launcher_job->step_layout->
					    task_cnt;
			else {
				*p2uint32 = 0;
				rc = ESPANK_ENV_NOEXIST;
			}
		} else if (slurmd_job)
			*p2uint32 = slurmd_job->ntasks;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_NCPUS:
		p2uint16 = va_arg(vargs, uint16_t *);
		if (slurmd_job)
			*p2uint16 = slurmd_job->cpus;
		else
			*p2uint16 = 0;
		break;
	case S_STEP_CPUS_PER_TASK:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (slurmd_job)
			*p2uint32 = slurmd_job->cpus_per_task;
		else
			*p2uint32 = 0;
		break;
	case S_JOB_ARGV:
		p2int = va_arg(vargs, int *);
		p2argv = va_arg(vargs, char ***);
		if (spank->stack->type == S_TYPE_LOCAL) {
			*p2int = launcher_job->argc;
			*p2argv = launcher_job->argv;
		} else if (slurmd_job) {
			*p2int = slurmd_job->argc;
			*p2argv = slurmd_job->argv;
		} else {
			*p2int = 0;
			*p2argv = NULL;
		}
		break;
	case S_JOB_ENV:
		p2argv = va_arg(vargs, char ***);
		if (slurmd_job)
			*p2argv = slurmd_job->env;
		else
			*p2argv = NULL;
		break;
	case S_TASK_ID:
		p2int = va_arg(vargs, int *);
		if (!spank->task) {
			*p2int = -1;
			rc = ESPANK_NOT_TASK;
		} else {
			*p2int = spank->task->id;
		}
		break;
	case S_TASK_GLOBAL_ID:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (!spank->task) {
			rc = ESPANK_NOT_TASK;
		} else {
			*p2uint32 = spank->task->gtid;
		}
		break;
	case S_TASK_EXIT_STATUS:
		p2int = va_arg(vargs, int *);
		if (!spank->task || !spank->task->exited) {
			rc = ESPANK_NOT_TASK;
		} else {
			*p2int = spank->task->estatus;
		}
		break;
	case S_TASK_PID:
		p2pid = va_arg(vargs, pid_t *);
		if (!spank->task) {
			rc = ESPANK_NOT_TASK;
			*p2pid = 0;
		} else {
			*p2pid = spank->task->pid;
		}
		break;
	case S_JOB_PID_TO_GLOBAL_ID:
		pid = va_arg(vargs, pid_t);
		p2uint32 = va_arg(vargs, uint32_t *);
		*p2uint32 = (uint32_t) -1;

		if (!tasks_execd(spank))
			rc = ESPANK_NOT_EXECD;
		else if (!(task = job_task_info_by_pid (slurmd_job, pid)))
			rc = ESPANK_NOEXIST;
		else
			*p2uint32 = task->gtid;
		break;
	case S_JOB_PID_TO_LOCAL_ID:
		pid = va_arg(vargs, pid_t);
		p2uint32 = va_arg(vargs, uint32_t *);
		*p2uint32 = (uint32_t) -1;

		if (!tasks_execd(spank))
			rc = ESPANK_NOT_EXECD;
		else if (!(task = job_task_info_by_pid (slurmd_job, pid)))
			rc = ESPANK_NOEXIST;
		else
			*p2uint32 = task->id;
		break;
	case S_JOB_LOCAL_TO_GLOBAL_ID:
		uint32 = va_arg(vargs, uint32_t);
		p2uint32 = va_arg(vargs, uint32_t *);
		*p2uint32 = (uint32_t) -1;

		if (slurmd_job && (uint32 <= slurmd_job->node_tasks) &&
		    slurmd_job->task && slurmd_job->task[uint32]) {
			*p2uint32 = slurmd_job->task[uint32]->gtid;
		} else
			rc = ESPANK_NOEXIST;
		break;
	case S_JOB_GLOBAL_TO_LOCAL_ID:
		uint32 = va_arg(vargs, uint32_t);
		p2uint32 = va_arg(vargs, uint32_t *);
		rc = _global_to_local_id (slurmd_job, uint32, p2uint32);
		break;
	case S_JOB_ALLOC_CORES:
		p2str = va_arg(vargs, char **);
		if (slurmd_job)
			*p2str = slurmd_job->job_alloc_cores;
		else
			*p2str = NULL;
		break;
	case S_JOB_ALLOC_MEM:
		p2uint64 = va_arg(vargs, uint64_t *);
		if (slurmd_job)
			*p2uint64 = slurmd_job->job_mem;
		else
			*p2uint64 = 0;
		break;
	case S_STEP_ALLOC_CORES:
		p2str = va_arg(vargs, char **);
		if (slurmd_job)
			*p2str = slurmd_job->step_alloc_cores;
		else
			*p2str = NULL;
		break;
	case S_STEP_ALLOC_MEM:
		p2uint64 = va_arg(vargs, uint64_t *);
		if (slurmd_job)
			*p2uint64 = slurmd_job->step_mem;
		else
			*p2uint64 = 0;
		break;
	case S_SLURM_RESTART_COUNT:
		p2uint32 = va_arg(vargs, uint32_t *);
		if (slurmd_job)
			*p2uint32 = slurmd_job->restart_cnt;
		else
			*p2uint32 = 0;
		break;
	case S_SLURM_VERSION:
		p2vers = va_arg(vargs, char  **);
		*p2vers = SLURM_VERSION_STRING;
		break;
	case S_SLURM_VERSION_MAJOR:
		p2vers = va_arg(vargs, char  **);
		*p2vers = SLURM_MAJOR;
		break;
	case S_SLURM_VERSION_MINOR:
		p2vers = va_arg(vargs, char  **);
		*p2vers = SLURM_MINOR;
		break;
	case S_SLURM_VERSION_MICRO:
		p2vers = va_arg(vargs, char  **);
		*p2vers = SLURM_MICRO;
		break;
	default:
		rc = ESPANK_BAD_ARG;
		break;
	}
	va_end(vargs);
	return (rc);
}

spank_err_t spank_env_access_check (spank_t spank)
{
	if ((spank == NULL) || (spank->magic != SPANK_MAGIC))
		return (ESPANK_BAD_ARG);
	if (spank->stack->type != S_TYPE_REMOTE)
		return (ESPANK_NOT_REMOTE);
	if (spank->job == NULL)
		return (ESPANK_BAD_ARG);
	return (ESPANK_SUCCESS);

}

spank_err_t spank_getenv(spank_t spank, const char *var, char *buf,
			 int len)
{
	char *val;
	spank_err_t err = spank_env_access_check (spank);

	if (err != ESPANK_SUCCESS)
		return (err);

	if (len < 0)
		return (ESPANK_BAD_ARG);

	if (!(val = getenvp(((stepd_step_rec_t *) spank->job)->env, var)))
		return (ESPANK_ENV_NOEXIST);

	if (strlcpy(buf, val, len) >= len)
		return (ESPANK_NOSPACE);

	return (ESPANK_SUCCESS);
}

spank_err_t spank_setenv(spank_t spank, const char *var, const char *val,
			 int overwrite)
{
	stepd_step_rec_t * job;
	spank_err_t err = spank_env_access_check (spank);

	if (err != ESPANK_SUCCESS)
		return (err);

	if ((var == NULL) || (val == NULL))
		return (ESPANK_BAD_ARG);

	job = spank->job;

	if (getenvp(job->env, var) && !overwrite)
		return (ESPANK_ENV_EXISTS);

	if (setenvf(&job->env, var, "%s", val) < 0)
		return (ESPANK_ERROR);

	return (ESPANK_SUCCESS);
}

spank_err_t spank_unsetenv (spank_t spank, const char *var)
{
	spank_err_t err = spank_env_access_check (spank);

	if (err != ESPANK_SUCCESS)
		return (err);

	if (var == NULL)
		return (ESPANK_BAD_ARG);

	unsetenvp(((stepd_step_rec_t *) spank->job)->env, var);

	return (ESPANK_SUCCESS);
}


/*
 *  Dynamically loaded versions of spank_*_job_env
 */
const char *dyn_spank_get_job_env(const char *name)
{
	void *h = dlopen(NULL, 0);
	char * (*fn)(const char *n);
	char *rc;

	fn = dlsym(h, "spank_get_job_env");
	if (fn == NULL) {
		(void) dlclose(h);
		return NULL;
	}

	rc = ((*fn) (name));
/*	(void) dlclose(h);	NOTE: DO NOT CLOSE OR SPANK WILL BREAK */
	return rc;
}

int dyn_spank_set_job_env(const char *n, const char *v, int overwrite)
{
	void *h = dlopen(NULL, 0);
	int (*fn)(const char *n, const char *v, int overwrite);
	int rc;

	fn = dlsym(h, "spank_set_job_env");
	if (fn == NULL) {
		(void) dlclose(h);
		return (-1);
	}

	rc = ((*fn) (n, v, overwrite));
/*	(void) dlclose(h);	NOTE: DO NOT CLOSE OR SPANK WILL BREAK */
	return rc;
}

extern int dyn_spank_unset_job_env(const char *n)
{
	void *h = dlopen(NULL, 0);
	int (*fn)(const char *n);
	int rc;

	fn = dlsym(h, "spank_unset_job_env");
	if (fn == NULL) {
		(void) dlclose(h);
		return (-1);
	}

	rc = ((*fn) (n));
/*	(void) dlclose(h);	NOTE: DO NOT CLOSE OR SPANK WILL BREAK */
	return rc;
}

static spank_err_t spank_job_control_access_check (spank_t spank)
{
	if ((spank == NULL) || (spank->magic != SPANK_MAGIC))
		return (ESPANK_BAD_ARG);

	if (spank_remote (spank))
		return (ESPANK_NOT_LOCAL);

	if (spank->stack->type == S_TYPE_SLURMD)
		return (ESPANK_NOT_AVAIL);

	return (ESPANK_SUCCESS);
}


spank_err_t spank_job_control_getenv (spank_t spank, const char *var,
			char *buf, int len)
{
	const char *val;
	spank_err_t err;

	if ((err = spank_job_control_access_check (spank)))
		return (err);

	if ((var == NULL) || (buf == NULL) || (len <= 0))
		return (ESPANK_BAD_ARG);

	val = dyn_spank_get_job_env (var);
	if (val == NULL)
		return (ESPANK_ENV_NOEXIST);

	if (strlcpy (buf, val, len) >= len)
		return (ESPANK_NOSPACE);

	return (ESPANK_SUCCESS);
}

spank_err_t spank_job_control_setenv (spank_t spank, const char *var,
			const char *val, int overwrite)
{
	spank_err_t err;

	if ((err = spank_job_control_access_check (spank)))
		return (err);

	if ((var == NULL) || (val == NULL))
		return (ESPANK_BAD_ARG);

	if (dyn_spank_set_job_env (var, val, overwrite) < 0)
		return (ESPANK_BAD_ARG);

	return (ESPANK_SUCCESS);
}

spank_err_t spank_job_control_unsetenv (spank_t spank, const char *var)
{
	spank_err_t err;

	if ((err = spank_job_control_access_check (spank)))
		return (err);

	if (var == NULL)
		return (ESPANK_BAD_ARG);

	if (dyn_spank_unset_job_env (var) < 0)
		return (ESPANK_BAD_ARG);

	return (ESPANK_SUCCESS);
}

/*
 * spank_get_plugin_names
 * Get names of all spank plugins
 *
 * Parameters:
 * IN/OUT names	- Pointer to char ** (should be NULL when called) output of
 *		  function is allocated memory for the array of string
 *		  pointers, and allocated memory for the strings. Array will
 *		  be NULL terminated. Caller should manage the memory.
 * Returns:
 * 		- Number of allocated strings (excluding NULL terminator)
 */
size_t spank_get_plugin_names(char ***names)
{
	struct spank_plugin *p;
	ListIterator i;
	size_t n_names = 0;

	if (!global_spank_stack)
		return 0;

	i = list_iterator_create(global_spank_stack->plugin_list);
	while ((p = list_next(i))) {
		*names = xrecalloc(*names, ++n_names + 1, sizeof(char *));
		(*names)[n_names] = NULL;
		(*names)[n_names - 1] = xstrdup(p->name);
	}
	list_iterator_destroy(i);
	return n_names;
}

/*
 * spank_get_plugin_option_names
 * Get names of all spank plugins
 *
 * Parameters:
 * IN plugin_name	- Name of spank plugin being considered
 *			  (e.g., from spank_get_plugin_names)
 * IN/OUT opts		- Pointer to char ** (should be NULL when called)
 *			  output of function is allocated memory for the array
 *			  of string pointers, and allocated memory for the
 *			  strings. Array will be NULL terminated. Caller
 *			  should manage the memory.
 * Returns:
 *			- Number of allocated strings (excluding NULL
 *			  terminator)
 */
size_t spank_get_plugin_option_names(const char *plugin_name, char ***opts)
{
	struct spank_plugin_opt *spopt;
	size_t nopts = 0;

	List options = get_global_option_cache();
	ListIterator i;

	i = list_iterator_create(options);
	while ((spopt = list_next(i))) {
		if (spopt->disabled)
			continue;
		if (!xstrcmp(spopt->plugin->name, plugin_name)) {
			*opts = xrecalloc(*opts, ++nopts + 1, sizeof(char *));
			(*opts)[nopts] = NULL;
			(*opts)[nopts - 1] = xstrdup(spopt->opt->name);
			continue;
		}
	}
	list_iterator_destroy(i);
	return nopts;
}

/*
 * Get option value by common option name
 */
extern char *spank_option_get(char *name)
{
	List option_cache = get_global_option_cache();
	struct spank_plugin_opt *spopt;

	if (!option_cache)
		return NULL;

	spopt = list_find_first(option_cache,
			(ListFindF) _opt_by_name, name);

	if (spopt) {
		if (spopt->set && !spopt->optarg)
			return xstrdup("set");
		if (!spopt->set && !spopt->opt->has_arg)
			return xstrdup("unset");
		if (spopt->optarg)
			return xstrdup(spopt->optarg);
	}
	return NULL;
}

/*
 * Get plugin name by common option name
 */
extern char *spank_option_plugin(char *optname)
{
	List option_cache = get_global_option_cache();
	struct spank_plugin_opt *spopt;

	if (!option_cache)
		return NULL;

	spopt = list_find_first(option_cache,
			(ListFindF) _opt_by_name, optname);

	if (spopt)
		return xstrdup(spopt->plugin->name);
	return NULL;
}

/*
 * Is option set? Discover by common option name
 */
extern bool spank_option_isset(char *name)
{
	List option_cache = get_global_option_cache();
	struct spank_plugin_opt *spopt;

	if (!option_cache)
		return NULL;

	spopt = list_find_first(option_cache,
			(ListFindF) _opt_by_name, name);
	if (spopt)
		return spopt->set;
	return false;
}

/*
 * Function for iterating through all the common option data structure
 * and returning (via parameter arguments) the name and value of each
 * set slurm option.
 *
 * OUT plugin	- pointer to string to store the plugin name
 * OUT name	- pointer to string to store the option name
 * OUT value	- pointer to string to store the value
 * IN/OUT state	- internal state, should point to NULL for the first call
 * RETURNS	- true if plugin/name/value set; false if no more options
 */
extern bool spank_option_get_next_set(char **plugin, char **name,
				      char **value, void **state)
{
	List option_cache = get_global_option_cache();
	ListIterator *iter = (ListIterator *) *state;
	struct spank_plugin_opt *spopt;

	if (option_cache == NULL)
		return NULL;

	if (!iter) {
		iter = xmalloc(sizeof(ListIterator));
		*iter = list_iterator_create(option_cache);
		*state = iter;
	}

	while ((spopt = list_next(*iter))) {
		if (!spopt->set)
			continue;
		*plugin = xstrdup(spopt->plugin->name);
		*name = xstrdup(spopt->opt->name);
		if (spopt->optarg)
			*value = xstrdup(spopt->optarg);
		else if (spopt->set)
			*value = xstrdup("set");
		else if (!spopt->set && !spopt->opt->has_arg)
			*value = xstrdup("unset");
		return true;
	}

	list_iterator_destroy(*iter);
	xfree(iter);
	*state = NULL;

	return false;
}

extern bool spank_has_prolog()
{
	return has_prolog;
}

extern bool spank_has_epilog()
{
	return has_epilog;
}
