OpenSolaris_b135/cmd/rcm_daemon/common/rcm_script.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * rcm scripting module:
 *
 * This module implements rcm scripting interfaces.
 * It translates rcm module based interfaces to rcm script based
 * interfaces.
 *
 * Entry points:
 *
 *   int script_main_init()
 *	Initialize the rcm scripting framework.
 *	Called during the rcm daemon initialization
 *
 *   int script_main_fini()
 *	Called at the time of the rcm daemon exit.
 *
 *   struct rcm_mod_ops *script_init(module_t *module)
 *	Initialize the given script.
 *	module->name contains the name of the script.
 *	Called at the time of loading scripts.
 *	Semantics are similar to module init.
 *
 *   char *script_info(module_t *module)
 *	Called when the rcm daemon wishes to get the script information.
 *	module->name contains the name of the script.
 *	Semantics are similar to module info.
 *
 *   int script_fini(module_t *module)
 *	Called before removing the script.
 *	module->name contains the name of the script.
 *	Semantics are similar to module fini.
 *
 * In addition to the above entry points rcm_mod_ops structure contains
 * the other entry points. A pointer to this structure is returned when
 * script_init() is called.
 */

#include "rcm_impl.h"
#include "rcm_script_impl.h"
#include <sys/resource.h>
#include <procfs.h>
#include <sys/proc.h>
#include <ctype.h>

/*
 * All rcm scripting commands are enumerated here.
 * NOTE: command positions in script_cmd_id_t and script_cmd_name must match.
 */
typedef enum {
	C_SCRIPTINFO,
	C_RESOURCEINFO,
	C_REGISTER,
	C_QUERYREMOVE,
	C_PREREMOVE,
	C_POSTREMOVE,
	C_UNDOREMOVE,
	C_QUERYCAPACITY,
	C_PRECAPACITY,
	C_POSTCAPACITY,
	C_QUERYSUSPEND,
	C_PRESUSPEND,
	C_POSTRESUME,
	C_CANCELSUSPEND
} script_cmd_id_t;

/* NOTE: command positions in script_cmd_id_t and script_cmd_name must match */
static char *script_cmd_name[] = {
	"scriptinfo",
	"resourceinfo",
	"register",
	"queryremove",
	"preremove",
	"postremove",
	"undoremove",
	"querycapacity",
	"precapacity",
	"postcapacity",
	"querysuspend",
	"presuspend",
	"postresume",
	"cancelsuspend",
	NULL
};

/*
 * All rcm scripting data items are enumerated here.
 * NOTE: data item positions in script_data_item_id_t and
 * script_data_item_name must match.
 */
typedef	enum {
	D_SCRIPT_VERSION,
	D_SCRIPT_FUNC_INFO,
	D_CMD_TIMEOUT,
	D_RESOURCE_NAME,
	D_RESOURCE_USAGE_INFO,
	D_FAILURE_REASON,
	D_LOG_ERR,
	D_LOG_WARN,
	D_LOG_INFO,
	D_LOG_DEBUG
} script_data_item_id_t;

/*
 * NOTE: data item positions in script_data_item_id_t and
 * script_data_item_name must match.
 */
static const char *script_data_item_name[] = {
	"rcm_script_version",
	"rcm_script_func_info",
	"rcm_cmd_timeout",
	"rcm_resource_name",
	"rcm_resource_usage_info",
	"rcm_failure_reason",
	"rcm_log_err",
	"rcm_log_warn",
	"rcm_log_info",
	"rcm_log_debug",
	NULL
};

/*
 * Maximum number of rcm scripts that can run in parallel.
 * RCM daemon has no limit on the number of scripts supported. But
 * at most it runs script_max_parallelism number of scripts in parallel.
 * For each running script rcm daemon consumes two file descriptors
 * in order to communicate with the script via pipes.
 * So maximum number of file descriptor entries consumed by rcm daemon
 * on behalf of rcm scripts is "script_max_parallelism * 2"
 */
static const int script_max_parallelism = 64;

/*
 * semaphore to limit the number of rcm script processes running in
 * parallel to script_max_parallelism.
 */
static sema_t script_process_sema;

/* mutex to protect the any global data */
static mutex_t script_lock;

/* contains head to a queue of script_info structures */
static rcm_queue_t script_info_q;

/*
 * This mmapped state file is used to store the process id and
 * rcm script name of all currently running rcm scripts.
 */
static const char *script_ps_state_file = "/var/run/rcm_script_state";
static state_file_descr_t script_ps_statefd;

static char *script_env_noforce = "RCM_ENV_FORCE=FALSE";
static char *script_env_force = "RCM_ENV_FORCE=TRUE";
static char *script_env_interval = "RCM_ENV_INTERVAL=%ld";

#define	RSCR_TRACE		RCM_TRACE1

/* rcm script base environment */
static char *script_env[MAX_ENV_PARAMS];

struct rlimit file_limit;

/* function prototypes */
static void build_env(void);
static void copy_env(char *[], char *[]);
static void open_state_file(const char *, state_file_descr_t *, size_t, int,
	uint32_t);
static void truncate_state_file(state_file_descr_t *);
static void close_state_file(const char *, state_file_descr_t *);
static void grow_state_file(state_file_descr_t *);
static void *get_state_element(state_file_descr_t *, int, int *);
static void *allocate_state_element(state_file_descr_t *, int *);
static void free_state_element(void *);
static void script_ps_state_file_kill_pids(void);
static void script_ps_state_file_add_entry(pid_t, char *);
static void script_ps_state_file_remove_entry(pid_t);
static int dname_to_id(char *);
static void script_process_sema_wait(void);
static int run_script(script_info_t *, char *[], char *[], char **);
static int get_line(int fd, char *, char *, int, size_t *, time_t, int *);
static void script_exited(script_info_t *);
static int kill_pid(pid_t);
static void kill_script(script_info_t *);
static char *flags_to_name(int, char *, int);
static void fill_argv(script_info_t *, char *[], char *);
static void *read_stderr(script_info_t *);
static int process_dataitem(script_info_t *, int, char *, char **);
static int do_cmd(script_info_t *, char *[], char *[], char **);
static int do_script_info(script_info_t *);
static int do_dr(script_info_t *, char *[], char *[], char **);
static int script_get_info(rcm_handle_t *, char *, pid_t, uint_t, char **,
	char **, nvlist_t *, rcm_info_t **);
static void add_for_unregister(script_info_t *);
static void remove_from_unregister(script_info_t *, char *);
static void complete_unregister(script_info_t *);
static int script_register_interest(rcm_handle_t *);
static void add_drreq(script_info_t *, char *);
static void remove_drreq(script_info_t *, char *);
static void remove_drreq_all(script_info_t *);
static int script_request_offline(rcm_handle_t *, char *, pid_t, uint_t,
	char **, rcm_info_t **);
static int script_notify_online(rcm_handle_t *, char *, pid_t, uint_t,
	char **, rcm_info_t **);
static int script_notify_remove(rcm_handle_t *, char *, pid_t, uint_t,
	char **, rcm_info_t **);
static int script_request_suspend(rcm_handle_t *, char *, pid_t, timespec_t *,
	uint_t, char **, rcm_info_t **);
static int script_notify_resume(rcm_handle_t *, char *, pid_t, uint_t,
	char **, rcm_info_t **);
static capacity_descr_t *get_capacity_descr(char *);
static int build_env_for_capacity(script_info_t *, char *, uint_t, nvlist_t *,
	char *[], int *, char **);
static int script_request_capacity_change(rcm_handle_t *, char *, pid_t,
	uint_t, nvlist_t *, char **, rcm_info_t **);
static int script_notify_capacity_change(rcm_handle_t *, char *, pid_t,
	uint_t, nvlist_t *, char **, rcm_info_t **);
static void log_msg(script_info_t *, int, char *);
static char *dup_err(int, char *, ...);
static void rcmscript_snprintf(char **, int *, char **, char *, ...);
static char *rcmscript_strdup(char *);
static void *rcmscript_malloc(size_t);
static void *rcmscript_calloc(size_t, size_t);


static struct rcm_mod_ops script_ops =
{
	RCM_MOD_OPS_VERSION,
	script_register_interest, /* register */
	script_register_interest, /* unregister */
	script_get_info,
	script_request_suspend,
	script_notify_resume,
	script_request_offline,
	script_notify_online,
	script_notify_remove,
	script_request_capacity_change,
	script_notify_capacity_change,
	NULL
};

