root/usr/src/cmd/rcm_daemon/common/rcm_script.c
/*
 * 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.
 */

/*
 * 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);
}