/*
 * Messages fall into two categories:
 *   framework messages (MF_..)
 *   errors directly attributable to scripts (MS_..)
 */
#define	MF_MEMORY_ALLOCATION_ERR \
	gettext("rcm: failed to allocate memory: %1$s\n")
#define	MF_STATE_FILE_ERR \
	gettext("rcm: state file error: %1$s: %2$s\n")
#define	MF_FUNC_CALL_ERR \
	gettext("rcm: %1$s: %2$s\n")
#define	MF_NV_ERR \
	gettext("rcm: required name-value parameters missing (%1$s)\n")
#define	MF_UNKNOWN_RSRC_ERR \
	gettext("rcm: unknown resource name %1$s (%2$s)\n")
#define	MS_REGISTER_RSRC_ERR \
	gettext("rcm script %1$s: failed to register %2$s\n")
#define	MS_REGISTER_ERR \
	gettext("rcm script %1$s: register: %2$s\n")
#define	MS_SCRIPTINFO_ERR \
	gettext("rcm script %1$s: scriptinfo: %2$s\n")
#define	MS_PROTOCOL_ERR \
	gettext("rcm script %1$s: scripting protocol error\n")
#define	MS_TIMEOUT_ERR \
	gettext("rcm script %1$s: timeout error\n")
#define	MS_UNSUPPORTED_VER \
	gettext("rcm script %1$s: unsupported version %2$d\n")
#define	MS_SCRIPT_ERR \
	gettext("rcm script %1$s: error: %2$s\n")
#define	MS_UNKNOWN_ERR \
	gettext("rcm script %1$s: unknown error\n")
#define	MS_LOG_MSG \
	gettext("rcm script %1$s: %2$s\n")


/*
 * Initialize rcm scripting framework.
 * Called during initialization of rcm daemon.
 */
int
script_main_init(void)
{
#define	PS_STATE_FILE_CHUNK_SIZE	32

	/* set base script environment */
	build_env();

	rcm_init_queue(&script_info_q);

	/*
	 * Initialize the semaphore to limit the number of rcm script
	 * process running in parallel to script_max_parallelism.
	 */
	(void) sema_init(&script_process_sema, script_max_parallelism,
			USYNC_THREAD, NULL);

	(void) mutex_init(&script_lock, USYNC_THREAD, NULL);

	/* save original file limit */
	(void) getrlimit(RLIMIT_NOFILE, &file_limit);

	open_state_file(script_ps_state_file, &script_ps_statefd,
		sizeof (ps_state_element_t),
		PS_STATE_FILE_CHUNK_SIZE,
		PS_STATE_FILE_VER);

	/*
	 * If any pids exist in the ps state file since the last incarnation of
	 * the rcm daemon, kill the pids.
	 * On a normal daemon exit no pids should exist in the ps state file.
	 * But on an abnormal daemon exit pids may exist in the ps state file.
	 */
	if (script_ps_statefd.state_file) {
		script_ps_state_file_kill_pids();
		truncate_state_file(&script_ps_statefd);
	}

	return (0);
}

/*
 * Do any cleanup.
 * Called at the time of normal rcm daemon exit.
 */
int
script_main_fini(void)
{
	script_ps_state_file_kill_pids();
	close_state_file(script_ps_state_file, &script_ps_statefd);
	return (0);
}

/*
 * Initialize the given rcm script.
 * module->name contains the name of the rcm script.
 */
struct rcm_mod_ops *
script_init(module_t *module)
{
	script_info_t *rsi;
	size_t len;
	char *script_path;

	rcm_log_message(RSCR_TRACE, "script_init: script name = %s\n",
						module->name);

	module->rsi = NULL;

	if ((script_path = rcm_get_script_dir(module->name)) == NULL)
		return (NULL);

	len = strlen(script_path) + strlen(module->name) + 2;

	/* calloc also zeros the contents */
	rsi = (script_info_t *)rcmscript_calloc(1, sizeof (script_info_t));
	rsi->script_full_name = (char *)rcmscript_calloc(1, len);

	rsi->module = module;
	rcm_init_queue(&rsi->drreq_q);

	(void) mutex_init(&rsi->channel_lock, USYNC_THREAD, NULL);

	(void) snprintf(rsi->script_full_name, len, "%s%s", script_path,
			module->name);
	rsi->script_name = strrchr(rsi->script_full_name, '/') + 1;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->cmd_timeout = -1; /* don't time scriptinfo command */
	if (do_script_info(rsi) == RCM_SUCCESS) {
		/*
		 * if the script hasn't specified a timeout value set it to
		 * default
		 */
		if (rsi->cmd_timeout == -1)
			rsi->cmd_timeout = SCRIPT_CMD_TIMEOUT;
		(void) mutex_unlock(&rsi->channel_lock);

		/* put rsi on script_info_q */
		(void) mutex_lock(&script_lock);
		rcm_enqueue_tail(&script_info_q, &rsi->queue);
		(void) mutex_unlock(&script_lock);

		module->rsi = rsi;
		return (&script_ops);
	}

	(void) mutex_unlock(&rsi->channel_lock);

	free(rsi->script_full_name);
	free(rsi);
	return (NULL);
}

/*
 * Returns a string describing the script's functionality.
 * module->name contains the name of the rcm script for which information
 * is requested.
 */
char *
script_info(module_t *module)
{
	script_info_t *rsi = module->rsi;

	rcm_log_message(RSCR_TRACE, "script_info: script name = %s\n",
						rsi->script_name);
	return (rsi->func_info_buf);
}

/*
 * Called before unloading the script.
 * module->name contains the name of the rcm script which is being unloaded.
 * Do any cleanup.
 */
int
script_fini(module_t *module)
{
	script_info_t *rsi = module->rsi;

	rcm_log_message(RSCR_TRACE, "script_fini: script name = %s\n",
						rsi->script_name);

	/* remove rsi from script_info_q */
	(void) mutex_lock(&script_lock);
	rcm_dequeue(&rsi->queue);
	(void) mutex_unlock(&script_lock);

	remove_drreq_all(rsi);

	if (rsi->func_info_buf)
		free(rsi->func_info_buf);

	free(rsi->script_full_name);
	free(rsi);

	module->rsi = NULL;

	return (RCM_SUCCESS);
}

/* build base environment for scripts */
static void
build_env(void)
{
	const char *env_list[] = { "LANG", "LC_COLLATE", "LC_CTYPE",
		"LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME",
		"LC_ALL", "TZ", NULL };
	char *x;
	int len;
	int i, j = 0;
	int d;
	extern int debug_level;

	script_env[j++] = rcmscript_strdup("PATH=/usr/sbin:/usr/bin");

	for (i = 0; env_list[i] != NULL; i++) {
		x = getenv(env_list[i]);
		if (x) {
			len = strlen(env_list[i]) + strlen(x) + 2;
			script_env[j] = (char *)rcmscript_malloc(len);

			(void) snprintf(script_env[j++], len, "%s=%s",
				env_list[i], x);
		}
	}

	len = strlen("RCM_ENV_DEBUG_LEVEL") + 3;
	script_env[j] = (char *)rcmscript_malloc(len);

	if (debug_level < 0)
		d = 0;
	else if (debug_level > 9)
		d = 9;
	else
		d = debug_level;

	(void) snprintf(script_env[j++], len, "RCM_ENV_DEBUG_LEVEL=%d", d);

	script_env[j] = NULL;
}

static void
copy_env(char *src[], char *dst[])
{
	int i;

	for (i = 0; src[i] != NULL; i++)
		dst[i] = src[i];

	dst[i] = NULL;
}

/*
 * Open (or create if the file does not exist) the given state file
 * and mmap it.
 */
static void
open_state_file(const char *filename,
	state_file_descr_t *statefd,
	size_t element_size,
	int chunk_size,
	uint32_t version)
{
	struct stat stats;
	int error_num;

	if ((statefd->fd = open(filename, O_CREAT|O_RDWR, 0600)) ==
			-1) {
		error_num = errno;
		rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR,
			"open", strerror(error_num));
		rcmd_exit(error_num);
		/*NOTREACHED*/
	}

	if (fstat(statefd->fd, &stats) != 0) {
		error_num = errno;
		rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR,
			"fstat", strerror(error_num));
		rcmd_exit(error_num);
		/*NOTREACHED*/
	}

	if (stats.st_size != 0) {
		/* LINTED */
		statefd->state_file = (state_file_t *)mmap(NULL,
			stats.st_size, PROT_READ|PROT_WRITE, MAP_SHARED,
			statefd->fd, 0);

		if (statefd->state_file == MAP_FAILED) {
			error_num = errno;
			rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR,
				"mmap", strerror(error_num));
			rcmd_exit(error_num);
			/*NOTREACHED*/
		}

		if (statefd->state_file->version != version) {
			(void) munmap((void *)statefd->state_file,
					stats.st_size);
			statefd->state_file = NULL;
			(void) ftruncate(statefd->fd, 0);
		}
	} else {
		statefd->state_file = NULL;
	}

	statefd->version = version;
	statefd->element_size = sizeof (state_element_t) +
				RSCR_ROUNDUP(element_size, 8);
	statefd->chunk_size = chunk_size;
	statefd->index = 0;
}

static void
truncate_state_file(state_file_descr_t *statefd)
{
	size_t size;

	if (statefd->state_file) {
		size = sizeof (state_file_t) + statefd->element_size *
			statefd->state_file->max_elements;

		(void) munmap((void *)statefd->state_file, size);
		statefd->state_file = NULL;
	}
	(void) ftruncate(statefd->fd, 0);
}

static void
close_state_file(const char *filename, state_file_descr_t *statefd)
{
	truncate_state_file(statefd);
	(void) close(statefd->fd);
	(void) unlink(filename);
}

/*
 * Grow the state file by the chunk size specified in statefd
 * and mmap it.
 */
static void
grow_state_file(state_file_descr_t *statefd)
{
	size_t size;
	int max_elements;
	int error_num;

	max_elements = statefd->chunk_size;
	if (statefd->state_file)
		max_elements += statefd->state_file->max_elements;

	size = sizeof (state_file_t) +
		statefd->element_size * max_elements;

	if (ftruncate(statefd->fd, size) != 0) {
		error_num = errno;
		rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR,
			"ftruncate", strerror(error_num));
		rcmd_exit(error_num);
		/*NOTREACHED*/
	}

	/* LINTED */
	statefd->state_file = (state_file_t *)mmap(NULL, size,
		PROT_READ|PROT_WRITE, MAP_SHARED, statefd->fd, 0);

	if (statefd->state_file == MAP_FAILED) {
		error_num = errno;
		rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR,
			"mmap", strerror(error_num));
		rcmd_exit(error_num);
		/*NOTREACHED*/
	}

	statefd->index = statefd->state_file->max_elements;
	statefd->state_file->max_elements = max_elements;
	statefd->state_file->version = statefd->version;
}

/*
 * Given index into state element array, get the pointer to the actual
 * state element.
 * If flag is non-null set *flag to
 *	TRUE if the state element is currently is use.
 *	FALSE if the state element is free.
 */
static void *
get_state_element(state_file_descr_t *statefd, int index, int *flag)
{
	char *ptr;

	if (statefd->state_file &&
	    (index < statefd->state_file->max_elements)) {

		ptr = (char *)(statefd->state_file);
		ptr += sizeof (state_file_t) +
			index * statefd->element_size;

		if (flag) {
			*flag = (((state_element_t *)((void *)ptr))->flags &
				STATE_ELEMENT_IN_USE) ? 1 : 0;
		}

		ptr += sizeof (state_element_t);
	} else
		ptr = NULL;

	return ((void *)ptr);
}

/*
 * Allocate a state element entry in the state file and return a pointer
 * to the allocated entry.
 * If index is non-null set *index to index into the state element array
 * of the allocated entry.
 */
static void *
allocate_state_element(state_file_descr_t *statefd, int *index)
{
	void *x;
	int i;
	int flag;

	if (statefd->state_file) {
		/* find an empty slot */
		for (i = 0; i < statefd->state_file->max_elements; i++) {
			x = get_state_element(statefd, statefd->index,
						&flag);
			assert(x != NULL);

			if (flag == 0)
				/* entry is free */
				break;

			statefd->index++;
			if (statefd->index >= statefd->state_file->max_elements)
				statefd->index = 0;
		}
	}

	if (statefd->state_file == NULL ||
		i == statefd->state_file->max_elements) {

		/* All entries are in use. Grow the list */
		grow_state_file(statefd);
		x = get_state_element(statefd, statefd->index, &flag);
		assert(flag == 0);
	}

	if (index != NULL)
		*index = statefd->index;

	statefd->index++;
	if (statefd->index >= statefd->state_file->max_elements)
		statefd->index = 0;

	((state_element_t *)x - 1)->flags |= STATE_ELEMENT_IN_USE;
	return (x);
}

static void
free_state_element(void *x)
{
	((state_element_t *)x - 1)->flags &= ~STATE_ELEMENT_IN_USE;
}

/*
 * Kill the pids contained in ps state file.
 */
static void
script_ps_state_file_kill_pids(void)
{
	ps_state_element_t *x;
	char procfile[80];
	psinfo_t psi;
	int fd, i, flag;

	/* LINTED */
	for (i = 0; 1; i++) {
		if ((x = (ps_state_element_t *)get_state_element(
					&script_ps_statefd, i, &flag)) == NULL)
			break;

		if (flag == 1) { /* the entry is in use */
			(void) snprintf(procfile, 80, "/proc/%ld/psinfo",
					(long)x->pid);
			if ((fd = open(procfile, O_RDONLY)) != -1 &&
				read(fd, &psi, sizeof (psi)) == sizeof (psi) &&
				strcmp(psi.pr_fname,
				x->script_name) == 0) {

				(void) close(fd);

				/*
				 * just a safety check to not to blow up
				 * system processes if the file is ever corrupt
				 */
				if (x->pid > 1) {
					rcm_log_message(RCM_DEBUG,
					"script_ps_state_file_kill_pids: "
					"killing script_name = %s pid = %ld\n",
					x->script_name, x->pid);

					/* kill the process group */
					(void) kill(-(x->pid), SIGKILL);
				}
			} else {
				if (fd != -1)
					(void) close(fd);
			}
			free_state_element((void *)x);
		}
	}
}

/*
 * Add a state element entry to ps state file.
 */
static void
script_ps_state_file_add_entry(pid_t pid, char *script_name)
{
	ps_state_element_t *x;

	(void) mutex_lock(&script_lock);

	x = (ps_state_element_t *)allocate_state_element(
		&script_ps_statefd, NULL);

	x->pid = pid;
	(void) strlcpy(x->script_name, script_name, MAXNAMELEN);

	(void) fsync(script_ps_statefd.fd);

	(void) mutex_unlock(&script_lock);
}

/*
 * Remove the state element entry corresponding to pid from the
 * ps state file.
 */
static void
script_ps_state_file_remove_entry(pid_t pid)
{
	ps_state_element_t *x;
	int flag, i;

	(void) mutex_lock(&script_lock);

	/* LINTED */
	for (i = 0; 1; i++) {
		if ((x = (ps_state_element_t *)get_state_element(
					&script_ps_statefd, i, &flag)) == NULL)
			break;

		/* if the state element entry is in use and pid matches */
		if (flag == 1 && x->pid == pid) {
			free_state_element((void *)x);
			break;
		}
	}

	(void) mutex_unlock(&script_lock);
}

/*
 * Get data item id given data item name
 */
static int
dname_to_id(char *dname)
{
	int i;

	for (i = 0; script_data_item_name[i] != NULL; i++) {
		if (strcmp(dname, script_data_item_name[i]) == 0)
			return (i);
	}

	return (-1);
}

/*
 * Called before running any script.
 * This routine waits until the number of script processes running in
 * parallel drops down below to script_max_parallelism.
 */
static void
script_process_sema_wait(void)
{
	int error_num;

	/* LINTED */
	while (1) {
		if (sema_wait(&script_process_sema) == 0)
			return;

		if (errno != EINTR && errno != EAGAIN) {
			error_num = errno;
			rcm_log_message(RCM_ERROR, MF_FUNC_CALL_ERR,
				"sema_wait", strerror(error_num));
			rcmd_exit(error_num);
			/*NOTREACHED*/
		}
	}

	/*NOTREACHED*/
}

/*
 * Fork and execute the script.
 */
static int
run_script(script_info_t *rsi, char *argv[], char *envp[], char **errmsg)
{
	int i, p1 = -1, p2 = -1;
	struct rlimit rlp;
	struct stat stats;

	rcm_log_message(RSCR_TRACE, "run_script: script name = %s\n",
					rsi->script_full_name);

	for (i = 0; argv[i] != NULL; i++)
		rcm_log_message(RSCR_TRACE, "run_script: argv[%d] = %s\n",
					i, argv[i]);

	*errmsg = NULL;

	/* check that the script exists */
	if (stat(rsi->script_full_name, &stats) != 0)
		goto error;

	/*
	 * If the syscall pipe fails because of reaching the max open file
	 * count per process then dynamically increase the limit on the max
	 * open file count.
	 *
	 * At present the rcm_daemon consumes file descriptor
	 * entries for the following files.
	 *   RCM_STATE_FILE   - /var/run/rcm_daemon_state
	 *   DAEMON_LOCK_FILE - /var/run/rcm_daemon_lock
	 *   RCM_SERVICE_DOOR - /var/run/rcm_daemon_door
	 *   proc files in the format "/proc/pid/as" for each pid
	 *	communicating with the rcm_daemon via doors
	 *   dlopen for each rcm module
	 *   When in daemon mode stdin, stdout and stderr are closed;
	 *	/dev/null opened and duped to stdout, and stderr
	 *   openlog
	 *   Some files which are opened briefly and closed such as
	 *	directory files.
	 *   Two file descriptors for each script in running state.
	 *	Note that the constant script_max_parallelism sets an
	 *	upper cap on how many rcm scripts can run in
	 *	parallel.
	 */
	if ((p1 = pipe(rsi->pipe1)) == -1 || (p2 = pipe(rsi->pipe2)) == -1) {
		if ((errno == EMFILE) &&
			(getrlimit(RLIMIT_NOFILE, &rlp) == 0)) {

			rlp.rlim_cur += 16;
			if (rlp.rlim_max < rlp.rlim_cur)
				rlp.rlim_max = rlp.rlim_cur;
			(void) setrlimit(RLIMIT_NOFILE, &rlp);

			if (p1 == -1) {
				if ((p1 = pipe(rsi->pipe1)) == -1)
					goto error;
			}
			if ((p2 = pipe(rsi->pipe2)) == -1)
				goto error;
		} else
			goto error;
	}

forkagain:
	if ((rsi->pid = fork1()) == (pid_t)-1) {
		if (errno == EINTR || errno == EAGAIN)
			goto forkagain;

		goto error;
	}

	if (rsi->pid == 0) {
		/* child process */

		(void) setsid();

		/* close stdin, stdout and stderr */
		(void) close(0);
		(void) close(1);
		(void) close(2);

		/* set stdin to /dev/null */
		(void) open("/dev/null", O_RDWR, 0);

		/* redirect stdout and stderr to pipe */
		(void) dup2(rsi->pipe1[CHILD_END_OF_PIPE], 1);
		(void) dup2(rsi->pipe2[CHILD_END_OF_PIPE], 2);

		/* close all other file descriptors */
		closefrom(3);

		/* restore original file limit */
		(void) setrlimit(RLIMIT_NOFILE, &file_limit);

		/* set current working dir */
		if (stats.st_uid == 0) {
			/* root */
			if (chdir("/var/run") == -1)
				_exit(127);
		} else {
			if (chdir("/tmp") == -1)
				_exit(127);
		}

		/*
		 * setuid sets real, effective and saved user ids to the
		 * given id.
		 * setgid sets real, effective and saved group ids to the
		 * given id.
		 */
		(void) setgid(stats.st_gid);
		(void) setuid(stats.st_uid);

		(void) execve(rsi->script_full_name, argv, envp);
		_exit(127);
		/*NOTREACHED*/
	}

	(void) close(rsi->pipe1[CHILD_END_OF_PIPE]);
	(void) close(rsi->pipe2[CHILD_END_OF_PIPE]);

	script_ps_state_file_add_entry(rsi->pid, rsi->script_name);

	return (0);

error:
	*errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR,
			rsi->script_name, strerror(errno));

	if (p1 != -1) {
		(void) close(rsi->pipe1[PARENT_END_OF_PIPE]);
		(void) close(rsi->pipe1[CHILD_END_OF_PIPE]);
	}

	if (p2 != -1) {
		(void) close(rsi->pipe2[PARENT_END_OF_PIPE]);
		(void) close(rsi->pipe2[CHILD_END_OF_PIPE]);
	}

	return (-1);
}

/*
 * Reads one line of input (including the newline character) from the
 * given file descriptor "fd" to buf.
 * maxbuflen specifies the size of memory allocated for buf.
 * Timeoutval is the max timeout value in seconds for the script to supply
 * input. A timeoutval of 0 implies no timeout.
 *
 * Upon return *buflen contains the number of bytes read.
 *
 * Return values:
 *   0  success
 *   -1 an error occured
 *   -2 timeout occurred
 *   -3 script exited
 */
static int
get_line(int fd,
	char *fdname,
	char *buf,
	int maxbuflen,
	size_t *buflen,
	time_t timeoutval,
	int *error_num)
{
	char c = '\0';
	struct pollfd fds[1];
	int x;
	size_t len = 0;
	char *ptr;
	int timeit;
	time_t deadline;
	int rval = 0;

	if (timeoutval) {
		timeit = TRUE;
		deadline = time(NULL) + timeoutval;
		fds[0].fd = fd;
		fds[0].events = POLLIN;
	} else
		timeit = FALSE;

	ptr = buf;

	while (c != '\n' && len < (maxbuflen -1)) {
		if (timeit) {
pollagain:
			fds[0].revents = 0;
			timeoutval = deadline - time(NULL);
			if (timeoutval <= 0) {
				rval = -2;
				break;
			}
			x = poll(fds, 1, timeoutval*1000);
			if (x <= 0) {
				if (x == 0)
					/* poll timedout */
					rval = -2;
				else {
					if (errno == EINTR || errno == EAGAIN)
						goto pollagain;
					*error_num = errno;
					rval = -1;
				}
				break;
			}
		}
readagain:
		if ((x = read(fd, &c, 1)) != 1) {
			if (x == 0)
				/*
				 * Script exited. Or more specifically the
				 * script has closed its end of the pipe.
				 */
				rval = -3;
			else {
				if (errno == EINTR || errno == EAGAIN)
					goto readagain;
				*error_num = errno;
				rval = -1;
			}
			break;
		}

		*ptr++ = c;
		len++;
	}

	*ptr = '\0';
	*buflen = len;

	rcm_log_message(RSCR_TRACE,
		"get_line(%s): rval = %d buflen = %d line = %s\n",
		fdname, rval, *buflen, buf);
	return (rval);
}

static void
script_exited(script_info_t *rsi)
{
	if (rsi->flags & STDERR_THREAD_CREATED) {
		rcm_log_message(RSCR_TRACE,
		    "script_exited: doing thr_join (%s)\n", rsi->script_name);
		(void) thr_join(rsi->tid, NULL, NULL);
		rsi->flags &= ~STDERR_THREAD_CREATED;
	}

	(void) close(rsi->pipe1[PARENT_END_OF_PIPE]);
	(void) close(rsi->pipe2[PARENT_END_OF_PIPE]);
	rsi->pipe1[PARENT_END_OF_PIPE] = -1;
	rsi->pipe2[PARENT_END_OF_PIPE] = -1;

	script_ps_state_file_remove_entry(rsi->pid);
	rsi->pid = 0;
	(void) sema_post(&script_process_sema);
}

/*
 * Kill the specified process group
 */
static int
kill_pid(pid_t pid)
{
	time_t deadline, timeleft;
	int child_status;

	/* kill the entire process group */
	(void) kill(-(pid), SIGKILL);

	/* give some time for the script to be killed */
	deadline = time(NULL) + SCRIPT_KILL_TIMEOUT;
	do {
		if (waitpid(pid, &child_status, WNOHANG) == pid)
			return (0);

		/* wait for 100 ms */
		(void) poll(NULL, 0, 100);

		timeleft = deadline - time(NULL);
	} while (timeleft > 0);

	/* script process was not killed successfully */
	return (-1);
}

/*
 * Kill the specified script.
 */
static void
kill_script(script_info_t *rsi)
{
	if (rsi->pid > 1) {
		(void) kill_pid(rsi->pid);
		script_exited(rsi);
		remove_drreq_all(rsi);
	}
}

/*
 * Convert rcm flags parameter to a string.
 * Used for debug prints.
 */
static char *
flags_to_name(int flags, char *buf, int maxbuflen)
{
	(void) snprintf(buf, maxbuflen, "%s%s",
		(flags & RCM_QUERY) ? "RCM_QUERY " : "",
		(flags & RCM_FORCE) ? "RCM_FORCE" : "");

	return (buf);
}

static void
fill_argv(script_info_t *rsi, char *argv[], char *resource_name)
{
	argv[0] = rsi->script_full_name;
	argv[1] = script_cmd_name[rsi->cmd];
	if (resource_name) {
		argv[2] = resource_name;
		argv[3] = NULL;
	} else
		argv[2] = NULL;
}

/*
 * stderr thread:
 * Reads stderr and logs to syslog.
 * Runs as a separate thread.
 */
static void *
read_stderr(script_info_t *rsi)
{
	char buf[MAX_LINE_LEN];
	size_t buflen;
	int error_num;

	while ((get_line(rsi->pipe2[PARENT_END_OF_PIPE], "stderr",
		buf, MAX_LINE_LEN, &buflen, 0, &error_num)) == 0) {
		log_msg(rsi, RCM_ERROR, buf);
	}

	if (buflen)
		log_msg(rsi, RCM_ERROR, buf);

	return (NULL);
}

/* process return data items passed by scripts to the framework */
static int
process_dataitem(script_info_t *rsi, int token, char *value, char **errmsg)
{
	char *ptr;
	int status;

	*errmsg = NULL;

	if (*value == '\0')
		goto error;

	switch (token) {
	case D_SCRIPT_VERSION:
		if (rsi->cmd != C_SCRIPTINFO)
			goto error;

		/* check that value contains only digits */
		for (ptr = value; *ptr != '\0'; ptr++)
			if (isdigit((int)(*ptr)) == 0)
				break;

		if (*ptr == '\0')
			rsi->ver = atoi(value);
		else
			goto error;

		break;

	case D_SCRIPT_FUNC_INFO:
		if (rsi->cmd != C_SCRIPTINFO)
			goto error;

		rcmscript_snprintf(&rsi->func_info_buf,
			&rsi->func_info_buf_len,
			&rsi->func_info_buf_curptr,
			"%s", value);
		break;

	case D_CMD_TIMEOUT:
		if (rsi->cmd != C_SCRIPTINFO)
			goto error;

		/* check that value contains only digits */
		for (ptr = value; *ptr != '\0'; ptr++)
			if (isdigit((int)(*ptr)) == 0)
				break;

		if (*ptr == '\0')
			rsi->cmd_timeout = atoi(value);
		else
			goto error;
		break;

	case D_RESOURCE_NAME:
		if (rsi->cmd != C_REGISTER)
			goto error;

		if (get_capacity_descr(value) != NULL)
			status = rcm_register_capacity(rsi->hdl, value,
					0, NULL);
		else
			status = rcm_register_interest(rsi->hdl, value, 0,
					NULL);

		if (status == RCM_FAILURE && errno == EALREADY)
			status = RCM_SUCCESS;

		if (status != RCM_SUCCESS) {
			rcm_log_message(RCM_ERROR, MS_REGISTER_RSRC_ERR,
				rsi->script_name, value);
		}

		remove_from_unregister(rsi, value);
		break;

	case D_RESOURCE_USAGE_INFO:
		if (rsi->cmd != C_RESOURCEINFO)
			goto error;

		rcmscript_snprintf(&rsi->resource_usage_info_buf,
			&rsi->resource_usage_info_buf_len,
			&rsi->resource_usage_info_buf_curptr,
			"%s", value);
		break;

	case D_FAILURE_REASON:
		rcmscript_snprintf(&rsi->failure_reason_buf,
			&rsi->failure_reason_buf_len,
			&rsi->failure_reason_buf_curptr,
			"%s", value);
		break;

	default:
		goto error;
	}

	return (0);

error:
	*errmsg = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, rsi->script_name);
	return (-1);
}

/* Send the given command to the script and process return data */
static int
do_cmd(script_info_t *rsi, char *argv[], char *envp[], char **errmsg)
{
	char buf[MAX_LINE_LEN];
	size_t buflen;
	int loglevel = -1, continuelog = 0;
	char *ptr, *dname, *value;
	time_t maxsecs;
	time_t deadline;
	int sigaborted = 0;
	int rval, child_status, token;
	int error_num;
	int cmd_timeout = rsi->cmd_timeout;

	*errmsg = NULL;

	script_process_sema_wait();

	if (run_script(rsi, argv, envp, errmsg) == -1) {
		(void) sema_post(&script_process_sema);
		goto error2;
	}

	(void) time(&rsi->lastrun);
	deadline = rsi->lastrun + cmd_timeout;

	if (thr_create(NULL, 0, (void *(*)(void *))read_stderr, rsi,
	    0, &rsi->tid) != 0) {
		*errmsg = dup_err(RCM_ERROR, MF_FUNC_CALL_ERR,
				"thr_create", strerror(errno));
		goto error1;
	}
	rsi->flags |= STDERR_THREAD_CREATED;

	/* LINTED */
	while (1) {
		if (cmd_timeout > 0) {
			maxsecs = deadline - time(NULL);
			if (maxsecs <= 0)
				goto timedout;
		} else
			maxsecs = 0;

		rval = get_line(rsi->pipe1[PARENT_END_OF_PIPE],
				"stdout", buf, MAX_LINE_LEN, &buflen,
				maxsecs, &error_num);

		if (buflen) {
			if (continuelog)
				log_msg(rsi, loglevel, buf);
			else {
				if ((ptr = strchr(buf, '=')) == NULL)
					goto error;

				*ptr = '\0';
				dname = buf;
				value = ptr + 1;
				if ((token = dname_to_id(dname)) == -1)
					goto error;

				switch (token) {
				case D_LOG_ERR:
					loglevel = RCM_ERROR;
					break;

				case D_LOG_WARN:
					loglevel = RCM_WARNING;
					break;

				case D_LOG_INFO:
					loglevel = RCM_INFO;
					break;

				case D_LOG_DEBUG:
					loglevel = RCM_DEBUG;
					break;

				default:
					loglevel = -1;
					break;
				}

				if (loglevel != -1) {
					log_msg(rsi, loglevel, value);
					if (buf[buflen - 1] == '\n')
						continuelog = 0;
					else
						continuelog = 1;
				} else {
					if (buf[buflen - 1] != '\n')
						goto error;

					buf[buflen - 1] = '\0';
					if (process_dataitem(rsi, token,
						value, errmsg) != 0)
						goto error1;
				}
			}
		}

		if (rval == -3) {
			/* script exited */
waitagain:
			if (waitpid(rsi->pid, &child_status, 0)
					!= rsi->pid) {
				if (errno == EINTR || errno == EAGAIN)
					goto waitagain;
				*errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR,
					rsi->script_name, strerror(errno));
				goto error1;
			}

			if (WIFEXITED(child_status)) {
				script_exited(rsi);
				rsi->exit_status = WEXITSTATUS(child_status);
			} else {
				if (sigaborted)
					*errmsg = dup_err(RCM_ERROR,
					MS_TIMEOUT_ERR, rsi->script_name);
				else
					*errmsg = dup_err(RCM_ERROR,
					MS_UNKNOWN_ERR, rsi->script_name);

				/* kill any remaining processes in the pgrp */
				(void) kill(-(rsi->pid), SIGKILL);
				script_exited(rsi);
				goto error2;
			}

			break;
		}

		if (rval == -1) {
			*errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR,
				rsi->script_name, strerror(errno));
			goto error1;
		}

		if (rval == -2) {
timedout:
			/* timeout occurred */
			if (sigaborted == 0) {
				(void) kill(rsi->pid, SIGABRT);
				sigaborted = 1;
				/* extend deadline */
				deadline += SCRIPT_ABORT_TIMEOUT;
			} else {
				*errmsg = dup_err(RCM_ERROR,
					MS_TIMEOUT_ERR, rsi->script_name);
				goto error1;
			}
		}
	}

	return (0);

error:
	*errmsg = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, rsi->script_name);

error1:
	kill_script(rsi);

error2:
	return (-1);
}

static int
do_script_info(script_info_t *rsi)
{
	char *argv[MAX_ARGS];
	int status = RCM_FAILURE;
	int err = 0;
	char *errmsg = NULL;

	rcm_log_message(RSCR_TRACE, "do_script_info: script name = %s\n",
						rsi->script_name);

	rsi->cmd = C_SCRIPTINFO;
	rsi->func_info_buf = NULL;
	rsi->failure_reason_buf = NULL;
	fill_argv(rsi, argv, NULL);

	if (do_cmd(rsi, argv, script_env, &errmsg) == 0) {
		switch (rsi->exit_status) {
		case E_SUCCESS:
			if (rsi->func_info_buf != NULL &&
				rsi->failure_reason_buf == NULL) {

				if (rsi->ver >= SCRIPT_API_MIN_VER &&
					rsi->ver <= SCRIPT_API_MAX_VER)
					status = RCM_SUCCESS;
				else
					rcm_log_message(RCM_ERROR,
					MS_UNSUPPORTED_VER, rsi->script_name,
					rsi->ver);
			} else
				err = 1;
			break;

		case E_FAILURE:
			if (rsi->failure_reason_buf != NULL) {
				rcm_log_message(RCM_ERROR, MS_SCRIPTINFO_ERR,
					rsi->script_name,
					rsi->failure_reason_buf);
			} else
				err = 1;
			break;

		default:
			err = 1;
			break;
		}
		if (err)
			rcm_log_message(RCM_ERROR, MS_PROTOCOL_ERR,
				rsi->script_name);
	} else if (errmsg)
		(void) free(errmsg);

	if (status != RCM_SUCCESS && rsi->func_info_buf != NULL)
		free(rsi->func_info_buf);

	if (rsi->failure_reason_buf)
		free(rsi->failure_reason_buf);

	return (status);
}

static int
do_dr(script_info_t *rsi, char *argv[], char *envp[], char **info)
{
	int status = RCM_FAILURE;
	int err = 0;

	rsi->failure_reason_buf = NULL;

	if (do_cmd(rsi, argv, envp, info) == 0) {
		switch (rsi->exit_status) {
		case E_SUCCESS:
		case E_UNSUPPORTED_CMD:
			if (rsi->failure_reason_buf == NULL)
				status = RCM_SUCCESS;
			else
				err = 1;
			break;

		case E_FAILURE:
		case E_REFUSE:
			if (rsi->failure_reason_buf != NULL) {
				*info = rsi->failure_reason_buf;
				rsi->failure_reason_buf = NULL;
			} else
				err = 1;
			break;

		default:
			err = 1;
			break;
		}

		if (err)
			*info = dup_err(RCM_ERROR, MS_PROTOCOL_ERR,
				rsi->script_name);
	}

	if (rsi->failure_reason_buf)
		free(rsi->failure_reason_buf);

	return (status);
}

/*
 * get_info entry point
 */
/* ARGSUSED */
static int
script_get_info(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	char **info,
	char **error,
	nvlist_t *props,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	int status = RCM_FAILURE;
	int err = 0;

	rcm_log_message(RSCR_TRACE, "script_get_info: resource = %s\n",
				resource_name);

	*info = NULL;
	*error = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = C_RESOURCEINFO;
	rsi->resource_usage_info_buf = NULL;
	rsi->failure_reason_buf = NULL;
	fill_argv(rsi, argv, resource_name);

	if (do_cmd(rsi, argv, script_env, error) == 0) {
		switch (rsi->exit_status) {
		case E_SUCCESS:
			if (rsi->resource_usage_info_buf != NULL &&
				rsi->failure_reason_buf == NULL) {

				*info = rsi->resource_usage_info_buf;
				rsi->resource_usage_info_buf = NULL;
				status = RCM_SUCCESS;
			} else
				err = 1;
			break;

		case E_FAILURE:
			if (rsi->failure_reason_buf != NULL) {
				*error = rsi->failure_reason_buf;
				rsi->failure_reason_buf = NULL;
			} else
				err = 1;
			break;

		default:
			err = 1;
			break;
		}
		if (err)
			*error = dup_err(RCM_ERROR, MS_PROTOCOL_ERR,
				rsi->script_name);
	}

	if (rsi->resource_usage_info_buf)
		free(rsi->resource_usage_info_buf);

	if (rsi->failure_reason_buf)
		free(rsi->failure_reason_buf);

	(void) mutex_unlock(&rsi->channel_lock);

	return (status);
}

static void
add_for_unregister(script_info_t *rsi)
{
	module_t *module = rsi->module;
	client_t *client;
	rcm_queue_t *head;
	rcm_queue_t *q;

	(void) mutex_lock(&rcm_req_lock);

	head = &module->client_q;

	for (q = head->next; q != head; q = q->next) {
		client = RCM_STRUCT_BASE_ADDR(client_t, q, queue);
		client->prv_flags |= RCM_NEED_TO_UNREGISTER;
	}

	(void) mutex_unlock(&rcm_req_lock);
}

static void
remove_from_unregister(script_info_t *rsi, char *resource_name)
{
	module_t *module = rsi->module;
	client_t *client;
	rcm_queue_t *head;
	rcm_queue_t *q;

	(void) mutex_lock(&rcm_req_lock);

	head = &module->client_q;

	for (q = head->next; q != head; q = q->next) {
		client = RCM_STRUCT_BASE_ADDR(client_t, q, queue);
		if (strcmp(client->alias, resource_name) == 0) {
			client->prv_flags &= ~RCM_NEED_TO_UNREGISTER;
			break;
		}
	}

	(void) mutex_unlock(&rcm_req_lock);
}

static void
complete_unregister(script_info_t *rsi)
{
	module_t *module = rsi->module;
	client_t *client;
	rcm_queue_t *head;
	rcm_queue_t *q;

	(void) mutex_lock(&rcm_req_lock);

	head = &module->client_q;

	for (q = head->next; q != head; q = q->next) {
		client = RCM_STRUCT_BASE_ADDR(client_t, q, queue);
		if (client->prv_flags & RCM_NEED_TO_UNREGISTER) {
			client->prv_flags &= ~RCM_NEED_TO_UNREGISTER;
			client->state = RCM_STATE_REMOVE;
		}
	}

	(void) mutex_unlock(&rcm_req_lock);
}

/*
 * register_interest entry point
 */
static int
script_register_interest(rcm_handle_t *hdl)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	int status = RCM_FAILURE;
	int err = 0;
	char *errmsg = NULL;

	rcm_log_message(RSCR_TRACE,
		"script_register_interest: script name = %s\n",
		rsi->script_name);

	(void) mutex_lock(&rsi->channel_lock);

	if (rsi->drreq_q.next != &rsi->drreq_q) {
		/* if DR is already in progress no need to register again */
		(void) mutex_unlock(&rsi->channel_lock);
		return (RCM_SUCCESS);
	}

	rsi->hdl = hdl;
	rsi->cmd = C_REGISTER;
	rsi->failure_reason_buf = NULL;
	fill_argv(rsi, argv, NULL);

	add_for_unregister(rsi);

	if (do_cmd(rsi, argv, script_env, &errmsg) == 0) {
		switch (rsi->exit_status) {
		case E_SUCCESS:
			status = RCM_SUCCESS;
			break;

		case E_FAILURE:
			if (rsi->failure_reason_buf != NULL) {
				rcm_log_message(RCM_ERROR, MS_REGISTER_ERR,
					rsi->script_name,
					rsi->failure_reason_buf);
			} else
				err = 1;
			break;

		default:
			err = 1;
			break;
		}
		if (err)
			rcm_log_message(RCM_ERROR, MS_PROTOCOL_ERR,
				rsi->script_name);
	} else if (errmsg)
		(void) free(errmsg);

	complete_unregister(rsi);

	if (rsi->failure_reason_buf)
		free(rsi->failure_reason_buf);

	(void) mutex_unlock(&rsi->channel_lock);

	return (status);
}

/*
 * Add the specified resource name to the drreq_q.
 */
static void
add_drreq(script_info_t *rsi, char *resource_name)
{
	rcm_queue_t *head = &rsi->drreq_q;
	rcm_queue_t *q;
	drreq_t *drreq;

	/* check if the dr req is already in the list */
	for (q = head->next; q != head; q = q->next) {
		drreq = RCM_STRUCT_BASE_ADDR(drreq_t, q, queue);
		if (strcmp(drreq->resource_name, resource_name) == 0)
			/* dr req is already present in the queue */
			return;
	}

	drreq = (drreq_t *)rcmscript_calloc(1, sizeof (drreq_t));
	drreq->resource_name = rcmscript_strdup(resource_name);

	rcm_enqueue_tail(&rsi->drreq_q, &drreq->queue);
}

/*
 * Remove the dr req for the specified resource name from the drreq_q.
 */
static void
remove_drreq(script_info_t *rsi, char *resource_name)
{
	rcm_queue_t *head = &rsi->drreq_q;
	rcm_queue_t *q;
	drreq_t *drreq;

	/* search for dr req and remove from the list */
	for (q = head->next; q != head; q = q->next) {
		drreq = RCM_STRUCT_BASE_ADDR(drreq_t, q, queue);
		if (strcmp(drreq->resource_name, resource_name) == 0)
			break;
	}

	if (q != head) {
		/* found drreq on the queue */
		rcm_dequeue(&drreq->queue);
		free(drreq->resource_name);
		free(drreq);
	}
}

/*
 * Remove all dr req's.
 */
static void
remove_drreq_all(script_info_t *rsi)
{
	drreq_t *drreq;

	while (rsi->drreq_q.next != &rsi->drreq_q) {
		drreq = RCM_STRUCT_BASE_ADDR(drreq_t,
					rsi->drreq_q.next, queue);
		remove_drreq(rsi, drreq->resource_name);
	}
}

/*
 * request_offline entry point
 */
/* ARGSUSED */
static int
script_request_offline(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	char *envp[MAX_ENV_PARAMS];
	char flags_name[MAX_FLAGS_NAME_LEN];
	int status;
	int i;

	rcm_log_message(RSCR_TRACE,
		"script_request_offline: resource = %s flags = %s\n",
			resource_name,
			flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN));

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = (flag & RCM_QUERY) ? C_QUERYREMOVE : C_PREREMOVE;

	if (rsi->cmd == C_PREREMOVE)
		add_drreq(rsi, resource_name);

	fill_argv(rsi, argv, resource_name);
	copy_env(script_env, envp);
	for (i = 0; envp[i] != NULL; i++)
		;
	envp[i++] = (flag & RCM_FORCE) ? script_env_force : script_env_noforce;
	envp[i] = NULL;

	status = do_dr(rsi, argv, envp, info);

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

/*
 * notify_online entry point
 */
/* ARGSUSED */
static int
script_notify_online(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	int status;

	rcm_log_message(RSCR_TRACE, "script_notify_online: resource = %s\n",
				resource_name);

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = C_UNDOREMOVE;
	fill_argv(rsi, argv, resource_name);

	status = do_dr(rsi, argv, script_env, info);

	remove_drreq(rsi, resource_name);

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

/*
 * notify_remove entry point
 */
/* ARGSUSED */
static int
script_notify_remove(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	int status;

	rcm_log_message(RSCR_TRACE, "script_notify_remove: resource = %s\n",
				resource_name);

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = C_POSTREMOVE;
	fill_argv(rsi, argv, resource_name);

	status = do_dr(rsi, argv, script_env, info);

	remove_drreq(rsi, resource_name);

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

/*
 * request_suspend entry point
 */
/* ARGSUSED */
static int
script_request_suspend(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	timespec_t *interval,
	uint_t flag,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *buf = NULL;
	char *curptr = NULL;
	char *argv[MAX_ARGS];
	char *envp[MAX_ENV_PARAMS];
	char flags_name[MAX_FLAGS_NAME_LEN];
	int buflen = 0;
	long seconds;
	int status;
	int i;

	rcm_log_message(RSCR_TRACE,
	    "script_request_suspend: resource = %s flags = %s\n", resource_name,
	    flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN));

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = (flag & RCM_QUERY) ? C_QUERYSUSPEND : C_PRESUSPEND;

	if (rsi->cmd == C_PRESUSPEND)
		add_drreq(rsi, resource_name);

	fill_argv(rsi, argv, resource_name);

	copy_env(script_env, envp);
	for (i = 0; envp[i] != NULL; i++);

	envp[i++] = (flag & RCM_FORCE) ? script_env_force : script_env_noforce;

	if (interval) {
		/*
		 * Merge the seconds and nanoseconds, rounding up if there
		 * are any remainder nanoseconds.
		 */
		seconds = interval->tv_sec + (interval->tv_nsec / 1000000000L);
		if (interval->tv_nsec % 1000000000L)
			seconds += (interval->tv_sec > 0) ? 1L : -1L;
		rcmscript_snprintf(&buf, &buflen, &curptr, script_env_interval,
		    seconds);
		envp[i++] = buf;
	}

	envp[i] = NULL;

	status = do_dr(rsi, argv, envp, info);

	(void) mutex_unlock(&rsi->channel_lock);
	if (buf)
		free(buf);
	return (status);
}

/*
 * notify_resume entry point
 */
/* ARGSUSED */
static int
script_notify_resume(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	int status;

	rcm_log_message(RSCR_TRACE, "script_notify_resume: resource = %s\n",
	    resource_name);

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = (flag & RCM_SUSPENDED) ? C_POSTRESUME : C_CANCELSUSPEND;
	fill_argv(rsi, argv, resource_name);

	status = do_dr(rsi, argv, script_env, info);

	remove_drreq(rsi, resource_name);

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

static capacity_descr_t capacity_type[] = {
	{ "SUNW_memory", MATCH_EXACT,
		"new_pages", "RCM_ENV_CAPACITY",
		"page_size", "RCM_ENV_UNIT_SIZE",
		"", ""},
	{ "SUNW_cpu", MATCH_EXACT,
		"new_total", "RCM_ENV_CAPACITY",
		"new_cpu_list", "RCM_ENV_CPU_IDS",
		"", ""},
	{ "SUNW_cpu/set", MATCH_PREFIX,
		"new_total", "RCM_ENV_CAPACITY",
		"new_cpu_list", "RCM_ENV_CPU_IDS",
		"", ""},
	{ "", MATCH_INVALID, "", "" }
};

static capacity_descr_t *
get_capacity_descr(char *resource_name)
{
	int i;

	for (i = 0; *capacity_type[i].resource_name != '\0'; i++) {
		if ((capacity_type[i].match_type == MATCH_EXACT &&
			strcmp(capacity_type[i].resource_name,
			resource_name) == 0) ||
			(capacity_type[i].match_type == MATCH_PREFIX &&
			strncmp(capacity_type[i].resource_name,
			resource_name,
			strlen(capacity_type[i].resource_name)) == 0))

			return (&capacity_type[i]);
	}

	return (NULL);
}

static int
build_env_for_capacity(script_info_t *rsi,
	char *resource_name,
	uint_t flag,
	nvlist_t *capacity_info,
	char *envp[],
	int *dynamic_env_index,
	char **errmsg)
{
	int p, i;
	capacity_descr_t *capa = NULL;
	nvpair_t *nvpair;
	char *buf;
	char *curptr;
	int buflen;
	int error;
	uint_t n;

	copy_env(script_env, envp);
	for (p = 0; envp[p] != NULL; p++)
		;

	if (rsi->cmd == C_QUERYCAPACITY || rsi->cmd == C_PRECAPACITY)
		envp[p++] = (flag & RCM_FORCE) ? script_env_force :
						script_env_noforce;

	envp[p] = NULL;
	*dynamic_env_index = p;

	if ((capa = get_capacity_descr(resource_name)) == NULL) {
		*errmsg = dup_err(RCM_ERROR, MF_UNKNOWN_RSRC_ERR,
			resource_name, rsi->script_name);
		return (-1);
	}

	for (i = 0; *capa->param[i].nvname != '\0'; i++) {
		nvpair = NULL;
		while ((nvpair = nvlist_next_nvpair(capacity_info, nvpair))
				!= NULL) {
			if (strcmp(nvpair_name(nvpair),
					capa->param[i].nvname) == 0)
				break;
		}

		if (nvpair == NULL) {
			*errmsg = dup_err(RCM_ERROR, MF_NV_ERR,
				rsi->script_name);
			return (-1);
		}

		error = 0;
		buf = NULL;

		rcmscript_snprintf(&buf, &buflen, &curptr, "%s=",
				capa->param[i].envname);

		switch (nvpair_type(nvpair)) {
		case DATA_TYPE_INT16:
		{
			int16_t x;

			if (nvpair_value_int16(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%hd", (short)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT16:
		{
			uint16_t x;

			if (nvpair_value_uint16(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%hu", (unsigned short)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_INT32:
		{
			int32_t x;

			if (nvpair_value_int32(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%d", (int)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT32:
		{
			uint32_t x;

			if (nvpair_value_uint32(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%u", (uint_t)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_INT64:
		{
			int64_t x;

			if (nvpair_value_int64(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%lld", (long long)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT64:
		{
			uint64_t x;

			if (nvpair_value_uint64(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
					"%llu", (unsigned long long)x);
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_INT16_ARRAY:
		{
			int16_t *x;

			if (nvpair_value_int16_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%hd%s",
						(short)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT16_ARRAY:
		{
			uint16_t *x;

			if (nvpair_value_uint16_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%hu%s",
						(unsigned short)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_INT32_ARRAY:
		{
			int32_t *x;

			if (nvpair_value_int32_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%d%s",
						(int)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT32_ARRAY:
		{
			uint32_t *x;

			if (nvpair_value_uint32_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%u%s",
						(uint_t)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_INT64_ARRAY:
		{
			int64_t *x;

			if (nvpair_value_int64_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%lld%s",
						(long long)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_UINT64_ARRAY:
		{
			uint64_t *x;

			if (nvpair_value_uint64_array(nvpair, &x, &n) == 0) {
				while (n--) {
					rcmscript_snprintf(&buf, &buflen,
						&curptr, "%llu%s",
						(unsigned long long)(*x),
						(n == 0) ? "" : " ");
					x++;
				}
			} else
				error = 1;
			break;
		}

		case DATA_TYPE_STRING:
		{
			char *x;

			if (nvpair_value_string(nvpair, &x) == 0) {
				rcmscript_snprintf(&buf, &buflen, &curptr,
						"%s", x);
			} else
				error = 1;
			break;
		}


		default:
			error = 1;
			break;
		}

		envp[p++] = buf;

		if (error) {
			envp[p] = NULL;
			for (p = *dynamic_env_index; envp[p] != NULL; p++)
				free(envp[p]);
			*errmsg = dup_err(RCM_ERROR, MF_NV_ERR,
				rsi->script_name);
			return (-1);
		}
	}

	envp[p] = NULL;

	return (0);
}

/*
 * request_capacity_change entry point
 */
/* ARGSUSED */
static int
script_request_capacity_change(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	nvlist_t *capacity_info,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	char *envp[MAX_ENV_PARAMS];
	char flags_name[MAX_FLAGS_NAME_LEN];
	int status;
	int dynamic_env_index;

	rcm_log_message(RSCR_TRACE,
		"script_request_capacity_change: resource = %s flags = %s\n",
			resource_name,
			flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN));

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = (flag & RCM_QUERY) ? C_QUERYCAPACITY : C_PRECAPACITY;
	fill_argv(rsi, argv, resource_name);

	if (build_env_for_capacity(rsi, resource_name, flag,
			capacity_info, envp, &dynamic_env_index, info) == 0) {

		status = do_dr(rsi, argv, envp, info);

		while (envp[dynamic_env_index] != NULL) {
			free(envp[dynamic_env_index]);
			dynamic_env_index++;
		}
	} else
		status = RCM_FAILURE;

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

/*
 * notify_capacity_change entry point
 */
/* ARGSUSED */
static int
script_notify_capacity_change(rcm_handle_t *hdl,
	char *resource_name,
	pid_t pid,
	uint_t flag,
	nvlist_t *capacity_info,
	char **info,
	rcm_info_t **dependent_info)
{
	script_info_t *rsi = hdl->module->rsi;
	char *argv[MAX_ARGS];
	char *envp[MAX_ENV_PARAMS];
	int status;
	int dynamic_env_index;

	rcm_log_message(RSCR_TRACE,
	"script_notify_capacity_change: resource = %s\n", resource_name);

	*info = NULL;

	(void) mutex_lock(&rsi->channel_lock);

	rsi->hdl = hdl;
	rsi->cmd = C_POSTCAPACITY;
	fill_argv(rsi, argv, resource_name);

	if (build_env_for_capacity(rsi, resource_name, flag,
			capacity_info, envp, &dynamic_env_index, info) == 0) {

		status = do_dr(rsi, argv, envp, info);

		while (envp[dynamic_env_index] != NULL) {
			free(envp[dynamic_env_index]);
			dynamic_env_index++;
		}
	} else
		status = RCM_FAILURE;

	(void) mutex_unlock(&rsi->channel_lock);
	return (status);
}

/* Log the message to syslog */
static void
log_msg(script_info_t *rsi, int level, char *msg)
{
	rcm_log_msg(level, MS_LOG_MSG, rsi->script_name, msg);
}

/*PRINTFLIKE2*/
static char *
dup_err(int level, char *format, ...)
{
	va_list ap;
	char buf1[1];
	char *buf2;
	int n;

	va_start(ap, format);
	n = vsnprintf(buf1, 1, format, ap);
	va_end(ap);

	if (n > 0) {
		n++;
		if (buf2 = (char *)malloc(n)) {
			va_start(ap, format);
			n = vsnprintf(buf2, n, format, ap);
			va_end(ap);
			if (n > 0) {
				if (level != -1)
					rcm_log_message(level, buf2);
				return (buf2);
			}
			free(buf2);
		}
	}

	return (NULL);
}

/*PRINTFLIKE4*/
static void
rcmscript_snprintf(char **buf, int *buflen, char **curptr, char *format, ...)
{
/* must be power of 2 otherwise RSCR_ROUNDUP would break */
#define	SPRINTF_CHUNK_LEN	512
#define	SPRINTF_MIN_CHUNK_LEN	64

	va_list ap;
	int offset, bytesneeded, bytesleft, error_num;

	if (*buf == NULL) {
		*buflen = 0;
		*curptr = NULL;
	}

	offset = *curptr - *buf;
	bytesneeded = SPRINTF_MIN_CHUNK_LEN;
	bytesleft = *buflen - offset;

	/* LINTED */
	while (1) {
		if (bytesneeded > bytesleft) {
			*buflen += RSCR_ROUNDUP(bytesneeded - bytesleft,
					SPRINTF_CHUNK_LEN);
			if ((*buf = (char *)realloc(*buf, *buflen)) == NULL) {
				error_num = errno;
				rcm_log_message(RCM_ERROR,
					MF_MEMORY_ALLOCATION_ERR,
					strerror(error_num));
				rcmd_exit(error_num);
				/*NOTREACHED*/
			}
			*curptr = *buf + offset;
			bytesleft = *buflen - offset;
		}

		va_start(ap, format);
		bytesneeded = vsnprintf(*curptr, bytesleft, format, ap);
		va_end(ap);

		if (bytesneeded < 0)  {
			/* vsnprintf encountered an error */
			error_num = errno;
			rcm_log_message(RCM_ERROR, MF_FUNC_CALL_ERR,
				"vsnprintf", strerror(error_num));
			rcmd_exit(error_num);
			/*NOTREACHED*/

		} else if (bytesneeded < bytesleft) {
			/* vsnprintf succeeded */
			*curptr += bytesneeded;
			return;

		} else {
			bytesneeded++; /* to account for storage for '\0' */
		}
	}
}

static char *
rcmscript_strdup(char *str)
{
	char *dupstr;

	if ((dupstr = strdup(str)) == NULL) {
		rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR,
			strerror(errno));
		rcmd_exit(errno);
		/*NOTREACHED*/
	}

	return (dupstr);
}

static void *
rcmscript_malloc(size_t len)
{
	void *ptr;

	if ((ptr = malloc(len)) == NULL) {
		rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR,
			strerror(errno));
		rcmd_exit(errno);
		/*NOTREACHED*/
	}

	return (ptr);
}

static void *
rcmscript_calloc(size_t nelem, size_t elsize)
{
	void *ptr;

	if ((ptr = calloc(nelem, elsize)) == NULL) {
		rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR,
			strerror(errno));
		rcmd_exit(errno);
		/*NOTREACHED*/
	}

	return (ptr);
}